Using ThenInclude with condition in Entity Framework - c#

return _companyRepository.GetAll().Where(company => company.Id == Id)
.Include(company => company.offices.Where(o => o.IsActive))
.ThenInclude(office => office.People.Where(p => p.IsFired))
.ThenInclude(person => person.Children)
.ToList();
How can I get all fired people of all active offices using Entity Framework Core?
I'm getting this error:
Where clause in not valid inside ThenInclude or Include
company.offices.Where(o => o.IsActive)
office.People.Where(p => p.IsFired)
I know I can not use Where inside Include. the question is how to filter data with Include clause?

It's not possible.
I'd suggest to look around for any of the 3rd party libraries listed there, example :
EntityFramework.Filters
Query IncludeFilter
How to filter “Include” entities in entity framework?

You do not need to use any Include, in case of you use your data in your query. Include is just a way to disable Lazy Loading. Include controls whether a navigation property is filled with data or not. Include is not made to filter anything.
Your query will also return companies (with people) not people.
return _companyRepository.GetAll().Where(company => company.Id == Id)
.SelectMany(company => company.offices)
.Where(o => o.IsActive)
.SelectMany(office => office.People)
.Where(p => p.IsFired)
.Include(person => person.Children)
.ToList();
This creates a List with persons, including their children.
The last include controls says, all the children are loaded with this query. If you remove this include, you still can access the children, but than it is load-on-demand or lazy-loading. You will not notice any difference in the functions of your program, but in the communication with your sql-server.

Related

Strange behaviour when filtering on Include with AsNoTracking

I don't know if it is expected to be this way but I thought it is strange since changes the query results.
When I execute the query below I get my entity with only 2 "Mensagens" entity because i'm filtering only the actives so it's right, I have 2 active entities on my database and 1 inactive.
return await context.Lancamentos
.Include(x => x.UsuarioCriacao)
.Include(x => x.Mensagens.Where(m => m.Ativo))
.ThenInclude(m => m.MensagemMedias)
.ThenInclude(m => m.MediaWhatsapp)
.ThenInclude(m => m.TipoMediaWhatsapp)
.Include(x => x.Mensagens.Where(m => m.Ativo))
.ThenInclude(x => x.TemplateMensagem)
.ThenInclude(t => t.Medias)
.ThenInclude(m => m.MediaWhatsapp)
.ThenInclude(m => m.TipoMediaWhatsapp)
.AsNoTracking()
.FirstOrDefaultAsync(l => l.Id == id && l.Ativo);
But if I execute the exact same command just removing the AsNoTracking() line it gives me the 3 records, both active and inactive.
But the inactive one EF doesn't fetch the ThenInclude below. It changes the behaviour.
With AsNoTracking it filters the data according to the filter I used on Include
Without AsNoTracking it bring me all the data but it filters if it will load or not the ThenInclude objects.
Does anyone know if this is a normal behaviour and why does it behaves like this?
This is sort of explained in Filtered include documentation:
Caution
In case of tracking queries, results of Filtered Include may be unexpected due to navigation fixup. All relevant entities that have been queried for previously and have been stored in the Change Tracker will be present in the results of Filtered Include query, even if they don't meet the requirements of the filter. Consider using NoTracking queries or re-create the DbContext when using Filtered Include in those situations.
So most likely your context is not clean when doing tracking tests. And even if it is clean, in case it is used for executing other queries, the navigation fixup can load some non satisfying filter entities later since it keeps track (has access) to all tracked entities.
In general you cannot rely on content of navigation property of a tracked entity since it may be updated at any time during the lifetime of the context until the context is disposed or entity detached from change tracker. If you need full control, the either use no tracking entity queries or DTO/ViewModel etc. projecting queries and select exactly what you want (no Include / ThenInclude, just plain LINQ).

How to use where in relation with EntityFramework? [duplicate]

This question already has answers here:
EF: Include with where clause [duplicate]
(5 answers)
Closed 2 years ago.
I have two models that are related. Cars and announcements. Announcement and car ha one-to-one relationship.
How can I include a where in the relation?
updated: the name of the property is announcement not announcements and it is not a collection.
this.context.Cars.Include(a => a.announcement); // stuck here, I want to find the
// announcements that are active.
For a Where() on the Car:
this.context.Cars.Include(c => c.announcements).Where(c => c.Value == value);
Note, I've change your 'a' to a 'c' on the Include() because it represents a car, not an announcement.
For a Where() on the Announcement:
this.context.Cars.Where(c => c.announcements.Value == value);
Note, there is no need to Include() announcements in order to look at it in the Where(). You only need Include() if you're going to read announcements data in the programme once the query is executed.
Note: since you have use the plural form for the announcements property, I deduced it's a collection. My answer is based on this assumption. Btw., the rule is to use PascalCase for properties.
Include always includes all the related records. You have to make the test afterwards:
IEnumerable<Announcement> activeAnnouncements = context.Cars
.Include(c => c.announcements)
.SelectMany(c => c.announcements)
.Where(a => a.IsActive);
Note that SelectMany flattens nested sequences. Here, it produces a sequence of all announcements in all the cars.
If you had a one-to-many relationship and needed the cars together with the active announcements, you could combine the two in a ValueTuple:
IEnumerable<(Car c, Announcement a)> carsAndActiveAnnouncements = context.Cars
.Include(c => c.announcements)
.SelectMany(c => c.announcements, (c, a) => (c, a)) // 1st (c, a) are lambda parameters,
// 2nd creates tuple.
.Where(ca => ca.a.IsActive);
Get all the cars with active announcements, but including all the announcements (in a one-to-one relationship, this is always a single active announcement):
IEnumerable<Car> carsHavingActiveAnnouncements = context.Cars
.Include(c => c.announcements)
.Where(c => c.announcements.Any(a => a.IsActive));
And finally, you can add this property to the Car class (expression bodied syntax):
public IEnumerable<Announcement> ActiveAnnouncements =>
announcements.Where(a => a.IsActive);
Same as (full syntax):
public IEnumerable<Announcement> ActiveAnnouncements
{
get {
return announcements.Where(a => a.IsActive);
}
}
It lets you retrieve the active announcements easily at any time. The result is accurate even after edits.
But since, in this case, you have a one-to-one relationship, this does not help much.
Update:
Since, according to your update, announcement is not a collection and since you want to select the cars having an active announcement, here is my new solution:
IEnumerable<Car> carsHavingAnActiveAnnouncement = context.Cars
.Include(c => c.announcement)
.Where(c => c.announcement.IsActive);
Note that the Include is not required for the Where clause to work, as it will be translated to SQL and does not rely on an object reference. But it is of course legitimate, if you want the announcements to be loaded.
You elaborated on the purpose of the query in a comment:
My original problem is only select the cars that have an active annoucement. In another worlds only car data is needed.
If you don't need to load the announcement data, then you don't need to use Include.
I suspect this may be a confusion between EF/LINQ and SQL. In SQL, you have to join your data (= "include" it in the data set) before you can use it, regardless of whether you intend to use it in a SELECT, WHERE, or other.
But with EF/LINQ, you only need to use Include in a very specific circumstance: when you want to load this data in the result set, and when you don't already use a Select() to return a custom type (because this overrides the include behavior anyway).
What you want is to filter the data, and that doesn't require you to include it. You can simply call Where() to filter the data appropriately:
var carsWithAnActiveAnnouncement = db.Cars
.Where(c => c.announcement.IsActive)
.ToList();
Note: I assumed the property is called IsActive for the sake of example. You can correct this if needed.
This gives you the correct filtered list of cars without actually loading the announcements data, since you said you don't need it.

LINQ: How to sort the items of a recursive tree structure? [duplicate]

This question already has answers here:
Filtering on Include in EF Core
(9 answers)
Closed 1 year ago.
The community reviewed whether to reopen this question last year and left it closed:
Original close reason(s) were not resolved
I am trying to get something like the following to work:
_dbmsParentSections = FactoryTools.Factory.PdfSections
.Include(x => x.Children.OrderBy(y => y.Order).ToList())
.Include(x => x.Hint).Include(x => x.Fields)
.Where(x => x.FormId == FormId && x.Parent == null)
.OrderBy(o => o.Order)
.ToList();
The part that causes the exception is:
.Include(x => x.Children.OrderBy(y => y.Order).ToList())
EDIT:
Upon further observation,
_dbmsParentSections.ForEach(x => x.Children = x.Children.OrderBy(y => y.Order).ToList());
did the job for me (after the initial Factory call and without the Children.OrderBy).
It seems you cannot sort the children collection in your query.
Either sort after the query or load the children in a second query.
Similar question and answer here
According to this documentation, starting with EF Core 5.0, you can sort by a property of your Included Entity:
await context.Parents
.OrderBy(parent => parent.Order)
.Include(parent => parent.Children.OrderBy(child => child.Order))
.ToListAsync();
The above example sorts Parent entities by their Order, as well as their Children entities by the Children entities' Order property.
The extension method Includeis a mere wrapper around DbQuery.Include. Internally it does not execute the expressions but only parses them, i.e. it takes their member expressions and converts them to a path as string. The path is used as input for DbQuery.Include.
It has been requested before to enhance the functionality of Include, e.g. to allow partly loaded collections by including a Where clause. Ordering could be another change request. But as you see, because of the internal working of Include the whole mechanism will have to be re-engineered to implement such enhancements. I don't see it on the current road map so it may take a while...
Depending on the use case you might not need to load in separate query or sort afterwards.
In my case I needed them ordered for when looping in the view so I just ordered there
#foreach (var subObject in Object.SubObjects.OrderBy(x=>x.Order))
I use this code por order the include, using a select and a function to order the collection.
Is not the best but work fine if subcollection is small
// GET: api/Tareas
[HttpGet]
public IEnumerable<Tarea> GetTareas()
{
var result = _context.Tareas
.Include(p => p.SubTareas)
.Select(p => SortInclude(p));
return result;
}
private Tarea SortInclude(Tarea p)
{
p.SubTareas = (p.SubTareas as HashSet<SubTarea>)?
.OrderBy(s => s.Position)
.ToHashSet<SubTarea>();
return p;
}
This will never gona work. EF include is try to understand and translate everything to SQL, but you want to much from this. Load all entities without sorting and .ToList()-ing, and write an extension method for IEnumerable to get an ordered result.
Generally if you're using a bunch of includes, it's because you need to access child properties in a view. What I do is order the child collection when I need to access it in a view.
For example, I might build some Include statements for a master/detail form. There's no sense ordering this at the initial EF query. Instead, why not order these child records at the view level when you're actually accessing them?
I might have a survey with multiple survey questions. If I want to present the questions in a particular order at do it at the partial view level when I'm passing the model child collection to the partial view.
#Html.Partial("_ResponsesPartial",Model.SurveyResponses.OrderBy(x =>
x.QuestionId))
You should not convert an IQueryable type to IEnumerable and call Include because Include is not supported by IEnumerable type.
In short, never call Include after ToList
IQueryable = server side call (SQL)
IEnumerable = client side (loaded in memory)

Eager loading Entity framework core

I'm looking for the most elegant/best way to load navigation properties after creating an entry.
The situation is this:
*I create a row in my table and link 2 other tables by ID. The object I get back only contains the id's, not the actual linked objects.
*Through eager loading I want to load those objects
context.Entry(b)
.Reference(e => e.Table1)
.Reference(e => e.Table2)
.Load();
Doesn't seem to work, I can't chain References, so I could either query the complete object:
context
.Objects
.Where(o => o.ID == id)
.Include(o => o.Table1)
.Include(o => Table2)
.FirstOrDefault();
or do this :
context.Entry(b)
.Reference(e => e.Table1)
.Load();
context.Entry(b)
.Reference(e => e.Table2)
.Load();
But this creates (I suspect) 2 calls to the database instead of 1 combined call. Or am I missing another way to chain those references?
For this specific scenario you can use simple anonymous type projection and rely on the navigation property fix-up as described in Loading Related Data:
Tip
Entity Framework Core will automatically fix-up navigation properties to any other entities that were previously loaded into the context instance. So even if you don't explicitly include the data for a navigation property, the property may still be populated if some or all of the related entities were previously loaded.
So the following would do the job:
context.Objects
.Where(o => o.ID == id)
.Select(o => new { o.Table1, o.Table2 })
.Load();
and in theory should be better (should load only the related data). However, due to a current (v1.1.0) EF Core bug it would also include all the root object fields, thus making it equivalent of the variant with Includes:
context.Objects
.Where(o => o.ID == id)
.Include(o => o.Table1)
.Include(o => o.Table2)
.Load();
I personally would use the first method because the bugs hopefully will be fixed in some future EF Core release, while the second method behavior is "by design".

IQueryable<T>.Include() gets ignored

I have Parent and Child entities related to each other as 1 to M. I need to query childs together with parents within a single SQL query, but the Include method is not properly working for some cases.
This one makes a correct single query for both Parent and Child tables (via JOIN):
var r1 =
ctx.Set<Parent>()
.Include(p => p.Childs)
.Where(p => p.Id == 1)
.ToList();
Once i create an anonymous object on the fly, children are getting lost and SQL contains only Parent's fields. Retrieval of children remains lazy - they are still not loaded:
var r2 =
ctx.Set<Parent>()
.Include(p => p.Childs)
.Where(p => p.Id == 2)
.Select(p => new { myParent = p})
.ToList();
Questions:
Why it's so?
How can I construct a new anonymous object in my LINQ so childs are not geting lost?
p.s. i'd like keep Childs property of Parent virtual.
This is a general problem in all versions of EF known to me. EF tries hard to pass the 'includes' as far as possible, but when "the shape of the query changes", the 'includes' are irreversibly lost.
The "shape" of the query changes for example, when:
a projection is used (select not whole object but just some fields, or different object)
a group-by or other aggregation is used
.. and probably in some more cases, which currently I dont remember.
Sadly, I also dont remember where on MSDN I stumbled upon the "shape of the query" explanation. If I find it, I'll drop here a link.
The solution is actually quite simple: simply specify the 'include' part not early, but at the final result. So, instead of set.include(x) at beginning, do .Select( .. => new { .., x }) to include the 'x' manually. It also works during grouping, as you can do a projection there too.
However, this is not a solution. This is a manual patch/hotfix, which does not solve anything. Considering that you might want to expose a "IQueryable<>" through some interface, you may like to expose a "base query" with some things already .Included. And of course, this is simply not possible to do in a general way, as if the client of the interface does a projection or grouping, he will lose the includes and he will not even know which ones should be. For me, this is a major deficiency in EF.
EDIT: just found one: .Include in following query does not include really Not MSDN, but just as good.
As you're creating an anonymous object the Parent DbSet Set<Parent>() of the context is not being populated with any data and therefore neither are the Children being stored in the context. One solution could be to add the children to the anonymous object but I'm not sure this would cause them to be added to the Set<Child> DbSet.
var r2 = ctx.Set<Parent>()
.Include(p => p.Childs)
.Where(p => p.Id == 2)
.Select(p => new { myParent = p, children = p.Childs })
.ToList();

Categories