Exception thrown with split tables: System.InvalidOperationException: 'Sequence contains more than one element'
With EF Core 8.0.7 and 9.0.0, if I have an entity split to more than one table I get an error when trying to check for null. The error appears in RelationalSqlTranslatingExpressionVisitor method TryRewriteEntityEquality, due to a call to SingleOrDefault().
Code to reproduce (I am using SqlServer but it appears also with Sqlite):
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace EfCoreBug1Demo;
class Program
{
static void Main()
{
var connection = new SqliteConnection("Data Source=:memory:");
connection.Open();
var options = new DbContextOptionsBuilder<TestContext>()
.UseSqlite(connection)
.Options;
using (var dbContext = new TestContext(options))
{
dbContext.Database.EnsureDeleted();
dbContext.Database.EnsureCreated();
}
using (var dbContext = new TestContext(options))
{
var query = dbContext.Set<InvoiceLine>().AsNoTracking()
.Select(x => new
{
x.Id,
// throws exception
Name = x.Invoice.Creator != null ? x.Invoice.Creator!.FullName : "",
// works
//Name = x.Invoice.CreatorId != null ? x.Invoice.Creator!.FullName : ""
});
var result = query.ToArray();
}
}
}
internal class TestContext : DbContext
{
public TestContext(DbContextOptions options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
var assembly = typeof(Program).Assembly;
modelBuilder.ApplyConfigurationsFromAssembly(assembly);
}
}
internal class Person
{
public int Id { get; set; }
public string FullName { get; set; } = "";
public byte[] Photo { get; set; } = [];
public int PhotoVersion { get; set; } = 1;
}
internal class Invoice
{
public int Id { get; set; }
public int CustomerId { get; set; }
public required Person Customer { get; set; }
public int? CreatorId { get; set; }
public Person? Creator { get; set; }
}
internal class InvoiceLine
{
public int Id { get; set; }
public int InvoiceId { get; set; }
public required Invoice Invoice { get; set; }
}
internal class InvoiceConfig: IEntityTypeConfiguration<Invoice>
{
public void Configure(EntityTypeBuilder<Invoice> builder)
{
builder.HasOne(e => e.Customer).WithMany().HasForeignKey(e => e.CustomerId).OnDelete(DeleteBehavior.Restrict);
builder.HasOne(e => e.Creator).WithMany().HasForeignKey(e => e.CreatorId).OnDelete(DeleteBehavior.SetNull);
builder.HasKey(e => e.Id);
builder.ToTable("Invoices");
}
}
internal class InvoiceLineConfig : IEntityTypeConfiguration<InvoiceLine>
{
public void Configure(EntityTypeBuilder<InvoiceLine> builder)
{
builder.HasOne(e => e.Invoice).WithMany().HasForeignKey(e => e.InvoiceId).OnDelete(DeleteBehavior.Cascade);
builder.HasKey(e => e.Id);
builder.ToTable("InvoiceLines");
}
}
internal class PersonConfig : IEntityTypeConfiguration<Person>
{
public void Configure(EntityTypeBuilder<Person> builder)
{
builder.Property(e => e.FullName).IsRequired().HasMaxLength(132);
builder.ToTable("Persons")
.SplitToTable("PersonsPhotos", t =>
{
t.Property(e => e.Id).HasColumnName("PersonId");
t.Property(e => e.Photo);
t.Property(e => e.PhotoVersion);
});
builder.HasKey(e => e.Id);
builder.ToTable("Persons");
}
}
Stack trace:
System.InvalidOperationException: Sequence contains more than one element
at System.Linq.ThrowHelper.ThrowMoreThanOneElementException()
at System.Linq.Enumerable.TryGetSingle[TSource](IEnumerable`1 source, Boolean& found)
at System.Linq.Enumerable.SingleOrDefault[TSource](IEnumerable`1 source)
at Microsoft.EntityFrameworkCore.Query.RelationalSqlTranslatingExpressionVisitor.<>c__DisplayClass57_0.<TryRewriteStructuralTypeEquality>g__TryRewriteEntityEquality|0(Expression& result)
at Microsoft.EntityFrameworkCore.Query.RelationalSqlTranslatingExpressionVisitor.TryRewriteStructuralTypeEquality(ExpressionType nodeType, Expression left, Expression right, Boolean equalsMethod, Expression& result)
at Microsoft.EntityFrameworkCore.Query.RelationalSqlTranslatingExpressionVisitor.VisitBinary(BinaryExpression binaryExpression)
at Microsoft.EntityFrameworkCore.Sqlite.Query.Internal.SqliteSqlTranslatingExpressionVisitor.VisitBinary(BinaryExpression binaryExpression)
at Microsoft.EntityFrameworkCore.Query.RelationalSqlTranslatingExpressionVisitor.VisitConditional(ConditionalExpression conditionalExpression)
at Microsoft.EntityFrameworkCore.Query.RelationalSqlTranslatingExpressionVisitor.TranslateInternal(Expression expression, Boolean applyDefaultTypeMapping)
at Microsoft.EntityFrameworkCore.Query.RelationalSqlTranslatingExpressionVisitor.TranslateProjection(Expression expression, Boolean applyDefaultTypeMapping)
at Microsoft.EntityFrameworkCore.Query.Internal.RelationalProjectionBindingExpressionVisitor.Visit(Expression expression)
at Microsoft.EntityFrameworkCore.Query.Internal.RelationalProjectionBindingExpressionVisitor.VisitNew(NewExpression newExpression)
at Microsoft.EntityFrameworkCore.Query.Internal.RelationalProjectionBindingExpressionVisitor.Visit(Expression expression)
at Microsoft.EntityFrameworkCore.Query.Internal.RelationalProjectionBindingExpressionVisitor.Translate(SelectExpression selectExpression, Expression expression)
at Microsoft.EntityFrameworkCore.Query.RelationalQueryableMethodTranslatingExpressionVisitor.TranslateSelect(ShapedQueryExpression source, LambdaExpression selector)
at Microsoft.EntityFrameworkCore.Query.QueryableMethodTranslatingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
at Microsoft.EntityFrameworkCore.Query.RelationalQueryableMethodTranslatingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
at Microsoft.EntityFrameworkCore.Query.QueryableMethodTranslatingExpressionVisitor.Translate(Expression expression)
at Microsoft.EntityFrameworkCore.Query.QueryCompilationContext.CreateQueryExecutorExpression[TResult](Expression query)
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__DisplayClass11_0`1.<ExecuteCore>b__0()
at Microsoft.EntityFrameworkCore.Query.Internal.CompiledQueryCache.GetOrAddQuery[TResult](Object cacheKey, Func`1 compiler)
at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.ExecuteCore[TResult](Expression query, Boolean async, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.Execute[TResult](Expression query)
at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryProvider.Execute[TResult](Expression expression)
at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable`1.GetEnumerator()
at System.Collections.Generic.LargeArrayBuilder`1.AddRange(IEnumerable`1 items)
at System.Collections.Generic.EnumerableHelpers.ToArray[T](IEnumerable`1 source)
at EfCoreBug1Demo.Program.Main() in d:\EfCoreBug1Demo\Program.cs:line 33
EF Core version: 8.0.7 / 9.0.0 Database provider: Microsoft.EntityFrameworkCore.SqlServer / Microsoft.EntityFrameworkCore.Sqlite Target framework: (e.g. .NET 8.0) Operating system: Windows 10 IDE: Visual Studio 2022 17.11.4
Guys, are there any updates and timelines for this problem?
@NikitaReut7 it can take us some time to get around to investigating a problem - especially during the December holiday season. You'll have to be patient for a bit more.
@roji I see, thanks
I can reproduce this on main. Problem is that when we try to see if we are comparing entity with optional dependent, we assume the entity will always be mapped to one table (outside TPC which we filter out).
I also seems to encounter this issue. Table splitting seemed too good and easy until I got this on a query further down my application.
Until the task to review Single in the pipeline is completed in NET11, what is the recommendation to watch out for to avoid hitting this? Not to use SplitToTable() at all? Avoid null checking?