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".
Related
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).
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.
This question already has answers here:
Using EF Core ThenInclude() on Junction tables
(1 answer)
EF Core Second level ThenInclude missworks
(2 answers)
Closed 3 years ago.
I have a .Net Core 2.2 project with EntityFramework and a model with many nested properties either single object or collections.
I've enabled lazy loading and now I want selectively load only the parts of the object tree I'm insterested in in my controller.
When I use .Include() everything is fine until I want to include a collection property and for every item in that collection I want to include a related entity.
Reading the documentation I used this approach:
var mainObj = _db.MyEntityA.AsNoTracking()
.Include(e => e.MyEntityB)
.Include(e => e.CollectionOfEntityC.Select((MyEntityC ce) => ce.MyEntityD))
When I run it it gives something like this:
InvalidOperationException: The Include property lambda expression 'e => {from EntityC ce in e.CollectionOfEntityC select [ce].MyEntityD}' is invalid. The expression should represent a property access: 't => t.MyProperty'. To target navigations declared on derived types, specify an explicitly typed lambda parameter of the target type, E.g. '(Derived d) => d.MyProperty'.
So I've tried to add the cast:
var mainObj = _db.MyEntityA.AsNoTracking()
.Include(e => e.MyEntityB)
.Include(e => e.CollectionOfEntityC.Select((MyEntityC ce) => ce.MyEntityD))
But nothing changes.
I've tried to use .ThenInclude() this way:
var mainObj = _db.MyEntityA.AsNoTracking()
.Include(e => e.MyEntityB)
.ThenInclude(e => e.Select((MyEntityC ce) => ce.MyEntityD))
With or without the cast, nothing changes.
Finally, reasonably, if I remove that navigation property, it gives me this exception:
Error generated for warning 'Microsoft.EntityFrameworkCore.Infrastructure.DetachedLazyLoadingWarning: An attempt was made to lazy-load navigation property 'MyEntityD' on detached entity of type 'MyEntityCProxy'. Lazy-loading is not supported for detached entities or entities that are loaded with 'AsNoTracking()'.
This same scenario, worked as expected on the EF for the .Net Framework, form which I'm porting to .Net Core.
EDIT:
Exploring the correctly mentioned duplicates, I just wanted to add few more related aspects.
#StriplingWarrior answer below is good; I fixed my code using .ThenInclude() instead of .Select(); now everything builds, but:
IntelliSense doesn't help writing the code inside ThenInclude()
if I leave AsNoTraking() I must disable the DetachedLazyLoadingWarning
if I remove AsNoTracking() EF tracks changes I don't want it to track (I want a readonly snapshot of a complex object)
It looks like this is how you're supposed to use ThenInclude:
var mainObj = _db.MyEntityA.AsNoTracking()
.Include(e => e.MyEntityB)
.Include(e => e.CollectionOfEntityC)
.ThenInclude(ce => ce.MyEntityD);
My context is marked with
this.Configuration.LazyLoadingEnabled = false;
I want to load selected related entities. Example like
context.Entry(catalog)
.Collection(p => p.Products)
.Query()
.Where(p => p.VendorId == 1)
.Load();
This works fine.
context.Entry(catalog)
.Collection(p => p.Tags)
.Query()
.Where(p => p.TagId== 1)
.Load();
This works fine too. But I assume this causes two separate Db calls. I want to make this in one call. How can I do that ? (I do not want to use .Include because it loads a huge list of another property values into the category object). Any ideas ?
Update
I want to load both Product and Tag at the same time without loading them separately as above.
I have this line of code that selects a workflow status and then gets the next workflow statuses mapped to it.
status = db.WorkflowStatuses
.Include(x => x.CurrentMappings.Where(y => y.IsActive && y.NextWorkflowStatus.IsActive))
.Include(x => x.CurrentMappings.Select(y => y.NextWorkflowStatus).Where(y => y.IsActive))
.FirstOrDefault(x => x.Id == id);
My question is do I need the second Include since I referenced NextWorkflowStatus in the first Include?
Include represents eager loading and eager loading in EF doesn't support filtering or ordering so your code will not work at all. You cannot use Where inside include call.