Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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!

like image 991
David Garwin Avatar asked Jan 29 '23 20:01

David Garwin


2 Answers

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

For example:

var projects = dbContext.Projects.Include(x => x.Location).DefaultIfEmpty()...;
like image 76
Metehan Teber Avatar answered Feb 06 '23 12:02

Metehan Teber


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.

like image 31
Daniel Earwicker Avatar answered Feb 06 '23 11:02

Daniel Earwicker