I have a Tag entity in EF6 that has a one-to-many relationship to 3 other entities:
Tag
/ | \
Definition Variant Machine
Tag
{
Id : 1,
Name : New York,
Definition
{
Id : 1,
Title: EquipA
},
Definition
{
Id : 2,
Title: EquipB
},
Variant
{
Id : 1,
Name: EquipA11
},
Variant
{
Id : 2,
Name: EquipB11
},
Machine
{
Id : 1,
Plant : New York,
Line : 1A
}
Machine
{
Id : 2,
Plant : New York,
Line : 2B
}
}
I want to return the flattened results for all 4 entities so I get results like this:
Tag.Id, Tag.Name, Tag.Definition.Id, Tag.Definition.Title, Tag.Variant.Id, Tag.Variant.Name, Tag.Machine.Id, Tag.Machine.Plant, Tag.Machine.Line
1, New York, 1, EquipA, 1, EquipA11, 1, New York, 1A
1, New York, 1, EquipA, 1, EquipA11, 2, New York, 2B
1, New York, 1, EquipA, 2, EquipB11, 1, New York, 1A
1, New York, 1, EquipA, 2, EquipB11, 2, New York, 2B
1, New York, 2, EquipB, 1, EquipA11, 1, New York, 1A
1, New York, 2, EquipB, 1, EquipA11, 2, New York, 2B
1, New York, 2, EquipB, 2, EquipB11, 1, New York, 1A
1, New York, 2, EquipB, 2, EquipB11, 2, New York, 2B
I am able to do this, but I can only get the Definitions, not sure how to select from all 4 entities:
var temp = db.Tags.Include(c => c.Definition)
.Include(v => v.Variant)
.Include(p => p.PaperMachine)
.SelectMany(t => t.Definition)
.Select(t => new { t.Id, t.Title } )
//.SelectMany(c => c.Definition, v => v.Variant, )
//.SelectMany(v => v.)
.ToList();
It sounds like you want to produce a Cartesian across all of the associated entities.
Something like this should net the results you are looking for:
var temp = db.Tags
.SelectMany(t => t.Definitions
.SelectMany(d => d.Tag.Variants
.SelectMany(v => v.Tag.PaperMachines
.Select(p => new
{
TagId = t.Id,
TagName = t.Name,
DefinitionId = d.Id,
DefinitionName = d.Name,
VariantId = v.Id,
VariantName = v.Name,
PaperMachineId = p.Id,
PaperMachineName = p.Name
})))).ToList();
This populates an anonymous type with the requested details. You can define a DTO/ViewModel to populate and return if this needs to go back to a caller/view. This requires bi-directional navigation properties to get from the tag to each child and back to the tag. Alternatively you could use Join though I suspect that will be a bit messier to read.
There may be a more succinct way to get it. Normally I'm helping people avoid Cartesians, not purposefully making them. :)
Edit: For a many-to-many relationship you can use the above query without the reverse navigation property...
var temp = db.Tags
.SelectMany(t => t.Definitions
.SelectMany(d => t.Variants
.SelectMany(v => t.PaperMachines
.Select(p => new
{
TagId = t.Id,
TagName = t.Name,
DefinitionId = d.Id,
DefinitionName = d.Name,
VariantId = v.Id,
VariantName = v.Name,
PaperMachineId = p.Id,
PaperMachineName = p.Name
})))).ToList();
This looks a bit weird, but does appear to do the trick. Note that for the SelectMany joins we join back in on the t. references, but this allows us to still join across combinations of our (t)ag, (d)efinition, (v)ariant, and (p)aperMachine reference in the final Select.
Be cautious because this will get exponentially bigger and more expensive with the more combinations you introduce.
Related
Take the following example code:
public record ProductGroups(int size, List<Product> products);
public record Product(int size, int Id, string style);
queryResults = new List<Product>()
{
new Product(1, 1, "short sleeve"),
new Product(1, 2, "long sleeve"),
new Product(1, 3, "hoodie"),
new Product(2, 4, "med short sleeve"),
new Product(2, 5, "med long sleeve")
}
/* Want to turn queryResults into a list<ProductGroups> object that looks like this:
[
{
size: 1,
products: [{1, 1, "short sleeve"}, {1, 2, "long sleeve"} {1, 3, "hoodie}]
},
{
size: 2,
products: [{2, 4, "med short sleeve"}, {2, 5, "med long sleeve"}]
}
]
*/
I've tried multiple variations with GroupBy but didn't have any success achieving the desired format:
var productGroups = queryResults.GroupBy(x => x.size).ToList(); returns a List<IGrouping<int, Product>> which isn't quite what I want.
You could just group by Size and assign Products to the list of items in the group. The following returns an anonymous object:
var result = queryResults.GroupBy(r => r.size)
.Select(g => new { Size = g.Key, Products = g.ToList() });
If you need a concrete class/record ProductGroup then the query is very similar to the above:
var result = queryResults.GroupBy(r => r.size)
.Select(g => new ProductGroup(g.Key, g.ToList()));
public record ProductGroup(int Size, List<Product> Products);
But it also looks like your datatype matches Dictionary<int, List<Product>>. So you could use ToDictionary after grouping:
var result = queryResults.GroupBy(r => r.size)
.ToDictionary(r => r.Key, r => r.ToList());
List<empl> lstSource = new List<empl>();
lstSource.Add(new empl { departmentId = 2, Id = 101, Name = "S1" });
lstSource.Add(new empl { departmentId = 2, Id = 109, Name = "S9" });
lstSource.Add(new empl { departmentId = 2, Id = 102, Name = "S2" });
lstSource.Add(new empl { departmentId = 4, Id = 101, Name = "S1" });
lstSource.Add(new empl { departmentId = 4, Id = 102, Name = "S2" });
lstSource.Add(new empl { departmentId = 4, Id = 108, Name = "S8" });
lstSource.Add(new empl { departmentId = 3, Id = 105, Name = "S5" });
lstSource.Add(new empl { departmentId = 3, Id = 103, Name = "S3" });
lstSource.Add(new empl { departmentId = 3, Id = 102, Name = "S2" });
should result {Id = 102, Name = "S2"}
if I add
lstSource.Add(new empl { departmentId = 3, Id = 101, Name = "S1" });
should result {Id = 102, Name = "S2"} {Id = 101, Name = "S1"}
Hint : we can group with departmentId and find common Id in 3 group.
Based on your comments and example above, I take it that the Name associated with any given Id is always the same. In that case, you could split the Ids registered on each department into separate lists, then intersect those lists to find the common Ids, and then find the associated Name for each common Id.
You have done something similar in your own example. By rewriting the code (e.g. by replacing the foreach loop with an Aggregate() function) you could achieve a more straight forward approach:
var idsPerDepartment = lstSource
.GroupBy(item => item.departmentId)
.Select(gr => gr.Select(item => item.Id));
var commonIds = idsPerDepartment
.Aggregate((common, next) => common.Intersect(next));
var commonItems = commonIds
.Select(id => lstSource.First(item => item.Id == id))
.Select(commonItem => new { commonItem.Id, commonItem.Name })
.OrderByDescending(commonItem => commonItem.Name);
commonItems is empty (not null) if there are no common items.
It could all be written as one single expression, but I've spilt it into several variables to clarify what is happening along the way.
var groups = lstSource.GroupBy(t1=> t1.departmentId)
.ToList();
var validIds = groups.First().Select(t1 => t1.Id).ToList();
foreach (var g in groups.Skip(0))
{
var otherGroupItemIds = g.Select(t1 => t1.Id).ToList();
validIds = validIds.Intersect(otherGroupItemIds).ToList();
}
if (validSRIds.Count > 0)
return lstSource.FindAll(t1 => validSRIds.Contains(t1.Id)).GroupBy(t2 => new { t2.Id, t2.Name }).Select(g => g.First()).OrderByDescending(t => t.Name).ToList();
you will get all common id,name which belongs to all group
I am using ASP.NET MVC and have a many-to-many table that as follows:
custID | objID
================
1 2
1 3
2 5
2 2
3 2
Both userID and objID are Foreign Keys linking to other tables. What I would like to do is get the highest count based on objID. The table above will yield the following results:
objID | objName | Price
=======================
2 | Chicken | 10
The custID does not matter in this scenario as I just want to get the objID with the highest count.
I've tried the following but i'm stuck here:
//retrieve many-to-many table
var retrieved = db.Customer.Include(c => c.Objects)
var topID = retrieved.GroupBy(q => q.objID)
.OrderByDescending(g => g.Count())
Should do the trick.
var topID = retrieved.GroupBy(q => q.objID)
.Select(g => new { Id = g.Key, Total = g.Count() })
.OrderByDescending(g => g.Total).First().Id;
List<int> lst = new List<int>() { 1, 3, 4, 5, 1, 2, 3, 4, 5, 2, 3, 2 };
var count = lst.GroupBy(q => q).OrderByDescending(s => s.Count())
.First().Key;
var lstKeyWithCount lst.GroupBy(i => i).Select(g => new { Key=g.Key,Count=g.Count() });
in lstKeyWithCount variable you get key with count.
in count variable you get most times repeated value.
I have 2 tables and i want to match up 2 Id values.
First table
Id - 1, 2, 3, 4, 5
DepartmentId - 2, 4, 5, 2, 1
Second table
Id- 1, 2, 10, 30, 40
I want to match up first table's Id's with second table's Id's so i can get DepartmentId values.
I need to get this virtual result:
Id- 1, 2, 10, 30, 40
DepartmentId -2, 4, null, null, null
Here is my code:
for (int i = 0; i < model1.Count(); i++)
{
model1[i].DepartmentId= model2.FirstOrDefault(k => k.Id== model1[i].Id).DepartmentId;
}
I get this error:
An exception of type 'System.NullReferenceException' occurred in
IYP.UserInterfaceLayer.dll but was not handled in user code
I think loop fails because of it can't find 10, 30, 40 Id values. If my Id values are same in 2 tables( Id = 1,2,3,4,5) loop works.
How can i do this with Linq?
You are basically looking for Left Join in LINQ. Try this:-
var query = from emp2 in Employee2
join emp1 in Employee1
on emp2.Id equals emp1.Id into allEmployees
from result in allEmployees.DefaultIfEmpty()
select new
{
ID = emp2.Id,
DeptID = result == null ? "No Department" : result.DepartmentId.ToString()
};
Where I have used following types:-
var Employee1 = new[]
{
new { Id = 1, DepartmentId = 2 },
new { Id = 2, DepartmentId = 4 },
new { Id = 3, DepartmentId = 5 },
new { Id = 4, DepartmentId = 2 },
new { Id = 5, DepartmentId = 1 },
};
var Employee2 = new[]
{
new { Id = 1 },
new { Id = 2 },
new { Id = 10 },
new { Id = 30 },
new { Id = 40 },
};
Complete Working Fiddle.
You should use the Join LINQ extension method. In the form of query syntax (which I believe is more readable for this case) it will look like:
var matchedValues =
from second in model2
join first in model1
on second.Id equals first.Id
into temp
from tempFirst in temp.DefaultIfEmpty()
select
new
{
second.Id,
DepartmentId = tempFirst == null ? null : tempFirst.DepartmentId
};
You join on the Id property and for any value you don't find in the model1, you use a default (DefaultIfEmpty call). Then you choose the resulting DepartmentId based on the join result.
try this
List<long> idlist=model2.tolist().select(t=>t.Id);
List<long> depIdList=model1.where(t=>idlist.contains(t.id)).toList();
I am going to assume that model1 and model2 are both IEnumerable. In that case the following should work.
var result = from x in model2
select
new Model1Type {DepartamentId = x,
Value=
model1.FirstOrDefault(y=>y.DepartamentId==x)
.Select(y=>y.Value)};
This is called Lamq :D
Hope this helps :)
Im going to try to describe this the best way I can. The best way I can think of it is to give you a dataset.
Animals
ID Type
1 Lions
2 Tigers
3 Bears
AnimalDetails
ID AnimalId Height Weight
1 1 62 inches 200lbs
2 1 56 inches 150lbs
3 1 23 inches 125lbs
4 2 47 inches 500lbs
5 2 88 inches 150lbs
6 2 15 inches 125lbs
If it helps, pretend like these tables are already joined
Maybe there is a FK to some other table that holds detailed data for each of these types of Animal; height, width, age, etc.
I want to group by Animal type (lion, etc) and select that but also select the details for the lion.
So I want the Key to be Lion then maybe a collection of lion information.
Does that make sense?
My attempt obviously wouldnt work but here it is anyway:
var animals = (from a in Animals
group a by new { AnimalType = a.Type }
into grouped
select grouped);
UPDATE
Added a psuedo table structure. Im not looking for the 1 answer to this as this is obviously fake data, just looking for direction on how to achieve this.
I would read this SO article: Linq with Left Join on SubQuery containing Count
Do your normal group by on Animals and then join on the AnimalId to the details table to get the detail attributes just once.
EDIT:
var query = (from d in details
join a in
(from animal in animals
group animal by animal.Name into g
select new { Name = g.Key }) on d.Name equals a.Name
select new { a.Name, d.Height, d.Weight }).ToList();
The query above assumes the pseudo data tables you have are not joined. If they are already joined, then I don't understand why you would want to group by animal name and then pickup details since the details in your example occur more than once per animal. (1 lion has multiple details records).
you lose id value in Animals using group by. if losing id value , you cannot use foreinKey between Animals and AnimalsDetail. So you not using group by With this code you can get the animalDetails.
var listAnimals = new List<Animals>
{
new Animals {Id = 1, Name = "Lions"},
new Animals {Id = 2, Name = "Tigers"},
new Animals {Id = 3, Name = "Bears"}
};
var listAnimalDetails = new List<AnimalDetails>
{
new AnimalDetails {Id = 1, AnimalId = 1, Height = 62, Weight = 200},
new AnimalDetails {Id = 2, AnimalId = 1, Height = 56, Weight = 150},
new AnimalDetails {Id = 3, AnimalId = 1, Height = 23, Weight = 125},
new AnimalDetails {Id = 4, AnimalId = 2, Height = 47, Weight = 500},
new AnimalDetails {Id = 5, AnimalId = 2, Height = 88, Weight = 150},
new AnimalDetails {Id = 6, AnimalId = 2, Height = 15, Weight = 125}
};
var join = (from anm in listAnimals
join anmD in listAnimalDetails
on anm.Id equals anmD.AnimalId
select new
{
Animal = anm.Name,
H = anmD.Height,
W = anmD.Weight
}).ToList();
after this you can using group by on the join. I hope you will help.