Why is Entity Framework 6.1 inconsistently retrieving related entities? - c#

I have the following (subset) in a SQL Server database that has been generated by Entity Framework 6.1 code-first:
I then run the following query
var response = await Context.SurveyResponses
.Include(x => x.Answers)
.SingleOrDefaultAsync(x => x.Client.Id == clientId &&
x.Survey.Id == surveyId &&
!x.CompletedOn.HasValue);
In 99% of cases, the Question object on the Answers collection would be populated. However in rate 1% of cases, it would be null.
This problem was solved by improving the Include statement as follows
var response = await Context.SurveyResponses
.Include(x => x.Answers.Select(y => y.Question))
.SingleOrDefaultAsync(x => x.Client.Id == clientId &&
x.Survey.Id == surveyId &&
!x.CompletedOn.HasValue);
However what I'd like to understand is why the first query ever worked? How did it know to include the Question property of the Answers object? Why did it work in some cases and not others?

EF will auto-fixup what it can. If your context had the data already, it would fill it in for you.
For example, assuming SurveryResponseAnswer with id of 1 has a question of id 1, then:
var db=new Context();
var test=db.Questions.Where(x=>x.id==1);
var result=db.SurveyResponseAnswers.Where(x=>x.id==1);
// result's Question property is filled in
var db=new Context();
var result=db.SurveyResponseAnswers.Where(x=>x.id==1);
// result's Question property is not filled in

Related

EF Core 2.0 Code First error in query (DetachedLazyLoadingWarning)

I have the following query in my application:
var Company = Db.Company.SingleOrDefault(si => si.Guid == companyId);
var items = Db.Programs.Where(w => w.SubCompanyId == Company.CompanyId)
.GroupBy(g => g.Projects).Include(i => i.Key.ProjectLeader);
if (skip.HasValue && take.HasValue)
{
items = items.OrderByDescending(o => o.Key.DatumAanmaak).Skip(skip.Value).Take(take.Value);
}
var materialized = items.ToList();
return materialized.Select(s => new Models.Project()
{
Guid = s.Key.Guid,
ProjectId = s.Key.Id,
Title = s.Key.Titel,
CompanyId= s.Key.CompanyId,
ProjectLeaderFk = s.Key.ProjectLeaderId,
ProjectLeaderName = s.Key.ProjectLeader.FullName,
IsIncoming = s.Key.IsIncoming ?? true,
ProgramCount = s.Count(w => w.TargetCompanyId == Company.CompanyId),
ApplicationAmount = s.Where(w => w.TargetCompanyId == Company.CompanyId).Sum(su => su.ApplicationAmount ),
AvailableAmount = s.Where(w => w.TargetCompanyId == Company.CompanyId).Sum(su => su.AvailableAmount)
}).ToList();
Since my project is code first, this gives the following error:
System.InvalidOperationException: 'Error generated for warning 'Microsoft.EntityFrameworkCore.Infrastructure.DetachedLazyLoadingWarning: An attempt was made to lazy-load navigation property 'ProjectLeider' on detached entity of type 'ProjectProxy'. Lazy-loading is not supported for detached entities or entities that are loaded with 'AsNoTracking()'.'. This exception can be suppressed or logged by passing event ID 'CoreEventId.DetachedLazyLoadingWarning' to the 'ConfigureWarnings' method in 'DbContext.OnConfiguring' or 'AddDbContext'.'
What is exactly causing this error? I am not using AsNoTracking and I included that table that is causing the error in the query. What is the easiest way to solve this?
What is exactly causing this error? I am not using AsNoTracking and I included that table that is causing the error in the query.
Your query is falling into Ignored Includes category:
If you change the query so that it no longer returns instances of the entity type that the query began with, then the include operators are ignored.
The GroupBy operator is changing the entity type the query began with (Program) to something else, so .Include(i => i.Key.ProjectLeader) has no effect (is ignored).
Probably the easiest way to resolve it is to remove the materialization and project (Select) directly from the source queryable (items), e.g.
//var materialized = items.ToList();
return items.Select(s => new Models.Project() { ... }).ToList();
The following solved it for me: (using ThenInclude instead of Include)
var items = Dbc.SubSubsidieProgrammas.Include(i => i.Project).ThenInclude(i => i.ProjectLeider).Where(w => w.TargetCompanyId == bedrijf.BedrijfPk).GroupBy(g => g.Project);

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.

EF 7 Include a collection with where clause and then a collection one level down

I am trying to query a collection and child collection using EF 7. Here's the code:
var customerID = 86795;
var query = await _context.Contacts
.Where(g => g.CustomerID == customerID )
.Include(g => g.Address.Where(p => p.AddressTypeID == 1))
.ThenInclude(p=> p.City)
.ToListAsync();
> Error CS1061 'IEnumerable<Address>' does not contain a definition for
> 'City' and no extension method 'City' accepting a first argument of
> type 'IEnumerable<Address>' could be found (are you missing a using
> directive or an assembly reference?) Contacts.DNX 4.5.1, Contacts.DNX
> Core 5.0
It works fine when I just use
var customerID = 86795;
var query = await _context.Contacts
.Where(g => g.CustomerID == customerID )
.Include(g => g.Address)
.ThenInclude(p=> p.City)
.ToListAsync();
But this will load all the addresses for the customer where I only want the recent address for which the AddressTypeID is 1.
Any idea how to do this?
You can try anonymous projection, that will fetch translate your query into SQL.
var customerID = 86795;
var query = await _context.Contacts
.Where(g => g.CustomerID == customerID)
.Select(cntct=> new
{
contact = cntct,
address = cntct.Address.Where(p => p.AddressTypeID == 1),
city = cntct.Address.Where(p => p.AddressTypeID == 1)
.Select(h=>h.City),
}.ToList();
You can't filter in Include. In any version of entity framework.
If you need to load a subset of the collection then you need to Join instead of using navigation property and filter whenever you need using Where clause
Like this (simplified, extra steps for readability):
var filteredAddresses = Addresses.Where(x=>x.AddressTypeId==1);
var customersWithAddress = Customers.Join(filteredAddresses, x=>x.Id,x=>x.CustomerId,(c,a)=> new {
Customer=c,
Address=a
});
Or if you need a single customer, assuming you have Customer navigation property in Address:
var addressWithCustomer = Addresses
.Where(x=>x.AddressTypeId==1 && x.CustomerId == customerId)
.Include(x=>x.Customer)
.Include(x=>x.City)
.Single();
A lot of times, it is better to approach queries which involve conditional nested entities, to start with the nested entity, apply the conditions to this nested fellow and then project out the parent entity, since it is always easier to reach to the parent entities from the nested enumerable ones. (many to one)
in our case, we can apply the filter out on the Address entity and then group it on the Contact entity.
var customerID = 86795;
var query = await _context.Addresses
.Where(a => a.Contact.CustomerID == customerID
&& a.Contact.RegistrationDate.Year == 2016
&& a.AddressTypeID == 1)
.Include(a => a.Contact)
.Include(a => a.City)
.GroupBy(a => a.Contact)
.Take(20) // by the way, you should apply some orderby for a predicatble Take
.ToListAsync();
and if you absolutely want a list of Contacts as the output of the above query, you can do this.
var contacts = query.Select(g =>
{
g.Key.Addresses = g.ToList();
return g.Key;
}).ToList();
// now you can work off the Contacts list, which has only specific addresses
This will basically give you a grouped list of all Contacts with CustomerID, and with those address types and registration years only. The important thing here is to iterate through the group to get the addresses, and not use the grouping.Key.Addresses navigation. (grouping.Key will be the Contact entity)
Also, I don't know if CustomerID is a primary key on the Contact entity, but if it is, it looks like you would just need a list of matching addresses for one Contact. In that case, the query would be:
var query = await _context.Addresses
.Where(a => a.Contact.CustomerID == customerID && a.AddressTypeID == 1)
.Include(a => a.Contact)
.Include(a => a.City)
.ToListAsync();
Include The Collection for Eager Load then use Any instead of Where ... to Select specific items in the child of the wanted entity.
var customerID = 86795;
var query = await _context.Contacts
.Where(g => g.CustomerID == customerID )
.Include(g => g.Address.Any(p => p.AddressTypeID == 1))
.ThenInclude(p=> p.City)
.ToListAsync();

Chain query in Linq

I have the following queries:
var ground = db
.Ground
.Where(g => g.RowKey == Ground_Uuid)
.ToList();
var building = db
.Building
.Where(b => ground.Any(gr => gr.RowKey == b.Ground.RowKey))
.ToList();
var floor = db
.Floor
.Where(b => building.Any(by => by.RowKey == b.Building.RowKey))
.ToList();
So the second relies on the id from the first set and so on.
I got following error when an execution goes to the second query:
Unable to create a constant value of type 'Domain.Model.Entities.Ground'. Only primitive types or enumeration types are supported in this context.
Any ideas how to resolve it?
The problem with your code is that ToList is converting the result into an in-memory object and a collection of objects in memory cannot be joined with a set of data in the database.
var ground = db.Ground.Where(g => g.RowKey == Ground_Uuid);
var building = db.Building.Where(b => ground.Any(gr => gr.RowKey == b.Ground.RowKey));
var floor = db.Floor.Where(b => building.Any(by => by.RowKey == b.Building.RowKey));
Also, frankly after reading #juharr's comment, I saw the relationship between floor, building & ground. Since you are already doing b.Building.RowKey, b.Ground.RowKey predicting the relationship was easy and I totally agree, it can be simplified as:-
var floor = db.Floor.Where(b => b.Building.Ground.RowKey == Ground_Uuid);
It seems like the first query is redundant. You already know that the RowKey column for each row will be equal to Ground_Uuid.
var building = db.Building.Where(b => b.Ground.RowKey == Ground_Uuid);
var floor = db.Floor.Where(b => b.Building.Ground.RowKey == Ground_Uuid);
Removing ToList() would do the job, but moreover if RowKey is the foreign key you can utilize Linq:
var floor = db.Floor
.Where(b => b.Building.Ground.RowKey == Ground_Uuid)
.ToList();

Entity Framework exclude records from the foreign table

I'm having a problem excluding the records from a foreign table based on a condition.
// this query returns everything from all the tables that are referenced in MainTable
var query = db.MainTable.Where(x => x.ID == 123)
How can I exclude some of the records from one of the foreign tables based on a Status field in the foreign table?
Something like this:
var query = db.MainTable.Where(x => x.ID == 123 && y => y.ForeignTable.Status == false)
Thank you.
Since you said that you do have the correct relationships set up, and your example seems to suggest that what you have is a singular relationship (not a reference to an ICollection) you should be able to just do:
var query = db.MainTable.Where(x => x.ID == 123 && x.ForeignTable.Status == false).
If you were using an ICollection, you'd then have to do something like this:
var query = db.MainTable.Where(x => x.ID == 123 && x.ForeignTable.All(y => y.Status == false))
which I think would work, but haven't tested.
You could also look into the Enumerable.Join syntax which I have needed for some more complex joins. It can be really verbose however, and for simple joins the method above should work just fine.

Categories

Resources