I have entities with relationships :
Packaging M---N Factories
Packaging M---N Components
f.e. content of tables is : Packaging (Type1) has factories (A, B, C) and Components (1, 2, 3, 4)
I want to write LINQ which will give me result as :
Type1 A 1
Type1 A 2
Type1 A 3
Type1 A 4
Type1 B 1
Type1 B 2
Type1 B 3
Type1 B 4
Type1 C 1
Type1 C 2
Type1 C 3
Type1 C 4
How can I get this using LINQ with lambda syntax ?
You want to select many objects from each source item. So use the SelectMany operator.
packagingCollection.SelectMany(p => p.Components.Select(c => new {
P = p,
C = c
})).SelectMany(x => x.P.Factories.Select(f => new {
P = x.P,
C = x.C,
F = f
})).Select(y => new {
PackagingName = y.P.Name,
ComponentName = y.C.Name,
FactoryName = y.F.Name
})
What you're looking for is a Cartesian Product. I'm going to define two small objects so that I can show a concrete LINQ query. For convenience I'll make the package type an enum, but in reality it doesn't matter so long as the objects representing a factory and component have a common field of the same type.
private enum PackagingType
{
Type1
};
private class Factory
{
public string Name { get; set; }
public PackagingType Type { get; set; }
}
private class Component
{
public string Name { get; set; }
public PackagingType Type { get; set; }
}
var factories = new List<Factory>
{
new Factory {Name = "A", Type = PackagingType.Type1},
new Factory {Name = "B", Type = PackagingType.Type1},
new Factory {Name = "C", Type = PackagingType.Type1}
};
var components = new List<Component>
{
new Component {Name = "1", Type = PackagingType.Type1},
new Component {Name = "2", Type = PackagingType.Type1},
new Component {Name = "3", Type = PackagingType.Type1},
new Component {Name = "4", Type = PackagingType.Type1}
};
Then we can join factories to components on the Type field using the LINQ extension method Join, which returns a Cartesian Product for us. That looks like:
var cartesianProduct = factories.Join(components,
factory => factory.Type,
component => component.Type,
(factory, component) =>
new
{
Type = factory.Type,
FactoryName = factory.Name,
ComponentName = component.Name
});
This results in the output:
Type1 A 1
Type1 A 2
Type1 A 3
Type1 A 4
Type1 B 1
Type1 B 2
Type1 B 3
Type1 B 4
Type1 C 1
Type1 C 2
Type1 C 3
Type1 C 4
If you had a third object with a Many to Many relationship based on Packaging then you could simply join that list of objects with the current Cartesian Product on its Type field to get a Cartesian Product across all three objects. See Eric Lippert's answer here for more information.
Related
I have an entity. A table named TABLE with 2 columns and 6 rows, and a connection to Database called db.
car 50
paper 3
paper 4
dictionary 3
car 30
computer 10
How can I create a NEWLIST from this that will show like this:
car 80
paper 7
dictionary 3
computer 10
var NEWLIST = db.Table ....LINQ
Assuming your entity which represent the table has 2 properties, Name and Amount ( of type Int)
public class Item
{
public string Name { set;get;}
public int Amount { set;get;}
}
And your dbContext class has a property of type DbSet of Item
public class YourDbContext
{
public DbSet<Item> Items { set;get;}
}
You can group by the name and then do a sum on the other property.
var db = new YourDbContext();
var result = db.Items.GroupBy(s => s.Name, p => p, (k, v) =>
new Item { Name = k, Amount = v.Sum(d => d.Amount)}).ToList();
I have a linq query which returns results ordered by first letter. Is there a way to return the first letter before the group ordered by that letter? For instance;
**A**
Acountants
Apothecary
**B**
Basketball
Biscuits
and so on. I tried grouping my results like this;
var companyquery = (from c in db.Categories
group c by c.Name.Substring(0, 1)
into cgroup
select new
{
FirstLetter = cgroup.Key,
Names = cgroup
}).OrderBy(letter => letter.FirstLetter);
return View(companyquery);
but got error:
"The model item passed into the dictionary is of type 'System.Data.Entity.Infrastructure.DbQuery1[<>f__AnonymousType31[System.String]]', but this dictionary requires a model item of type 'System.Collections.Generic.IEnumerable`1[NewAtAClick.Models.Category]'.'
So now I'm using orderby instead of group _ into _ . Here is my query;
var companyquery = (from c in db.Categories
orderby c.Name.Substring(0, 1)
select c);
This returns them in alphebetical order. I tried saying
letter1 = companyquery.ToString().SubString(0,1)
then
return view(letter1 + companyquery.ToList());
But nothing.
Thanks in advance!
The error says it all. Your view expects a model of type IEnumerable<Category> but you passed in something else (in this case because you are creating a new anonymous type via the call to select new { ... }).
Instead you could declare a new type:
public class AlphabeticalMapping<T> {
public char FirstLetter { get; set; }
public List<T> Items { get; set; }
}
And change your query to return:
var companyquery = (from c in db.Categories
group c by c.Name.Substring(0, 1)
into cgroup
select new AlphabeticalMapping<Category>() {
FirstLetter = cgroup.Key,
Items= cgroup.ToList()
}).OrderBy(mapping => mapping.FirstLetter);
And change your view to expect IEnumerable<AlphabeticalMapping<Category>>
What happens if you try this?
var companyquery = (from c in db.Categories
group c by c.Name.Substring(0, 1)
into cgroup
select new,
{
FirstLetter = cgroup.Key,
Names = cgroup
})
.OrderBy(letter => letter.FirstLetter)
.ToDictionary(k => k.FirstLetter, e => e.Names);
I need to join two objects (tables) A and B. For any A there can be zero to many B's. The query needs the return one row per A.
The B's I want to order before the join to be able to select the needed row from B's following a certain condition. Say B has a column Type. If there is a Type 1 then that's the B I need, if not: Type 2 must be selected, etc.
Now I think about it, I am not sure how I would to this even in T-sql.
I think something like this:
SELECT A.*
FROM A LEFT JOIN (
SELECT * FROM B AS B1 WHERE B1.Type = (SELECT TOP 1 B2.Type FROM B AS B2
WHERE B2.JoinID = B1.JoinID
ORDER BY B2.Type )
) AS B ON B.JoinID = A.JoinID
[edit]
With the answer of sgtz I've tried to make it work. If have to make an additional step because the field I want to order by is not present. I add this field in step 1, in step 2 I make a selection of the keys and join everything in step 3, but there I receive an error "The type of one of the expressions in the join clause is incorrect. Type inference failed in the call to 'GroupJoin'." on join "join a in adressen1 on new { b.TopRelatieID..."
var adressen1 = from a in db.Adres
select new
{
RelatieAdres = a,
Sortering = (int)(a.AdresType.Code == codeVestAdres ?
1 : a.AdresType.Code == codePostAdres ?
2 : (100 + (int)a.AdresType.Code.ToCharArray()[0]))
};
var adressen2 = from b in adressen1
group b by new { RelatieID = b.RelatieAdres.RelatieID } into p
let TopAdresType = p.Min(at => at.Sortering)
select new { TopRelatieID = p.Key.RelatieID, TopAdresType };
var q = from k in db.Klants
join b in adressen2 on k.RelatieID equals b.TopRelatieID into b_join
from b in b_join.DefaultIfEmpty()
join a in adressen1 on new { b.TopRelatieID, b.TopAdresType } equals new { a.RelatieAdres.RelatieID, a.Sortering } into a_join
from a in a_join.DefaultIfEmpty()
Here's a worked example. I did it two stages.
[Test]
public void Test333()
{
List<Order> O;
var M = Prepare333Data(out O);
var OTop = from o in O
group o by new {id=o.id, orderid=o.orderid}
into p
let topType = p.Min(tt => tt.type)
select new Order(p.Key.id, p.Key.orderid, topType);
var ljoin = from m in M
join t in OTop on m.id equals t.id into ts
from u in ts.DefaultIfEmpty()
select new {u.id, u.orderid, u.type};
}
public class Manufacturer
{
public Manufacturer(int id, string name)
{
this.id = id;
this.name = name;
}
public int id { get; set; }
public string name { get; set; }
}
public class Order
{
public Order(int id, int orderid, int type)
{
this.orderid = orderid;
this.id = id;
this.type = type;
}
public int orderid { get; set; }
public int id { get; set; }
public int type { get; set; }
}
private List<Manufacturer> Prepare333Data(out List<Order> O)
{
var M = new List<Manufacturer>() {new Manufacturer(1, "Abc"), new Manufacturer(2, "Def")};
O = new List<Order>()
{
new Order(1, 1, 2),
new Order(1, 2, 2),
new Order(1, 2, 3),
new Order(2, 3, 1)
,
new Order(2, 3, 1)
,
new Order(2, 3, 2)
};
return M;
}
response to comments:
your "new {" creates a new anonymous type. Two anonymous types created by difference processes are said to have the same signature if types are declared in the same order and they have the same type definition (i.e. int matches int, not int matches short). I haven't tested this scenario extensively in LINQ.
That's why I worked with real concrete classes, and not anon types within the JOIN portion. There's probably a way to rework it with pure LINQ, but I don't know what that is yet. I'll post you a response if it occurs to me okay.
I'd suggest using concrete classes too for now.
i.e. instead of
*new {*
when doing joins, always use
*new CLASSNAME(){prop1="abc",prop2="123"*
It's a little bit longer, but safer... safer at least until we work out what is going on inside the LINQ internals.
To be meaningful, you should add at least something to query result, not only A.*. Otherwise you'll have a copy of A with some rows possibly duplicated. If I understood the question correctly, this SQL query should work:
SELECT DISTINCT A.*, B.Type
FROM A LEFT JOIN
(SELECT TOP (1) JoinID, Type
FROM B
ORDER BY Type
GROUP BY JoinID, Type
) AS B ON A.JoinID = B.JoinID
Translated to LINQ, it is (UPDATED)
(from a in As
join b in
(from b1 in Bs
orderby b1.Type
group b1 by b1.JoinID into B1
from b11 in B1
group b11 by b11.Type into B11
from b111 in B11
select new { b111.JoinID, b111.Type }).Take(1)
on a.JoinID equals b.JoinID into a_b
from ab in a_b.DefaultIfEmpty()
select new { a_b.JoinID, /*all other a properties*/ a_b.Type }).Distinct()
LINQ may not work 100% correct, but you should grab the idea.
Assume I have generic list L of some type in c#. Then, using linq, call OrderBy() on it, passing in a lambda expression.
If I then re-assign the L, the previous order operation will obviously be lost.
Is there any way I can 'save' the lambda expression I used on the list before i reassigned it, and re-apply it?
Use a Func delegate to store your ordering then pass that to the OrderBy method:
Func<int, int> orderFunc = i => i; // func for ordering
var list = Enumerable.Range(1,10).OrderByDescending(i => i); // 10, 9 ... 1
var newList = list.OrderBy(orderFunc); // 1, 2 ... 10
As another example consider a Person class:
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
}
Now you want to preserve a sort order that sorts by the Name property. In this case the Func operates on a Person type (T) and the TResult will be a string since Name is a string and is what you are sorting by.
Func<Person, string> nameOrder = p => p.Name;
var list = new List<Person>
{
new Person { Id = 1, Name = "ABC" },
new Person { Id = 2, Name = "DEF" },
new Person { Id = 3, Name = "GHI" },
};
// descending order by name
foreach (var p in list.OrderByDescending(nameOrder))
Console.WriteLine(p.Id + ":" + p.Name);
// 3:GHI
// 2:DEF
// 1:ABC
// re-assinging the list
list = new List<Person>
{
new Person { Id = 23, Name = "Foo" },
new Person { Id = 14, Name = "Buzz" },
new Person { Id = 50, Name = "Bar" },
};
// reusing the order function (ascending by name in this case)
foreach (var p in list.OrderBy(nameOrder))
Console.WriteLine(p.Id + ":" + p.Name);
// 50:Bar
// 14:Buzz
// 23:Foo
EDIT: be sure to add ToList() after the OrderBy calls if you need a List<T> since the LINQ methods will return an IEnumerable<T>.
Calling ToList() or ToArray() on your IEnumerable<T> will cause it to be immediately evaluated. You can then assign the resulting list or array to "save" your ordered list.
I've 2 tables with same column's name, for example, both table A and table B has column's name "Test". I want to select column Test from both table A and B to entity class. How can I do this?
It sounds like you want the two entities of TableA and TableB merged into a new object. You can use the .Select() extension method to create a new anonymous type, or into a class that you already have defined.
The requirement here is that you've got to find a common attribute between TableA and TableB. Here I assume you've got something like ID to match them together.
Anonymous Type
var mergedTests = from a in db.TableA
join b in db.TableB on a.CommonID equals b.CommonID
select new
{ TestFromA = a.Test, TestFromB = b.Test }
.ToList();
Existing Class
List<MyCustomTests> mergedTests = from a in db.TableA
join b in db.TableB on a.CommonID equals b.CommonID
select new MyCustomTests
{ TestName= a.Test, ShortName= b.Test }
.ToList();
class Program
{
static void Main(string[] args)
{
var A = new Data[] {
new Data { Test = 1, Relation = 1 },
new Data { Test = 2, Relation = 2 },
new Data { Test = 3, Relation = 3 },
new Data { Test = 4, Relation = 4 },
new Data { Test = 5, Relation = 5 },
};
var B = new Data[] {
new Data { Test = 2, Relation = 2 },
new Data { Test = 3, Relation = 3 },
new Data { Test = 5, Relation = 5 },
};
var res = from a in A
join b in B on a.Relation equals b.Relation
select new { TestA = a.Test, TestB = b.Test };
}
}
class Data
{
public int Test;
public int Relation;
}