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.8k stars 3.2k forks source link

Open generic entity types are broken #35169

Closed ZzZombo closed 17 hours ago

ZzZombo commented 22 hours ago

File a bug

Open generic types do not appear to be processed correctly. Attempting to check the model for changes/add a new migration ends with an error.

Include your code

using System.ComponentModel.DataAnnotations;

public class BaseEntity<T>
{
    [Key]
    public int Id { get; set; }

    public T? Foo { get; set; }
}
public class DerivedEntity<T>: BaseEntity<T>;

using Microsoft.EntityFrameworkCore;

public class Context : DbContext
{
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        _ = optionsBuilder.UseSqlite("Filename=database.db");
    }
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        _ = modelBuilder.Entity(typeof(BaseEntity<>), static (builder) => { });
    }
}

It crashes even with you call .Entity() with only the type argument, the lambda is just to simulate closer our actual code.

Include stack traces

Microsoft.EntityFrameworkCore.Design.OperationException: Unable to create a 'DbContext' of type 'Context'. The exception 'The string argument 'name' cannot be empty.' was thrown while attempting to create an instance. For the different patterns supported at design time, see https://go.microsoft.com/fwlink/?linkid=851728
 ---> System.ArgumentException: The string argument 'name' cannot be empty.
   at Microsoft.EntityFrameworkCore.Utilities.Check.NotEmpty(String value, String parameterName)
   at Microsoft.EntityFrameworkCore.Metadata.Internal.Model.FindIgnoredConfigurationSource(String name)
   at Microsoft.EntityFrameworkCore.Metadata.Internal.InternalModelBuilder.IsIgnored(TypeIdentity& type, Nullable`1 configurationSource)
   at Microsoft.EntityFrameworkCore.Metadata.Internal.InternalModelBuilder.CanHaveEntity(TypeIdentity& type, ConfigurationSource configurationSource, Nullable`1 shouldBeOwned, Boolean shouldThrow)
   at Microsoft.EntityFrameworkCore.Metadata.Internal.InternalModelBuilder.Entity(TypeIdentity& type, ConfigurationSource configurationSource, Nullable`1 shouldBeOwned)
   at Microsoft.EntityFrameworkCore.Metadata.Internal.InternalModelBuilder.Entity(Type type, ConfigurationSource configurationSource, Nullable`1 shouldBeOwned)
   at Microsoft.EntityFrameworkCore.Metadata.Internal.InternalEntityTypeBuilder.GetTargetEntityTypeBuilder(TypeIdentity targetEntityType, MemberIdentity navigation, Nullable`1 configurationSource, Nullable`1 targetShouldBeOwned)
   at Microsoft.EntityFrameworkCore.Metadata.Internal.InternalEntityTypeBuilder.GetTargetEntityTypeBuilder(Type targetClrType, MemberInfo navigationInfo, Nullable`1 configurationSource, Nullable`1 targetShouldBeOwned)
   at Microsoft.EntityFrameworkCore.Metadata.Internal.InternalEntityTypeBuilder.Microsoft.EntityFrameworkCore.Metadata.Builders.IConventionEntityTypeBuilder.GetTargetEntityTypeBuilder(Type targetClrType, MemberInfo navigationInfo, Boolean createIfMissing, Nullable`1 targetShouldBeOwned, Boolean fromDataAnnotation)
   at Microsoft.EntityFrameworkCore.Metadata.Conventions.RelationshipDiscoveryConvention.TryGetTargetEntityTypeBuilder(IConventionEntityTypeBuilder entityTypeBuilder, Type targetClrType, MemberInfo navigationMemberInfo, Nullable`1 shouldBeOwned, Boolean shouldCreate)
   at Microsoft.EntityFrameworkCore.Metadata.Conventions.RelationshipDiscoveryConvention.FindRelationshipCandidates(IConventionEntityTypeBuilder entityTypeBuilder, HashSet`1 otherInverseCandidateTypes)
   at Microsoft.EntityFrameworkCore.Metadata.Conventions.RelationshipDiscoveryConvention.DiscoverRelationships(IConventionEntityTypeBuilder entityTypeBuilder, IConventionContext context, HashSet`1 otherInverseCandidateTypes)
   at Microsoft.EntityFrameworkCore.Metadata.Conventions.RelationshipDiscoveryConvention.ProcessEntityTypeAdded(IConventionEntityTypeBuilder entityTypeBuilder, IConventionContext`1 context)
   at Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal.ConventionDispatcher.ImmediateConventionScope.OnEntityTypeAdded(IConventionEntityTypeBuilder entityTypeBuilder)
   at Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal.ConventionDispatcher.OnEntityTypeAddedNode.Run(ConventionDispatcher dispatcher)
   at Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal.ConventionDispatcher.DelayedConventionScope.Run(ConventionDispatcher dispatcher)
   at Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal.ConventionDispatcher.ConventionBatch.Run()
   at Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal.ConventionDispatcher.ConventionBatch.Dispose()
   at Microsoft.EntityFrameworkCore.Metadata.Internal.InternalModelBuilder.Entity(TypeIdentity& type, ConfigurationSource configurationSource, Nullable`1 shouldBeOwned)
   at Microsoft.EntityFrameworkCore.Metadata.Internal.InternalModelBuilder.Entity(Type type, ConfigurationSource configurationSource, Nullable`1 shouldBeOwned)
   at Microsoft.EntityFrameworkCore.ModelBuilder.Entity(Type type)
   at EMD.Shared.DB.Main.Context.OnModelCreating(ModelBuilder modelBuilder)
   at Microsoft.EntityFrameworkCore.Infrastructure.ModelCustomizer.Customize(ModelBuilder modelBuilder, DbContext context)
   at Microsoft.EntityFrameworkCore.Infrastructure.ModelSource.CreateModel(DbContext context, IConventionSetBuilder conventionSetBuilder, ModelDependencies modelDependencies)
   at Microsoft.EntityFrameworkCore.Infrastructure.ModelSource.GetModel(DbContext context, ModelCreationDependencies modelCreationDependencies, Boolean designTime)
   at Microsoft.EntityFrameworkCore.Internal.DbContextServices.CreateModel(Boolean designTime)
   at Microsoft.EntityFrameworkCore.Internal.DbContextServices.get_Model()
   at Microsoft.EntityFrameworkCore.Infrastructure.EntityFrameworkServicesBuilder.<>c.<TryAddCoreServices>b__8_4(IServiceProvider p)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitFactory(FactoryCallSite factoryCallSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite callSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite callSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite callSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite callSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite callSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite callSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.Resolve(ServiceCallSite callSite, ServiceProviderEngineScope scope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.DynamicServiceProviderEngine.<>c__DisplayClass2_0.<RealizeService>b__0(ServiceProviderEngineScope scope)
   at Microsoft.Extensions.DependencyInjection.ServiceProvider.GetService(ServiceIdentifier serviceIdentifier, ServiceProviderEngineScope serviceProviderEngineScope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope.GetService(Type serviceType)
   at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)
   at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService[T](IServiceProvider provider)
   at Microsoft.EntityFrameworkCore.DbContext.get_DbContextDependencies()
   at Microsoft.EntityFrameworkCore.DbContext.get_ContextServices()
   at Microsoft.EntityFrameworkCore.DbContext.get_InternalServiceProvider()
   at Microsoft.EntityFrameworkCore.DbContext.Microsoft.EntityFrameworkCore.Infrastructure.IInfrastructure<System.IServiceProvider>.get_Instance()
   at Microsoft.EntityFrameworkCore.Infrastructure.Internal.InfrastructureExtensions.GetService(IInfrastructure`1 accessor, Type serviceType)
   at Microsoft.EntityFrameworkCore.Infrastructure.Internal.InfrastructureExtensions.GetService[TService](IInfrastructure`1 accessor)
   at Microsoft.EntityFrameworkCore.Infrastructure.AccessorExtensions.GetService[TService](IInfrastructure`1 accessor)
   at Microsoft.EntityFrameworkCore.Design.Internal.DbContextOperations.CreateContext(String contextType)
   --- End of inner exception stack trace ---
   at Microsoft.EntityFrameworkCore.Design.Internal.DbContextOperations.CreateContext(String contextType)
   at Microsoft.EntityFrameworkCore.Design.Internal.MigrationsOperations.HasPendingModelChanges(String contextType)
   at Microsoft.EntityFrameworkCore.Design.OperationExecutor.HasPendingModelChangesImpl(String contextType)
   at Microsoft.EntityFrameworkCore.Design.OperationExecutor.HasPendingModelChanges.<>c__DisplayClass0_0.<.ctor>b__0()
   at Microsoft.EntityFrameworkCore.Design.OperationExecutor.OperationBase.Execute(Action action)
Unable to create a 'DbContext' of type 'Context'. The exception 'The string argument 'name' cannot be empty.' was thrown while attempting to create an instance. For the different patterns supported at design time, see https://go.microsoft.com/fwlink/?linkid=851728

Include verbose output

> dotnet-ef.exe migrations has-pending-model-changes -c Context -v
Using project 'G:\home\dev\csharp\EMD_Application\src\Program.csproj'.
Using startup project 'G:\home\dev\csharp\EMD_Application\src\Program.csproj'.
Writing 'G:\home\dev\csharp\EMD_Application\src\obj\Program.csproj.EntityFrameworkCore.targets'...
dotnet msbuild /target:GetEFProjectMetadata /property:EFProjectMetadataFile=G:\home\windows-profile\AppData\Local\Temp\tmpu32hzv.tmp /verbosity:quiet /nologo G:\home\dev\csharp\EMD_Application\src\Program.csproj
Writing 'G:\home\dev\csharp\EMD_Application\src\obj\Program.csproj.EntityFrameworkCore.targets'...
dotnet msbuild /target:GetEFProjectMetadata /property:EFProjectMetadataFile=G:\home\windows-profile\AppData\Local\Temp\tmptpoeis.tmp /verbosity:quiet /nologo G:\home\dev\csharp\EMD_Application\src\Program.csproj
Build started...
dotnet build G:\home\dev\csharp\EMD_Application\src\Program.csproj /verbosity:quiet /nologo /p:PublishAot=false

Build succeeded.
    0 Warning(s)
    0 Error(s)

Time Elapsed 00:00:02.75
Build succeeded.
dotnet exec --depsfile G:\home\dev\csharp\EMD_Application\build\bin\Program\debug\Program.deps.json --additionalprobingpath G:\home\windows-profile\.nuget\packages --additionalprobingpath "C:\Program Files (x86)\Microsoft Visual Studio\Shared\NuGetPackages" --additionalprobingpath "C:\Program Files (x86)\Progress\ToolboxNuGetPackages" --runtimeconfig G:\home\dev\csharp\EMD_Application\build\bin\Program\debug\Program.runtimeconfig.json G:\home\windows-profile\.dotnet\tools\.store\dotnet-ef\8.0.10\dotnet-ef\8.0.10\tools\net8.0\any\tools\netcoreapp2.0\any\ef.dll migrations has-pending-model-changes -c Context --assembly G:\home\dev\csharp\EMD_Application\build\bin\Program\debug\Program.dll --project G:\home\dev\csharp\EMD_Application\src\Program.csproj --startup-assembly G:\home\dev\csharp\EMD_Application\build\bin\Program\debug\Program.dll --startup-project G:\home\dev\csharp\EMD_Application\src\Program.csproj --project-dir G:\home\dev\csharp\EMD_Application\src\ --root-namespace RITM.EMD --language C# --framework net8.0-windows --nullable --working-dir G:\home\dev\csharp\EMD_Application\src --verbose
Using assembly 'Program'.
Using startup assembly 'Program'.
Using application base 'G:\home\dev\csharp\EMD_Application\build\bin\Program\debug'.
Using working directory 'G:\home\dev\csharp\EMD_Application\src'.
Using root namespace 'RITM.EMD'.
Using project directory 'G:\home\dev\csharp\EMD_Application\src\'.
Remaining arguments: .
Using configuration file 'G:\home\dev\csharp\EMD_Application\build\bin\Program\debug\Program.dll.config'.
The Entity Framework tools version '8.0.10' is older than that of the runtime '8.0.11'. Update the tools for the latest features and bug fixes. See https://aka.ms/AAc1fbw for more information.
Finding DbContext classes...
Finding IDesignTimeDbContextFactory implementations...
Finding application service provider in assembly 'Program'...
Finding Microsoft.Extensions.Hosting service provider...
Using environment 'Development'.
Using application service provider from Microsoft.Extensions.Hosting.
Found DbContext 'Context'.
Finding DbContext classes in the project...
dbug: Microsoft.EntityFrameworkCore.Infrastructure[10401]
      An 'IServiceProvider' was created for internal use by Entity Framework.
Using context 'Context'.

Include provider and version information

EF Core version: 8.0.11 Database provider: (e.g. Npgsql.EntityFrameworkCore.PostgreSQL) Target framework: (e.g. .NET 8.0-windows) Operating system: Microsoft Windows [Version 10.0.19045.5011] IDE: (e.g. Visual Studio 2022 17.11.5)

roji commented 20 hours ago

@ZzZombo what's your exact expectation here with open generic entity types? For example, what should actually get created in the database table here?

ZzZombo commented 20 hours ago

Each closed generic entity type, like BaseEntity<string>, add an entity to the TPH hierarchy in the model with BaseEntity<> as the base type of them.

roji commented 19 hours ago

But that's not what your model configuration is doing:

_ = modelBuilder.Entity(typeof(BaseEntity<>), static (builder) => { });

Here you're asking EF to map an open generic type as an entity type, which I don't think makes sense. Try configuring entity types for the closed subtypes.

ZzZombo commented 19 hours ago

Did you not read that it doesn't work and crashes? Rendering any subsequent model configuration moot. So it was omitted from the provided code.

roji commented 18 hours ago

Yes, I read that it doesn't work and crashes - but I think you're misunderstanding what I'm trying to say; so here's a code sample which works:

await using var context = new BlogContext();
await context.Database.EnsureDeletedAsync();
await context.Database.EnsureCreatedAsync();

public class BlogContext : DbContext
{
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        => optionsBuilder
            .UseSqlServer("Server=localhost;Database=test;User=SA;Password=Abcd5678;Connect Timeout=60;ConnectRetryCount=0;Encrypt=false")
            .LogTo(Console.WriteLine, LogLevel.Information)
            .EnableSensitiveDataLogging();

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        // _ = modelBuilder.Entity(typeof(BaseEntity<>), static (builder) => { });

        modelBuilder.Entity<BaseEntity<string>>();
        modelBuilder.Entity<BaseEntity<int>>();
    }
}

public class BaseEntity<T>
{
    [Key]
    public int Id { get; set; }
    public T? Foo { get; set; }
}

Note that there's no configuring of the open generic base type as an entity type, since I'm not sure what that would mean. Is the above code not sufficient for you in some way?

ZzZombo commented 17 hours ago

I suppose that would work.