How can I condense the following Linq query into one IQueryable - c#

I have an EntityFramework model that has User entity that has an EntityCollection of Organisations.
For a particular User I am trying to write a Linq Query to return the names of the organisations that the user belongs where that query will hit the db only once.
My problem is that I cannot see how to write this query without having to materialise the user first then query the users organisation collection.
I would like to try and write one query that hits the db once.
What I have so far:
var orgNames = context.Users
.Where(u => u.LoweredUserName == userName.ToLower())
//materialises user
.FirstOrDefault()
.Organisations
//second hit to the db
.Select(o => o.Name);
What I was psuedo aiming for but cannot see the wood for the trees:
orgNames = context.Users
.Where(u => u.LoweredUserName == userName.ToLower())
//don't materialise but leave as IQueryable
.Take(1)
//The problem: turn what the query sees as possibly multiple
// (due to the Take method) EntityCollection<Organisation> into a List<String>
.Select(u => u.Organisations.Select(o => o.Name));
I have looked at aggregates but I seem to be going in circles :)

Doh! I think I can answer my own question by using SelectMany to condense the Collection of Collections into one collection like so:
orgNames = context.Users.Where(u => u.LoweredUserName == userName.ToLower())
.Take(1)
.SelectMany(u => u.Organisations)
.Select(o => o.Name);

I'm assuming that Lowered User name is unique, otherwise the query would be fairly meaningless, so you can just use.
context.Users
.Where(u => u.LoweredUserName == userName.ToLower())
.Select(u => u.Organisations.Select(o => o.Name));

Related

How can I order in Linq Select Distinct if ordered by an Included entity?

I know that in Linq I have to do the OrderBy after doing a Select - Distinct, but I'm trying to order by an Included entity property that get lost after the Select.
For example:
var accounts = _context.AccountUser
.Include(o => o.Account)
.Where(o => o.UserId == userId || o.Account.OwnerId == userId)
.OrderByDescending(o => o.LastAccessed)
.Select(o => o.Account)
.Distinct();
As I'm doing the Where by an or of two different parameters, there is a good chance to obtain duplicated results. That's why I'm using the Distinct.
The problem here is that after I do the Select, I don't have the LastAccessed property anymore because it doesn't belong to the selected entity.
I thing the structure of the AccountUser and Account can be inferred from the query itself.
If you have the bi-directional navigation properties set up:
var accountsQuery = _context.AccountUser
.Where(o => o.UserId == userId || o.Account.OwnerId == userId)
.Select(o => o.Account)
.Distinct()
.OrderByDescending(a => a.AccountUser.LastAccessed);
When Selecting the Account you do not need .Include() Keep in mind that any related entities that you access off the Account will be lazy-loaded. I recommend using a .Select() to extract either a flattened view model or a view model hierarchy so that the SQL loads all needed fields rather than either eager-loading everything or tripping lazy-load calls.
Since LINQ doesn't implement DistinctBy and LINQ to SQL doesn't implement Distinct that takes an IEqualityComparer, you must substiture GroupBy+Select instead:
var accounts = _context.AccountUser
.Include(o => o.Account)
.Where(o => o.UserId == userId || o.Account.OwnerId == userId)
.GroupBy(o => o.Account).Select(og => og.First())
.OrderByDescending(o => o.LastAccessed)
.Select(o => o.Account);

How to use a where clause on a sub navigation property using linq and EF

My model has a navigation property and this navigation property has another sub navigation property. I need to use a where clause on sub navigation property to filter results.
I'm trying to use linq query but unable to get the results
_context.Set<Job>().Include(x=>x.Premises).ThenInclude(y=>y.Station.Where(s=>s.)
The following sql join gives me desired results
select *
from [dbo].[JOB] J inner join
[dbo].[PREMISES] P on J.PremisesId = P.Id inner join
[dbo].[STATION] S on P.StationCode=S.StationCode
where S.StationCode = '001'
Any ideas?
Notice these similar LINQ statements:
var jobs = db.Jobs
.Include(j => j.Premises)
.Include(j => j.Premises.Select(p => p.Stations))
.ToList();
var stations = db.Stations
.Include(s => s.Premise)
.Include(s => s.Premise.Job)
.ToList();
While your return type is different, you are essentially holding the same data in memory. I could use the second to get all jobs too:
var jobs_from_stations = stations.Select(s => s.Premise.Job).Distinct();
Both jobs_from_stations and jobs will contain the exact same data.
There is a difference in filtering though.
If you were to add a Where() clause in this query, it would work differently.
The first query would filter in scope of the Job entity, whereas the second would filter in scope of the Station entity.
Since you are currently trying to filter based on a station property, that suggests using the second query:
var stations = db.Stations
.Include(s => s.Premise)
.Include(s => s.Premise.Job)
.Where(s => s.StationCode == "001")
.ToList();
If you want the return type to be a list of jobs:
var jobs = db.Stations
.Include(s => s.Premise)
.Include(s => s.Premise.Job)
.Where(s => s.StationCode == "001")
.Select(s => s.Premise.Job)
.Distinct()
.ToList();
Note that it would still be possible to use the first query instead, but it becomes more verbose and unwieldy:
var jobs = db.Jobs
.Include(j => j.Premises)
.Include(j => j.Premises.Select(p => p.Stations))
.Where(j => j.Premises.Any(p => p.Stations.Any(s => s.StationCode == "001")))
.ToList();
As a rule of thumb, I always try to start from the child and work my way up. As you see in the above example, it makes the filtering easier. But maybe you also noticed that it keeps the Include() statements simple too:
.Include(s => s.Premise)
.Include(s => s.Premise.Job)
instead of
.Include(j => j.Premises)
.Include(j => j.Premises.Select(p => p.Stations))
While these two examples are functionally equivalent, having to add a Select() for every level becomes very cumbersome if you want to include entities that are several relationships removed from where you started.

Linq - Trying to filter out the Eager Selection

I am trying to filter out the second part of the tables (UserRoles.IsDeleted==false). Is there any advice how i can do that?
var Users = context.Users.Where(r => r.IsDeleted == IsDeleted).ToList<User>();
Users = context.Users.Include(x => x.UserRoles.Select(y=>y.IsDeleted==false)).ToList();
Thank you
You can do the following to filter using the second part:
var Users = context.Users.Where(r => r.IsDeleted == IsDeleted).ToList<User>();
if(condition)
{
Users = Users.where(y => y.IsDeleted == false)).ToList();
}
There are two options to filter related entities
Doing a projection.
Unfortunately, when you use Include method, you can't filter the related entities as you intend to do. You need to project your query to a DTO object or a anonymous object, as the below example.
var query=context.Users.Include(x => x.UserRoles)
.Where(r => r.IsDeleted == IsDeleted)
.Select(u=> new{ ...,
Roles=x => x.UserRoles.Where(y=>!y.IsDeleted)})
A second option could be using Explicitly Loading. But this is in case you can load the related entities of one specific entity,eg,.
var user=context.Users.FirstOrDefault(r.IsDeleted == IsDeleted);//Getting a user
context.Entry(user)
.Collection(b => b.UserRoles)
.Query()
.Where(y=>!y.IsDeleted)
.Load();
You can do this inside of a foreach per each entity you get from the first query,
var query=context.Users.Where(r => r.IsDeleted == IsDeleted);
foreach(var u in query)
{
context.Entry(u)
.Collection(b => b.UserRoles)
.Query()
.Where(y=>!y.IsDeleted)
.Load();
}
but it's going to be really inefficient because you are going to do a roundtrip to your DB per each entity. My advice is use the first option, projecting the query.

Filtering on Include in EF Core

I'm trying to filter on the initial query. I have nested include leafs off a model. I'm trying to filter based on a property on one of the includes. For example:
using (var context = new BloggingContext())
{
var blogs = context.Blogs
.Include(blog => blog.Posts)
.ThenInclude(post => post.Author)
.ToList();
}
How can I also say .Where(w => w.post.Author == "me")?
Entity Framework core 5 is the first EF version to support filtered Include.
How it works
Supported operations:
Where
OrderBy(Descending)/ThenBy(Descending)
Skip
Take
Some usage examples (from the original feature request and the github commmit)
:
Only one filter allowed per navigation, so for cases where the same navigation needs to be included multiple times (e.g. multiple ThenInclude on the same navigation) apply the filter only once, or apply exactly the same filter for that navigation.
context.Customers
.Include(c => c.Orders.Where(o => o.Name != "Foo")).ThenInclude(o => o.OrderDetails)
.Include(c => c.Orders).ThenInclude(o => o.Customer)
or
context.Customers
.Include(c => c.Orders.Where(o => o.Name != "Foo")).ThenInclude(o => o.OrderDetails)
.Include(c => c.Orders.Where(o => o.Name != "Foo")).ThenInclude(o => o.Customer)
Another important note:
Collections included using new filter operations are considered to be loaded.
That means that if lazy loading is enabled, addressing one customer's Orders collection from the last example won't trigger a reload of the entire Orders collection.
Also, two subsequent filtered Includes in the same context will accumulate the results. For example...
context.Customers.Include(c => c.Orders.Where(o => !o.IsDeleted))
...followed by...
context.Customers.Include(c => c.Orders.Where(o => o.IsDeleted))
...will result in customers with Orders collections containing all orders.
Filtered Include and relationship fixup
If other Orders are loaded into the same context, more of them may get added to a customers.Orders collection because of relationship fixup. This is inevitable because of how EF's change tracker works.
context.Customers.Include(c => c.Orders.Where(o => !o.IsDeleted))
...followed by...
context.Orders.Where(o => o.IsDeleted).Load();
...will again result in customers with Orders collections containing all orders.
The filter expression
The filter expression should contain predicates that can be used as a stand-alone predicate for the collection. An example will make this clear. Suppose we want to include orders filtered by some property of Customer:
context.Customers.Include(c => c.Orders.Where(o => o.Classification == c.Classification))
It compiles, but it'll throw a very technical runtime exception, basically telling that o.Classification == c.Classification can't be translated because c.Classification can't be found. The query has to be rewritten using a back-reference from Order to Customer:
context.Customers.Include(c => c.Orders.Where(o => o.Classification == o.Customer.Classification))
The predicate o => o.Classification == o.Customer.Classification) is "stand alone" in the sense that it can be used to filter Orders independently:
context.Orders.Where(o => o.Classification == o.Customer.Classification) // No one would try 'c.Classification' here
This restriction may change in later EF versions than the current stable version (EF core 5.0.7).
What can (not) be filtered
Since Where is an extension method on IEnumerable it's clear that only collections can be filtered. It's not possible to filter reference navigation properties. If we want to get orders and only populate their Customer property when the customer is active, we can't use Include:
context.Orders.Include(o => o.Customer.Where( ... // obviously doesn't compile
Filtered Include vs filtering the query
Filtered Include has given rise to some confusion on how it affects filtering a query as a whole. The rule of the thumb is: it doesn't.
The statement...
context.Customers.Include(c => c.Orders.Where(o => !o.IsDeleted))
...returns all customers from the context, not only the ones with undeleted orders. The filter in the Include doesn't affect the number of items returned by the main query.
On the other hand, the statement...
context.Customers
.Where(c => c.Orders.Any(o => !o.IsDeleted))
.Include(c => c.Orders)
...only returns customers having at least one undeleted order, but having all of their orders in the Orders collections. The filter on the main query doesn't affect the orders per customer returned by Include.
To get customers with undeleted orders and only loading their undeleted orders, both filters are required:
context.Customers
.Where(c => c.Orders.Any(o => !o.IsDeleted))
.Include(c => c.Orders.Where(o => !o.IsDeleted))
Filtered Include and projections
Another area of confusion is how filtered Include and projections (select new { ... }) are related. The simple rule is: projections ignore Includes, filtered or not. A query like...
context.Customers
.Include(c => c.Orders)
.Select(c => new { c.Name, c.RegistrationDate })
...will generate SQL without a join to Orders. As for EF, it's the same as...
context.Customers
.Select(c => new { c.Name, c.RegistrationDate })
It gets confusing when the Include is filtered, but Orders are also used in the projection:
context.Customers
.Include(c => c.Orders.Where(o => !o.IsDeleted))
.Select(c => new
{
c.Name,
c.RegistrationDate,
OrderDates = c.Orders.Select(o => o.DateSent)
})
One might expect that OrderDates only contains dates from undeleted orders, but they contain the dates from all Orders. Again, the projection completely ignores the Include. Projection and Include are separate worlds.
How strictly they lead their own lives is amusingly demonstrated by this query:
context.Customers
.Include(c => c.Orders.Where(o => !o.IsDeleted))
.Select(c => new
{
Customer = c,
OrderDates = c.Orders.Select(o => o.DateSent)
})
Now pause for a moment and predict the outcome...
The not so simple rule is: projections don't always ignore Include. When there is an entity in the projection to which the Include can be applied, it is applied. That means that Customer in the projection contains its undeleted Orders, whereas OrderDates still contains all dates. Did you get it right?
Not doable.
There is an on-going discussion about this topic:
https://github.com/aspnet/EntityFramework/issues/1833
I'd suggest to look around for any of the 3rd party libraries listed there, ex.: https://github.com/jbogard/EntityFramework.Filters
You can also reverse the search.
{
var blogs = context.Author
.Include(author => author.posts)
.ThenInclude(posts => posts.blogs)
.Where(author => author == "me")
.Select(author => author.posts.blogs)
.ToList();
}
Not sure about Include() AND ThenInclude(), but it's simple to do that with a single include:
var filteredArticles =
context.NewsArticles.Include(x => x.NewsArticleRevisions)
.Where(article => article.NewsArticleRevisions
.Any(revision => revision.Title.Contains(filter)));
Hope this helps!
Although it's (still in discussion) not doable with EF Core, I've managed to do it using Linq to Entities over EF Core DbSet. In your case instead of:
var blogs = context.Blogs
.Include(blog => blog.Posts)
.ThenInclude(post => post.Author)
.ToList()
.. you'll have:
await (from blog in this.DbContext.Blogs
from bPost in blog.Posts
from bpAuthor in bPost.Author
where bpAuthor = "me"
select blog)
.ToListAsync();
I used below package
Use Z.EntityFramework.Plus
IncludeFilter and IncludeFilterByPath two methods are which you can use.
var list = context.Blogs.IncludeFilter(x => x.Posts.Where(y => !y.IsSoftDeleted))
.IncludeFilter(x => x.Posts.Where(y => !y.IsSoftDeleted)
.SelectMany(y => y.Comments.Where(z => !z.IsSoftDeleted)))
.ToList();
Here is the example https://dotnetfiddle.net/SK934m
Or you can do like this
GetContext(session).entity
.Include(c => c.innerEntity)
.Select(c => new Entity()
{
Name = c.Name,
Logo = c.Logo,
InnerEntity= c.InnerEntity.Where(s => condition).ToList()
})
Interesting case and it worked!!
If you have table/model user(int id, int? passwordId, ICollection<PwdHist> passwordHistoryCollection) where collection is history of passwords. Could be many or none.
And PwdHistory(int id, int UserId, user User). This has a quasi relationship via attributes.
Needed to get user, with related current password record, while leaving historical records behind.
User user = _userTable
.Include(u => u.Tenant)
.Include(u => u.PwdHistory.Where(p => p.Id == p.PwdUser.PasswordId))
.Where(u => u.UserName == userName)
.FirstOrDefault();
Most interesting part is .Include(u => u.PwdHistory.Where(p => p.Id == p.PwdUser.PasswordId))
works with user and many passwords
works with user and no passwords
works with no user
We can use by extension
public static IQueryable<TEntity> IncludeCondition<TEntity, TProperty>(this IQueryable<TEntity> query, Expression<Func<TEntity, TProperty>> predicate, bool? condition) where TEntity : class where TProperty : class
{
return condition == true ? query.Include(predicate) : query;
}
Usage;
_context.Tables.IncludeCondition(x => x.InnerTable, true)
This task can be accomplished with two queries. For example:
var query = _context.Employees
.Where(x =>
x.Schedules.All(s =>
s.ScheduleDate.Month != DateTime.UtcNow.AddMonths(1).Month &&
s.ScheduleDate.Year != DateTime.UtcNow.AddMonths(1).Year) ||
(x.Schedules.Any(s =>
s.ScheduleDate.Month == DateTime.UtcNow.AddMonths(1).Month &&
s.ScheduleDate.Year == DateTime.UtcNow.AddMonths(1).Year) &&
x.Schedules.Any(i => !i.ScheduleDates.Any())));
var employees = await query.ToListAsync();
await query.Include(x => x.Schedules)
.ThenInclude(x => x.ScheduleDates)
.SelectMany(x => x.Schedules)
.Where(s => s.ScheduleDate.Month == DateTime.UtcNow.AddMonths(1).Month &&
s.ScheduleDate.Year == DateTime.UtcNow.AddMonths(1).Year).LoadAsync();

LINQ Queries Comparison

I have 2 LINQ Queries here, i just want to know which of these query is proper and fast to use.
Sample I
var GetUSer = (from UserItem in dbs.users
where UserItem.UserID == UserID
select new User(UserItem))
.OrderBy(item => item.FirstName)
.Skip(0)
.Take(10)
.ToList();
Sample II
var GetUSer = (from UserITem in dbs.user
.Where(item => item.UserID == UserID)
.OrderBy(item => item.FirstName)
.Skip(0)
.Take(10)
.AsEnumerable()
select new User(UserItem)).ToList();
Although they are both working well, i just want to know which is the best.
The Second one is better, the first 1 does a select then does filtering, meaning it has to get the data from the database first to turn it into a User object, then it filters.
The second one will do the query on the DB side, then turn it into a User object
The first one can be fixed by moving the select till just before the ToList()
Between those two, I would prefer the first (for readability, you'd need to switch some things around if you want the whole query to execute in the database). If they both work, it's up to you though.
Personally, I don't like mixing query syntax with lambda syntax if I don't have to, and I prefer lambda. I would write it something like:
var GetUsers = db.user
.Where(u => u.UserID == UserID)
.OrderBy(u => u.FirstName)
.Take(10)
.Select(u => new User(u))
.ToList();
This uses a single syntax, queries as much as possible in the database, and leaves out any superfluous calls.

Categories

Resources