LINQ : Nesting this into a single query - c#

I know that there are a brazillion examples of LINQ nested queries here on SO and elsewhere, but it just isn't making sense to me. If someone could explain this like I'm five, I'd be very appreciative. This is all pseudo-obfuscated, so please be patient with this contrived example.
I have a EF model that has these:
public class Car(){
public String Vin {get; private set;}
public string Type {get; private set;}
public List<Option> Options {get; set;}
}
public class Option(){
public string Name { get; set; }
}
and I get the collection of IQueryable<Car> cars from the repository.
Simple enough. The goal is:
List all of the car Types (say "truck", "suv", "minivan" etc)
Under each of these Types, have a sub-list of Option Names that exist on cars of that Type.
Under each Option, list the VIN of each car that has that option and is of that Type.
so the list would look like:
Truck
Trailer Hitch
vin1
vin8
Truck Nuts
vin2
vin3
vin4
Gun Rack
vin1
Folding Rear Seat
vin2
vin3
Minivan
Swivel Seats
vin6
Dvd Player
vin6
vin10
Folding Rear Seat
vin6
vin10
Suv
Folding Rear Seat
vin9
vin5
You probably get the idea. I know that I can group cars by Type, like this:
from c in cars
group by c.Type into g
but I think what I need to do is group Vin into Option and group that result into Type. I also think I might need to
cars.SelectMany(c => c.Options)
.Select(o => o.Name)
.Distinct();
to get a list of unique option Names, but I not sure that a) this is the most efficient way to do this and b) how to incorporate this into my grouping query. I don't really understand how to write a nested grouping query to accomplish this - is the first group the outer group or the inner group?
My understanding of this is below remedial, so please, once again: explain like I'm five.
Thanks to all.

That's surely not a trivial query.
I would do it something like this:
cars.SelectMany(c => c.Options.Select(o => new { Car = c, Option = o.Name }))
.GroupBy(x => x.Car.Type)
.Select(x => new
{
Type = x.Key,
Options = x.GroupBy(y => y.Option, y => y.Car.Vin)
.Select(y => new { Option = y.Key,
Cars = y.ToList() } )
});
This query does the following:
We change the data we work on a little bit to be easier to handle: You want to have the options above the cars. That means that the end result will have each car possibly under multiple options, so what we really need is a list of (Option, Car) tuples.
We achieve this with
cars.SelectMany(c => c.Options.Select(o => new { Car = c, Option = o.Name }))
This basically says: For each car and each option select a new anonymous type with the car and the option name as properties. Let's name that anonymous type Ano1.
The result will be an IEnumerable<Ano1>.
This flat data is now grouped by the car type. This means that for each car type we have a list of Ano1 instances. So we now have a list of groups with each group having a list of Ano1 instances.
On this list of groups we issue a select. For each group (= car type) the select returns a new anonymous type with a property for the car type - so that information is not lost - and a property for the options.
The options from the previous step will eventually be a list of anonymous types with the properties Option and Cars. To get this data, we group all the Ano1 instances for our by the Option and select the VIN of the car as the element inside the group. These groups are now transformed into a new anonymous type with a property for the option name and a property for the cars.
The query is not trivial and so is the explanation. Please ask if something is not clear.

This isnt going to be pretty, and im not sure L2E will handle this, you might need to select the entire list from the database and do this in L2O:
var result = cars.GroupBy(c => c.Type)
.Select(c => new {
Type = c.Key,
Options = c.SelectMany(x => x.Options)
.GroupBy(x => x.Name)
.Select(x => new {
Option = x.Key ,
Vins = c.Where(y => y.Options.Any(z => z.Name == x.Key)).Select(z => z.Vin)
})
});
Live example (Just the trucks modelled, but will work for all): http://rextester.com/OGD12123

This linq-to-entities query will do the trick
var query = from c in cars
group c by c.Type into g
select new {
Type = g.Key,
Options = from o in g.Options.SelectMany(x => x.Options).Distinct()
select new {
o.Name,
Vins = from c in cars
where c.Options.Any(x => x.Name == o.Name)
where c.Type == g.Key
select c.Vin
}
}

Related

Linq or EF - Multiple join into non anonymous object with filtered attributes

I am using EF and unfortunately includefiltered is not option. So I have to rewrite my code somehow and create non anonymous object from it. I decided to rewrite it to join but it can be anything that works.
I have entity, simplified version Car.Tires.Manufacturers.
Car can have zero to many tires, tires can have zero to many manufacturers
I want to get car with specific id and only it's tires with specific manufacturer.
The problem is that my result car's tires always have null manufacturers.
My current code is :
var car1 = (from c in this.dbContext.Cars
.Include(cr => cr.Tires)
.ThenInclude(crt => crt.Manufacturers)
join t in this.dbContext.Tires
.Include(ct => ct.Manufacturers)
on c.ID equals t.CarID into carTires
from t in carTires.DefaultIfEmpty()
join m in this.dbContext.Manufacturers on t.ManufacturerID equals m.ID into completeSet
from cs in completeSet.DefaultIfEmpty()
where (c.ID == someCarID ) // and later I will add filter for tire's manufacturer
select new Car
{
ID = c.ID,
Tires = c.Tires
}
If I use code
var car2 = this.dbContext.Cars
.Include(c => c.Tires)
.ThenInclude(t => t.Manufacturers)
Where(c => c.ID == someCarID)
In Car2 there are some manufacturers.
Why car1 Tire's manufacturers is null and how to fix it?
Note: This is middle goal. My final goal is to obtain car with tires only for selected manufacturer.
Try:
var manufacturerTires = dbContext.Tires.Where(t => t.ManufacturerID == someManufacturerID);
var carTires = dbContext.Cars.
Where(car => car.ID == someCarID)
.Join(manufacturerTires,
car => car.ID,
tire => tire.CarID,
(car, tire) => new { car, tire })
.ToList();
This should return an anonymous object new { Car, Tire }
if we need to get the existing structure of Car and Car.Tires, we could add a GroupBy at the end of the above query like:
.GroupBy(c => c.car, c => c.tire, (car, tires) => new Car{ ID = car.ID, Tires = tires});
//this could be an expensive query as the GroupBy is on all the columns in Car table
Try this :
var Cars=this.dbContext.Cars.Where(c => c.ID == someCarID).Select(s=> s.Tires).ToList();
Now you have tires with there Manufacturers

Conditional GroupBy() in LINQ

I'm working with a matrix filled with similarities between items. I save these as a list of objects in my database. The Similarity object looks like this:
public class Similarity
{
public virtual Guid MatrixId { get; set; } //The id of the matrix the similarity is in
public virtual Guid FirstIndex { get; set; } //The id of the item of the left side of the matrix
public virtual Guid SecondIndex { get; set; } //The id of the item of the top side of the matrix
public virtual double Similarity { get; set; } //The similarity
}
A user can review these items. I want to retrieve a list of items which are 'similar' to the items the user has reviewed. The problem is where I can't tell for sure whether the item's id is in the FirstIndex or the SecondIndex. I have written some code which does what I want, but I want to know if this is possible in 1 statement.
var itemsNotReviewed = Similarities.Where(x => !itemsReviewed.Contains(x.SecondIndex))
.GroupBy(x => x.SecondIndex)
.ToList();
itemsNotReviewed.AddRange(Similarities.Where(x => !itemsReviewed.Contains(x.FirstIndex))
.GroupBy(x => x.FirstIndex)
.ToList());
Where itemsReviewed is a list of guids of the items the user has reviewed and where Similarities is a list of all items which are similar to the items the user has reviewed. I retrieve that list with this function:
return (from Row in _context.SimilarityMatrix
where itemIds.Contains(Row.FirstIndex) || itemIds.Contains(Row.SecondIndex)
select Row)
.Distinct()
.ToList();
where itemIds is a list of guids of the items the user has reviewed.
Is there a way to group by either the first or second index based on the Where clause?
Please let me know if I should elaborate!
By my understanding, you have a list of Similarity which is guaranteed to contain items with either FirstIndex or SecondIndex contained in itemsReviewed list of Guid. And you need to take the elements (if any) with either index not contained in itemsReviewed (it could be only one of them due to the first constraint) and group by that index.
The straightforward LINQ translation of the above would be like this:
var itemsNotReviewed = Similarities
.Where(item => !itemsReviewed.Contains(item.FirstIndex) || !itemsReviewed.Contains(item.SecondIndex))
.GroupBy(item => !itemsReviewed.Contains(item.FirstIndex) ? item.FirstIndex : item.SecondIndex)
.ToList();
But it contains duplicate itemsReviewed.Contains checks, which affect negatively the performance.
So a better variant would be introducing intermediate variable, and the easiest way to do that is query syntax and let clause:
var itemsNotReviewed =
(from item in Similarities
let index = !itemsReviewed.Contains(item.FirstIndex) ? 1 :
!itemsReviewed.Contains(item.SecondIndex) ? 2 : 0
where index != 0
group item by index == 1 ? item.FirstIndex : item.SecondIndex)
.ToList();
I would go for changing the way you source the original list:
_context.SimilarityMatrix.Where(Row => itemIds.Contains(Row.FirstIndex) || itemIds.Contains(Row.SecondIndex))
.Select(r => new { r.MatrixId, r.FirstIndex, r.SecondIndex, r.Similarity, MatchingIndex = itemIds.Contains(r.FirstIndex) ? r.FirstIndex : r.SecondIndex })
.Distinct()
.ToList();
This way you only need to group by Matching Index.
var itemsNotReviewed = Similarities.
.GroupBy(x => x.MatchingIndex)
.ToList();
You may want to convert after the dynamic object to your Similarity class or just change the class to include the Matching Index.
You can convert them to your Similarity type by:
var itemsNotReviewed = Similarities.
.GroupBy(x => x.MatchingIndex)
.Select(g => new { g.Key, Values = g.Values.Select(d => new Similarity { MatrixId = d.MatrixId, FirstIndex = d.FirstIndex, SecondIndex = d.SecondIndex, Similarity = d.Similarity }).ToList() })
.ToList();
What about
(from x in Similarities
let b2 = !itemsReviewed.Contains(x.SecondIndex)
let b1 = !itemsReviewed.Contains(x.FirstIndex)
where b1 || b2
groupby b2 ? x.SecondIndex : x.FirstIndex into grp
select grp)
.ToList()
The let statement introduces a new tempoary variable storing the boolean. You can of course inline the other function, too:
(from x in (from Row in _context.SimilarityMatrix
where itemIds.Contains(Row.FirstIndex) || itemIds.Contains(Row.SecondIndex)
select Row)
.Distinct()
.ToList()
let b2 = !itemsReviewed.Contains(x.SecondIndex)
let b1 = !itemsReviewed.Contains(x.FirstIndex)
where b1 || b2
groupby b2 ? x.SecondIndex : x.FirstIndex into group
select group)
.ToList()
If you wanted to use non-LINQ syntax, you would probably need to introduce some anonymous types:
Similarities
.Select(s => new
{
b2 = !itemsReviewed.Contains(x.SecondIndex),
b1 = !itemsReviewed.Contains(x.FirstIndex),
s
})
.Where(a => a.b1 || a.b2)
.GroupBy(a => a.b2 ? a.s.SecondIndex : a.s.FirstIndex, a => a.x) //edit: to get same semantics, you of course also need the element selector
.ToList()

Group By struct list on multiple columns in C#

I am having a struct as
public struct structMailJob
{
public string ID;
public string MailID;
public int ResendCount;
public int PageCount;
}
and a list as
List<structMailJob> myStructList = new List<structMailJob>();
I have loaded data in myStructList from database and want myStructList data in a new list after grouping by MailID and ResendCount.
I am trying as:
List<structMailJob> newStructList = new List<structMailJob>();
newStructList = myStructList.GroupBy(u => u.MailID, u=>u.ResendCount)
.Select(grp => new { myStructList = grp.ToList() })
.ToList();
but unable to do that as getting error message - cant implicitly convert generic list to structMailJob.
I think that you are looking for is the following:
var newStructList = myStructList.GroupBy(smj => new { smj.MailID, smj.ResendCount })
.Select(grp => new
{
MailID = grp.Key.MailID,
ResendCount = grp.Key.ResendCount
MailJobs = grp.Select(x=>new
{
x.ID,
x.PageCount
}).ToList()
})
.ToList();
Note that we changed the GroupBy clause to the following one:
GroupBy(smj => new { smj.MailID, smj.ResendCount })
Doing so, the key on which the groups would be created would be consisted of both MailID and ResendCount. By the way the former GroupBy clause isn't correct.
Then having done the grouping, we project each group to an object with three properties, MailID and ResendCout, which are the components of the key and list of anonymous type object with two properties, ID and PageCount, which we gave it the name MailJobs.
Last but not least you will notice that I didn't mention the following
List<structMailJob> newStructList = new List<structMailJob>();
I just used the var and declared the newStructList. I don't think that you stated in your post makes sense. How do we expect to get a list of the same objects after grouping them? So I assumed that you might want is the above.
However, I thought you might want also something like this and you didn't want to refer to Grouping.
myStructList = myStructList.OrderBy(smj => smj.MailID)
.ThenBy(smj => smj.ResendCount)
.ToList();
Linq Query is completely incorrect, following are the important points:
myStructList.GroupBy(u => u.MailID, u=>u.ResendCount) // Incorrect grouping
myStructList.GroupBy(u => new {u.MailID, u.ResendCount }) // Correct grouping, which will do by two columns MailID and ResendCount, last one was only doing by MailID and was using ResendCount for result projection
Now the result is of type IEnumerable<IGrouping<AnonymousType,structMailJob>>, so when you do something like Select, it will end up creating Concatenated List of type IEnumerable<List<structMailJob>> (Removed the assignment to myStructList inside the Select, as that was not correct):
.Select(grp => grp.ToList())
Correct code would require you to flatten using SelectMany as follows:
newStructList = myStructList.GroupBy(u => new {u.MailID, u.ResendCount})
.SelectMany(grp => grp.ToList()).ToList();
Assign it to newStructList, but this code has little use, since literally newStructList is exactly same as myStructList post flattening, ideally you shall be able to use the grouping, so that you can get a subset and thus the correct result, however that depends on your business logic
I don't know if I got your question right but it seems to me you missed the 'Group by' signature.
List<structMailJob> myStructList = new List<structMailJob>();
List<structMailJob> newStructList = new List<structMailJob>();
newStructList = myStructList
// .GroupBy(/*Key Selector */u => u.MailID, /*Element Selector*/u=>u.ResendCount)
.GroupBy(u => new { u.MailID, u.ResendCount }) // broup by MailID, ResendCount
// Note no Element Selector , the 'STRUCT' is 'SELECTED'
.Select(grp => {
// NOte: Key == Anonymous {MailID, ResendCount }
return grp;
})
// otherwise you get a IEnumerable<IEnumerable<T>> instead of IEnumerable<T> because you grouped it
.SelectMany(x=>x)
.ToList();
If Mrinal Kamboj's answer is what you are looking for, then you could use the following as an alternative:
var orderedList = myStructList.OrderBy(x => x.MailID).ThenBy(x => x.ResendCount);

How to get all Objects from a List in a List with LINQ

I have a List of Cars and they have a List in them with their Production Factories.
List<Car> cars = new List();
Now I have in this list the Factorys where each car is in Production.
List<FactoryCollection> factoryCollections = cars
.Select(c => c.Factories)
.ToList();
and now i want to get a List with the single Factories so that i have all Factories where the Specific Cars are in Production.
For Example:
List<Factory> factories = factoryCollections
.Select(f => f.Werk);
My Problem is that the Objects are one level deeper.
My Question is:
How to get the Objects with a LINQ statement or is there a more easy way to get on all the Factories ?
Use SelectMany instead of Select
List<Factory> factories = Car.Select(x => x.Factories).SelectMany(y => y.Werk).ToList();
It seems, that you're looking for SelectMany:
List<Car> Car = new List<Car>() {... }
...
var factories = Car
.Where(car => IsCarSpecific(car))
.SelectMany(car => car.Factories
.Select(factory => factory.Werk));
// .Distinct(); // in case you want distinct factories
// .ToList(); if you want materiazlize into List<Factory>
You're looking for SelectMany, but as opposed to other answers somewhat nested:
List<Factory> factories = cars
.SelectMany(c => c.Factories.Select(f => f.Werk))
.ToList();

LINQ select distinct items and subitems

I am trying to learn advanced LINQ techniques, so how we could achieve, if possible with LINQ only, to select distinct items of a collection and merge their sub-items in a dictionary/struct or dynamic ExpandoObject?
Lets say i have these two class:
public class TestItem
{
public int Id;
public string Name;
public List<TestSubItem> SubItems;
}
public class TestSubItem
{
public string Name;
}
How can i create a single LINQ query(again, if possible) to select all distinct TestItem based on the Name property and if the same TestItem is found two time with the same name to merge the two List in the final result?
I know i could select distinct TestItem by doing the below code, but i'm stuck there:
var result = items.GroupBy(item => item.Name)
.ToList();
Thanks in advance!
A combination of a Select and an Aggregate on the groupings should get the job done. Finally, a ToDictionary call cleans everything up and gets rid of the potentially invalid ID field:
var result = items.GroupBy(item => item.Name)
.Select(g => g.Aggregate((i, j) =>
{
i.SubItems.AddRange(j.SubItems);
return i;
}))
.ToDictionary(k => k.Name, v => v.SubItems);
Alternatively, the query syntax is a bit more verbose, but I find it easier to read:
var result = (from item in items
group item by item.Name
into g
let n =
from ti in g
select ti.Name
let i =
from ti in g
from si in ti.SubItems
select si
select new { n, i }).ToDictionary(k => k.n, v => v.i);

Categories

Resources