dotnet / efcore

EF Core is a modern object-database mapper for .NET. It supports LINQ queries, change tracking, updates, and schema migrations.
https://docs.microsoft.com/ef/
MIT License
13.61k stars 3.14k forks source link

System.ArgumentException on List<T> with OwnsOne ToJson #31760

Closed nickshepherd1983 closed 11 months ago

nickshepherd1983 commented 11 months ago

When running the .NET 8 console application below I run into the following exception (thrown at line 10). This exception is thrown when using " 8.0.0-rc.1.23419.6" but works as expected with "7.0.11"

System.ArgumentException
  HResult=0x80070057
  Message=GenericArguments[1], 'System.Collections.Generic.IEnumerable`1[CaseRecordStaff]', on 'TResult MaterializeJsonEntityCollection[TEntity,TResult](Microsoft.EntityFrameworkCore.Query.QueryContext, System.Object[], Microsoft.EntityFrameworkCore.Storage.Json.JsonReaderData, Microsoft.EntityFrameworkCore.Metadata.INavigationBase, System.Func`4[Microsoft.EntityFrameworkCore.Query.QueryContext,System.Object[],Microsoft.EntityFrameworkCore.Storage.Json.JsonReaderData,TEntity])' violates the constraint of type 'TResult'.
  Source=System.Private.CoreLib
  StackTrace:
   at System.RuntimeType.ValidateGenericArguments(MemberInfo definition, RuntimeType[] genericArguments, Exception e)
   at System.Reflection.RuntimeMethodInfo.MakeGenericMethod(Type[] methodInstantiation)
   at Microsoft.EntityFrameworkCore.Query.RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.CreateJsonShapers(IEntityType entityType, Boolean nullable, ParameterExpression jsonReaderDataParameter, ParameterExpression keyValuesParameter, Expression parentEntityExpression, INavigation navigation)
   at Microsoft.EntityFrameworkCore.Query.RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.CreateJsonShapers(IEntityType entityType, Boolean nullable, ParameterExpression jsonReaderDataParameter, ParameterExpression keyValuesParameter, Expression parentEntityExpression, INavigation navigation)
   at Microsoft.EntityFrameworkCore.Query.RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.VisitExtension(Expression extensionExpression)
   at Microsoft.EntityFrameworkCore.Query.RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.ProcessShaper(Expression shaperExpression, RelationalCommandCache& relationalCommandCache, IReadOnlyList`1& readerColumns, LambdaExpression& relatedDataLoaders, Int32& collectionId)
   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.EntityFrameworkQueryableExtensions.ExecuteAsync[TSource,TResult](MethodInfo operatorMethodInfo, IQueryable`1 source, Expression expression, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ExecuteAsync[TSource,TResult](MethodInfo operatorMethodInfo, IQueryable`1 source, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.FirstOrDefaultAsync[TSource](IQueryable`1 source, CancellationToken cancellationToken)
   at Program.<<Main>$>d__0.MoveNext() in C:\Users\Nick Shepherd\source\repos\EfCoreJson\EfCoreJson\Program.cs:line 10

  This exception was originally thrown at this call stack:
    [External Code]

Inner Exception 1:
VerificationException: Method Microsoft.EntityFrameworkCore.Query.RelationalShapedQueryCompilingExpressionVisitor+ShaperProcessingExpressionVisitor.MaterializeJsonEntityCollection: type argument 'System.Collections.Generic.IEnumerable`1[CaseRecordStaff]' violates the constraint of type parameter 'TResult'.

If i change


class CaseRecordLocation
{
    private readonly List<CaseRecordStaff> _staff = new();
    public IEnumerable<CaseRecordStaff> Staff => _staff.AsReadOnly();
}

to this


class CaseRecordLocation
{
    public ICollection<CaseRecordStaff> Staff { get; set; }
}

The code works as expected.

Demo Application:

var dbContextOptionsBuilder = new DbContextOptionsBuilder<DemoDbContext>()
  .UseSqlServer("Server=(localdb)\\mssqllocaldb;Database=DemoDbContext;Trusted_Connection=True;")
  .LogTo(Console.WriteLine, Microsoft.Extensions.Logging.LogLevel.Warning);

var demoDbContext = new DemoDbContext(dbContextOptionsBuilder.Options);
var caseRecord = new CaseRecord();
demoDbContext.CaseRecords.Add(caseRecord);
await demoDbContext.SaveChangesAsync();

caseRecord = await demoDbContext.CaseRecords.Where(cr => cr.Id == 1).FirstOrDefaultAsync();

class CaseRecord
{
    public int Id { get; set; }
    public CaseRecordLocation? Clinic { get; private set; }
}

class CaseRecordLocation
{
    private readonly List<CaseRecordStaff> _staff = new();
    public IEnumerable<CaseRecordStaff> Staff => _staff.AsReadOnly();
}

class CaseRecordStaff
{
    public string Name { get; set; } = default!;
}

class CaseRecordConfiguration : IEntityTypeConfiguration<CaseRecord>
{
    public void Configure(EntityTypeBuilder<CaseRecord> builder)
    {
        _ = builder.Property(r => r.Id)
          .UseIdentityColumn();

        _ = builder.OwnsOne(cr => cr.Clinic, ownedNavigationBuilder =>
        {
            _ = ownedNavigationBuilder.ToJson();
            _ = ownedNavigationBuilder.OwnsMany(
              tc => tc.Staff);
        });
    }
}

class DemoDbContext : DbContext
{
    public DemoDbContext(DbContextOptions options) : base(options) { }

    public DbSet<CaseRecord> CaseRecords => Set<CaseRecord>();

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        _ = modelBuilder.HasDefaultSchema("mx");
        base.OnModelCreating(modelBuilder);
        _ = modelBuilder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly());
    }
}

class DemoDbContextFactory : IDesignTimeDbContextFactory<DemoDbContext>
{
    DemoDbContext IDesignTimeDbContextFactory<DemoDbContext>.CreateDbContext(string[] args) => BuildReferralsDbContext("DemoDbContext", "sa", "SuperPassword1234!");

    public static DemoDbContext BuildReferralsDbContext(string database, string username, string password, bool logging = false)
    {
        var connectionString = "Server=(localdb)\\mssqllocaldb;Database=DemoDbContext;Trusted_Connection=True;";
        var optionsBuilder = new DbContextOptionsBuilder<DemoDbContext>();
        _ = optionsBuilder.UseSqlServer(connectionString, x => x.MigrationsHistoryTable("MigrationsHistory", "mx"));
        return new(optionsBuilder.Options);
    }
}

Include provider and version information

EF Core version: 8.0.0-rc.1.23419.6 Database provider: Microsoft.EntityFrameworkCore.SqlServer Target framework: .NET 8.0 Operating system: Windows 11 Pro IDE: Visual Studio 2022 Version 17.8.0 Preview 2.0

ajcvickers commented 11 months ago

Verified still repros on latest daily.

maumar commented 11 months ago

MaterializeJsonEntityCollection has type constraint so that i can use ICollection.Add method. We could/should use collection accessor AddStandalone instead

nickshepherd1983 commented 11 months ago

@maumar thank you for getting this issue resolved so promptly.

Cheers, Nick