I have an EF Core entity with a collection of owned entities (Customer.Contacts) and a collection of regular entities (Customer.OrderedProducts).
When I load data using "explicit loading", I get a "cartesian explosion" error.
A complete minimal reproduction follows (for .NET 7).
dotnet new console -o Repro
cd Repro
dotnet add package Npgsql.EntityFrameworkCore.PostgreSQL
dotnet new tool-manifest
dotnet tool install dotnet-ef
dotnet add package Microsoft.EntityFrameworkCore.Design
# ...add Model.cs and Program.cs files...
dotnet ef migrations add Init
dotnet ef database update
dotnet run
Model.cs:
public class Customer // owner
{
public long Id { get; init; }
public ICollection<Contact> Contacts { get; } = new List<Contact>(); // owned
public ICollection<Product> OrderedProducts { get; } = new List<Product>(); // regular
}
public class Contact // owned
{
public long Id { get; init; }
public string Name { get; init; }
}
public class Product // regular
{
public long Id { get; init; }
public string Category { get; init; }
}
public class Context : DbContext // uses postgres database
{
public DbSet<Customer> Customers { get; set; }
public DbSet<Product> Products { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder options)
{
options.UseNpgsql("Host=localhost;Port=5432;Database=database;Username=username;Password=password;Include Error Detail=True;");
options.LogTo(Console.WriteLine, LogLevel.Warning);
options.ConfigureWarnings(x => x.Throw(RelationalEventId.MultipleCollectionIncludeWarning));
}
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(modelBuilder);
builder.Entity<Customer>().HasKey(x => x.Id);
builder.Entity<Customer>().OwnsMany(x => x.Contacts);
builder.Entity<Customer>().HasMany(x => x.OrderedProducts).WithMany();
builder.Entity<Product>().HasKey(x => x.Id);
}
}
Program.cs:
using Microsoft.EntityFrameworkCore;
// seed
using var context = new Context();
var customer = new Customer();
customer.Contacts.Add(new() { Name = "Bob" });
customer.OrderedProducts.Add(new() { Category = "Shoes" });
await context.AddAsync(customer);
await context.SaveChangesAsync();
// read entity
var customer1 = await context.Customers.OrderBy(x => x.Id).FirstAsync();
// load data for tracked entity using explicit loading
await context
.Entry(customer1)
.Collection(x => x.OrderedProducts)
.LoadAsync(); // ERROR
Error:
Unhandled exception. System.InvalidOperationException: An error was generated for warning 'Microsoft.EntityFrameworkCore.Query.MultipleCollectionIncludeWarning': Compiling a query which loads related collections for more than one collection navigation, either via 'Include' or through projection, but no 'QuerySplittingBehavior' has been configured. By default, Entity Framework will use 'QuerySplittingBehavior.SingleQuery', which can potentially result in slow query performance. See https://go.microsoft.com/fwlink/?linkid=2134277 for more information. To identify the query that's triggering this warning call 'ConfigureWarnings(w => w.Throw(RelationalEventId.MultipleCollectionIncludeWarning))'. This exception can be suppressed or logged by passing event ID 'RelationalEventId.MultipleCollectionIncludeWarning' to the 'ConfigureWarnings' method in 'DbContext.OnConfiguring' or 'AddDbContext'.
at Microsoft.EntityFrameworkCore.Diagnostics.EventDefinition.Log[TLoggerCategory](IDiagnosticsLogger`1 logger, Exception exception)
at Microsoft.EntityFrameworkCore.Diagnostics.RelationalLoggerExtensions.MultipleCollectionIncludeWarning(IDiagnosticsLogger`1 diagnostics)
at Microsoft.EntityFrameworkCore.Query.RelationalShapedQueryCompilingExpressionVisitor.VisitShapedQuery(ShapedQueryExpression shapedQueryExpression)
at Microsoft.EntityFrameworkCore.Query.ShapedQueryCompilingExpressionVisitor.VisitExtension(Expression extensionExpression)
at Microsoft.EntityFrameworkCore.Query.RelationalShapedQueryCompilingExpressionVisitor.VisitExtension(Expression extensionExpression)
at Microsoft.EntityFrameworkCore.Query.QueryCompilationContext.CreateQueryExecutor[TResult](Expression query)
at Microsoft.EntityFrameworkCore.Storage.Database.CompileQuery[TResult](Expression query, Boolean async)
at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.CompileQueryCore[TResult](IDatabase database, Expression query, IModel model, Boolean async)
at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.<>c__DisplayClass12_0`1.<ExecuteAsync>b__0()
at Microsoft.EntityFrameworkCore.Query.Internal.CompiledQueryCache.GetOrAddQuery[TResult](Object cacheKey, Func`1 compiler)
at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.ExecuteAsync[TResult](Expression query, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryProvider.ExecuteAsync[TResult](Expression expression, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable`1.GetAsyncEnumerator(CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.IncludableQueryable`2.GetAsyncEnumerator(CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.LoadAsync[TSource](IQueryable`1 source, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Internal.ManyToManyLoader`2.LoadAsync(InternalEntityEntry entry, CancellationToken cancellationToken)
at Program.<Main>$(String[] args) in /tmp/Repro/Program.cs:line 33
at Program.<Main>(String[] args)
The error is the one expected for a query with multiple .Include(x => x.Foos).Include(x => x.Bars), which leads to "Cartesian Explosion". The solution in those cases is to use AsSplitQuery(). I don't see how that applies in this case.
When I remove the owned type collection, it works as expected.
Is this a bug in my code or in EF?
(I know I can ignore the warning, but I don't want to do that. It is useful for detecting ACTUAL cartesian explosion issues elsewhere in my code. I think this is a false positive, but need confirmation.)
UPDATE
Reported on repo as possible bug.
Assuming this is an EF bug, is there a workaround where I can continue to rely on the "MultipleCollectionIncludeWarning" to detect cartesian explosion errors in my code, but disable it for this particular case?