Get ICollection out from IQueryable<ICollection> LINQ Query - c#

I'm trying to write a query that grabs a list of countries out from my joined data.
Places is List<Country>.
var zonedCountries = (from dz in db.DeliveryZones.Include(d => d.Places)
where model.DeliveryZones.Contains(dz.ID)
select dz.Places);
I would expect zonedCountries to be a List but instead it is a IQueryable<ICollection<Country>>.
How do I extract the list from this?

If you want to get flattened list of countries:
var zonedCountries = (from dz in db.DeliveryZones.Include(d => d.Places)
where model.DeliveryZones.Contains(dz.ID)
from p in dz.Places
select p);
Or use SelectMany:
var zonedCountries = db.DeliveryZones.Include(d => d.Places)
.Where(dz => model.DeliveryZones.Contains(dz.ID))
.SelectMany(dz => dz.Places);
BTW I'm not sure if you need to include places manually in this case (thus you are selecting places instead of delivery zones). And you will probably want to select distinct countries only - Distinct() will help you here. Also if you want to store results in list, then simple ToList() call will do the job.

Related

Select all distinct values of variable in object array using lambda expression

I have an array of objects with the property of ProductId. I would like to use a lambda expression to select all the distinct values of ProductId that are within my object array products.
Here I get the products
var products = Database.SqlQuery<StructuredProduct>("query").ToArray();
And I can group by distinct values of ProductId, but it still returns an array of objects, rather than an array of ProductIds
var productIds= products.GroupBy(p => p.ProductId).Select(group => group.First()).ToArray();
Any idea on how to use a Lambda Expression on the products array to get all distinct values of ProductIds?
var productIds= products.Select(p => p.ProductId).Distict();
But it may be even better to do this directly on the database, as part of the "query" sql command.
With LINQ method .Distinct()
var productIds = products.Select(p => p.ProductId).Distinct();
I've only ever done GroupBy operations with the query comprehension syntax. If you do that, group / by / into, the thing you group into has a property named key. That would contain your 'productid`
var results = from product in products
group product by ProductId
into individualProducts
select individualProducts;
var productsArray = individualProducts.Select(p => p.Key).ToArray();
The items individualProducts collection each have a Key and a collection of things that have the same productid.
If you do this directly out of the database, all your operations will get nicely combined into a single SQL statement that will get executed when your code gets to .ToArray()

How can i split and get distinct words in a list?

My sample data coloumn, which come from an CSV file is
|----Category------------|
SHOES
SHOES~SHOCKS
SHOES~SHOCKS~ULTRA SOCKS
I would love to split the specific column and get the distinct values in a list like
SHOES
SHOCKS
ULTRA SOCKS
I tried the following, but it does not work as expected.
var test = from c in products select c.Category.Split('~').Distinct().ToList();
It actually returns the following.
Any thoughts please? Thank you.
I would use SelectMany to "flatten" the list before removing duplicates:
products.SelectMany(c => c.Category.Split('~'))
.Distinct()
You can use SelectMany to flatten the collection:
products.SelectMany(p => p.Category.Split('~')).Distinct().ToList();
You were close, you just needed to flatten out your collection to pull the individual items of each grouping via a SelectMany() call :
// The SelectMany will map the results of each of your Split() calls
// into a single collection (instead of multiple)
var test = products.SelectMany(p => p.Category.Split('~'))
.Distinct()
.ToList();
You can see a complete working example demonstrated here and seen below :
// Example input
var input = new string[] { "SHOES","SHOES~SHOCKS","SHOES~SHOCKS~ULTRA SOCKS" };
// Get your results (yields ["SHOES","SHOCKS","ULTRA SOCKS"])
var output = input.SelectMany(p => p.Split('~'))
.Distinct()
.ToList();
Merge this list of list of strings into a single list by using SelectMany() and Just add another Distinct to your List..
var test = from c in products select c.Category.Split('~').Distinct().ToList().SelectMany(x => x).Distinct().ToList();
Here's how you'd do it in query syntax.
var test = (from p in products
from item in p.Category.Split('~')
select item).Distinct().ToList();

LINQ Intersect on inner collection

I have a list of Stores (of type ObservableCollection<Store>) and the Store object has a property called Features ( of type List<Feature> ). and the Feature object has a Name property (of type string).
To recap, a list of Stores that has a list of Features
I have a second collection of DesiredFeatures (of type List<string> ).
I need to use LINQ to give me results of only the stores that have all the DesiredFeatures. So far, I've only been able to come up with a query that gives me an OR result instead of AND.
Here's what that looks like:
var q = Stores.Where(s=> s.Features.Any(f=> DesiredFeatures.Contains(f.name)));
I know Intersect can help, and here's how I've used it:
var q = Stores.Where(s => s.Features.Intersect<Feature>(DesiredFeatures));
This is where I'm stuck, Intersect wants a Feature object, what I need to intersect is on the Feature.Name.
The goal is to end up with an ObservableCollection where each Store has all of the DesiredFeatures.
Thank you!
You've almost done what you need. A small refine would be to swap DesiredFeatures and s.Features.
var q = Stores.Where(s => DesiredFeatures.All(df => s.Features.Contains(df)));
It means take only those stores where desired features are all contained in features of the store.
I need to use LINQ to give me results of only the stores that have all the DesiredFeatures.
In other words, each desired feature must have a matching store feature.
I don't see how Intersect can help in this case. The direct translation of the above criteria to LINQ is like this:
var q = Stores.Where(s =>
DesiredFeatures.All(df => s.Features.Any(f => f.Name == df))
);
A more efficient way could be to use a GroupJoin for performing the match:
var q = Stores.Where(s =>
DesiredFeatures.GroupJoin(s.Features,
df => df, sf => sf.Name, (df, sf) => sf.Any()
).All(match => match)
);
or Except to check for unmatched items:
var q = Stores.Where(s =>
!DesiredFeatures.Except(s.Features.Select(sf => sf.Name)).Any()
);
Going on your intersect idea, the only way I thought of making this work was by using Select to get the Store.Features (List<Feature>) as a list of Feature Names (List<string>) and intersect that with DesiredFeatures.
Updated Answer:
var q = Stores.Where(s => s.Features.Select(f => f.Name).Intersect(DesiredFeatures).Any());
or
var q = Stores.Where(s => DesiredFeatures.Intersect(s.Features.Select(f => f.Name)).Any());
Old Answer (if DesiredFeatures is a List<Feature>):
var q = Stores.Where(s => s.Features.Select(f => f.Name).Intersect(DesiredFeatures.Select(df => df.Name)).Any());
Two things you want your code to perform.
var q = Stores.Where(s=> s.Features.All(f=> DesiredFeatures.Contains(f.name)) &&
s.Features.Count() == DesiredFeatures.Count()); // Incude Distinct in the comparison if Features list is not unique
Ensure that every Feature is DesiredFeature
Store contains all Desired features.
Code above assumes uniqueness in Features collection as well as DesiredFeatures, modify code as stated in comment line if this is not right

Add single item to linq to sql query

I would like to add a single item to the results of a linq query. I know it's not possible to join a local source and a SQL source. So, is it possible to construct a query to do the same as this?
SELECT ID FROM Types
UNION
SELECT 1
The best I've come up with is this:
List<int> OrgList = DBContext.Types.Select(b => b.ID).ToList();
OrgList.Add(1);
but I'd rather add the item beforehand and still have an IQueryable. Or is there a good reason to not do it this way?
You must get the data from the db and then add the new item like you have done in your code.
The only way to have an IQueryable would be to deffer the adding of the new item to the point were the query is resolved.
You can use Union:
var query = DBContext.Types.Select(b => b.ID).ToList().Union(new[]{1});
Not tested but it should work
Try Concat
var result = DBContext.Types.Select(p => p.ID)
.Concat(new List<int>() { 1 }).ToList();

Identify items in one list not in another of a different type

I need to identify items from one list that are not present in another list. The two lists are of different entities (ToDo and WorkshopItem). I consider a workshop item to be in the todo list if the Name is matched in any of the todo list items.
The following does what I'm after but find it awkward and hard to understand each time I revisit it. I use NHibernate QueryOver syntax to get the two lists and then a LINQ statement to filter down to just the Workshop items that meet the requirement (DateDue is in the next two weeks and the Name is not present in the list of ToDo items.
var allTodos = Session.QueryOver<ToDo>().List();
var twoWeeksTime = DateTime.Now.AddDays(14);
var workshopItemsDueSoon = Session.QueryOver<WorkshopItem>()
.Where(w => w.DateDue <= twoWeeksTime).List();
var matches = from wsi in workshopItemsDueSoon
where !(from todo in allTodos
select todo.TaskName)
.Contains(wsi.Name)
select wsi;
Ideally I'd like to have just one NHibernate query that returns a list of WorkshopItems that match my requirement.
I think I've managed to put together a Linq version of the answer put forward by #CSL and will mark that as the accepted answer as it put me in the direction of the following.
var twoWeeksTime = DateTime.Now.AddDays(14);
var subquery = NHibernate.Criterion.QueryOver.Of<ToDo>().Select(t => t.TaskName);
var matchingItems = Session.QueryOver<WorkshopItem>()
.Where(w => w.DateDue <= twoWeeksTime &&
w.IsWorkshopItemInProgress == true)
.WithSubquery.WhereProperty(x => x.Name).NotIn(subquery)
.Future<WorkshopItem>();
It returns the results I'm expecting and doesn't rely on magic strings. I'm hesitant because I don't fully understand the WithSubquery (and whether inlining it would be a good thing). It seems to equate to
WHERE WorkshopItem.Name IS NOT IN (subquery)
Also I don't understand the Future instead of List. If anyone would shed some light on those that would help.
I am not 100% sure how to achieve what you need using LINQ so to give you an option I am just putting up an alternative solution using nHibernate Criteria (this will execute in one database hit):
// Create a query
ICriteria query = Session.CreateCriteria<WorkShopItem>("wsi");
// Restrict to items due within the next 14 days
query.Add(Restrictions.Le("DateDue", DateTime.Now.AddDays(14));
// Return all TaskNames from Todo's
DetachedCriteria allTodos = DetachedCriteria.For(typeof(Todo)).SetProjection(Projections.Property("TaskName"));
// Filter Work Shop Items for any that do not have a To-do item
query.Add(SubQueries.PropertyNotIn("Name", allTodos);
// Return results
var matchingItems = query.Future<WorkShopItem>().ToList()
I'd recommend
var workshopItemsDueSoon = Session.QueryOver<WorkshopItem>()
.Where(w => w.DateDue <= twoWeeksTime)
var allTodos = Session.QueryOver<ToDo>();
Instead of
var allTodos = Session.QueryOver<ToDo>().List();
var workshopItemsDueSoon = Session.QueryOver<WorkshopItem>()
.Where(w => w.DateDue <= twoWeeksTime).List();
So that the collection isn't iterated until you need it to be.
I've found that it's helpfull to use linq extension methods to make subqueries more readable and less awkward.
For example:
var matches = from wsi in workshopItemsDueSoon
where !allTodos.Select(it=>it.TaskName).Contains(wsi.Name)
select wsi
Personally, since the query is fairly simple, I'd prefer to do it like so:
var matches = workshopItemsDueSoon.Where(wsi => !allTodos.Select(it => it.TaskName).Contains(wsi.Name))
The latter seems less verbose to me.

Categories