简体   繁体   中英

Return parents with null children using C# EF Core .Include

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!

You can use DefaultIfEmpty() method after Include() .

For example:

var projects = dbContext.Projects.Include(x => x.Location).DefaultIfEmpty()...;

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.

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM