问题
Our goal is to query a database, using Entity Framework Core and the .Include(...)
extension method to return a collection of objects that have a child, where some of the children will be null.
We have a table Projects with a C# model Project and a table Locations with a C# model Location.
Each Project has a one or zero Location objects and the objects look like this:
public class Project
{
public string LocationId { get; set; }
public Location Location { get; set; }
}
public class Location
{
public string LocationId { get; set; }
}
Database setup looks like this:
modelBuilder.Entity<Location>(entity =>
{
entity.HasKey(location => location.LocationId);
entity.ToTable("Locations");
});
modelBuilder.Entity<Project>(entity =>
{
entity.HasOne(project => project.Location).WithMany()
.HasForeignKey(x => x.LocationId);
entity.ToTable("Projects");
});
The query we've created is:
var projects = dbContext.Projects.Include(x => x.Location);
The SQL that EF generates includes a LEFT JOIN when we'd expect a LEFT OUTER JOIN:
SELECT [project].[LocationId]
FROM [Projects] AS [project]
LEFT JOIN [Locations] AS [c] ON [project].[LocationId] = [c].[LocationId]
The result is that only Projects with Locations are returned. We want all Projects and their Locations.
From this link I understand that .Include(...)
determines to do a LEFT JOIN or a LEFT OUTER JOIN depending on the nullability of the foreign key:
Code First infers the multiplicity of the relationship based on the nullability of the foreign key. If the property is nullable then the relationship is registered as optional.
As that isn't what happens, there is something missing.
What modifications need to be made to return all Projects, regardless of if their Location will be populated?
Thanks!
回答1:
You can use DefaultIfEmpty()
method after Include()
.
For example:
var projects = dbContext.Projects.Include(x => x.Location).DefaultIfEmpty()...;
回答2:
I hit this situation, except the generated SQL is:
INNER JOIN [Blah.[Table] AS [x.Child] ON [x].[ChildId] = [x.Child].[Id]
This genuinely causes the problem, whereas the question's LEFT JOIN
would be fine (as others have pointed out).
So why was this happening? My ChildId
is a Guid
, and I had made sure to make it a Guid?
as nullability makes it optional.
I next tried adding .IsRequired(false)
to the mapping for ChildId
. This gave an error that told me what the actual problem was: I had also included ChildId
(unnecessarily) in the primary key.
Removing it from the primary key caused the query to change to a LEFT JOIN
and all is good.
回答3:
In T-SQL a Left Join and Left Outer Join are the same thing. The Outer key word is optional. The query you gave will return all products whether or not they have a location. Test it and see.
For more details see: LEFT JOIN vs. LEFT OUTER JOIN in SQL Server
来源:https://stackoverflow.com/questions/48348412/return-parents-with-null-children-using-c-sharp-ef-core-include