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.72k stars 3.17k forks source link

Migration Issue with Inherited Contexts #13466

Closed danjohnso closed 2 years ago

danjohnso commented 6 years ago

public abstract class BaseContext : DbContext {} public class TenantAContext : BaseContext {} public class TenantBContext : BaseContext {}

BaseContext currently has an object called BasicMaterialDefinitionMasterModelCustomerInformation. TenantBContext no longer needs that table, so I removed the DbSet from BaseContext and moved it to TenantAContext. Adding a migration for TenantAContext worked fine. When I went to add a migration for TenantBContext, I get the exception below. TenantBContext has no references to that object anymore, but any attempt to do migration work or instantiate the context causes an exception like below on the IModel. I am assuming something is cached in the ModelValidator from when TenantBContext did have a reference via the BaseContext, but I am not sure how to work around this...feels like a bug but wanted to see if there was a workaround in the meantime.

Exception message: The entity type 'BasicMaterialDefinitionMasterModelCustomerInformation' requires a primary key to be defined
Stack trace:
System.InvalidOperationException: The entity type 'BasicMaterialDefinitionMasterModelCustomerInformation' requires a primary key to be defined.
   at Microsoft.EntityFrameworkCore.Infrastructure.ModelValidator.ValidateNonNullPrimaryKeys(IModel model)
   at Microsoft.EntityFrameworkCore.Infrastructure.ModelValidator.Validate(IModel model)
   at Microsoft.EntityFrameworkCore.Infrastructure.RelationalModelValidator.Validate(IModel model)
   at Microsoft.EntityFrameworkCore.Internal.SqlServerModelValidator.Validate(IModel model)
   at Microsoft.EntityFrameworkCore.Infrastructure.ModelSource.CreateModel(DbContext context, IConventionSetBuilder conventionSetBuilder, IModelValidator validator)
   at Microsoft.EntityFrameworkCore.Infrastructure.ModelSource.<>c__DisplayClass5_0.<GetModel>b__1()
   at System.Lazy`1.ViaFactory(LazyThreadSafetyMode mode)
   at System.Lazy`1.ExecutionAndPublication(LazyHelper executionAndPublication, Boolean useDefaultConstructor)
   at System.Lazy`1.CreateValue()
   at Microsoft.EntityFrameworkCore.Infrastructure.ModelSource.GetModel(DbContext context, IConventionSetBuilder conventionSetBuilder, IModelValidator validator)
   at Microsoft.EntityFrameworkCore.Internal.DbContextServices.CreateModel()
   at Microsoft.EntityFrameworkCore.Internal.DbContextServices.get_Model()
   at Microsoft.EntityFrameworkCore.Infrastructure.EntityFrameworkServicesBuilder.<>c.<TryAddCoreServices>b__7_1(IServiceProvider p)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitFactory(FactoryCallSite factoryCallSite, ServiceProviderEngineScope scope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(IServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScoped(ScopedCallSite scopedCallSite, ServiceProviderEngineScope scope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(IServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, ServiceProviderEngineScope scope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(IServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScoped(ScopedCallSite scopedCallSite, ServiceProviderEngineScope scope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(IServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.DynamicServiceProviderEngine.<>c__DisplayClass1_0.<RealizeService>b__0(ServiceProviderEngineScope scope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngine.GetService(Type serviceType, 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_InternalServiceProvider()
   at Microsoft.EntityFrameworkCore.DbContext.Microsoft.EntityFrameworkCore.Infrastructure.IInfrastructure<System.IServiceProvider>.get_Instance()
   at Microsoft.EntityFrameworkCore.Internal.InternalAccessorExtensions.GetService[TService](IInfrastructure`1 accessor)
   at Microsoft.EntityFrameworkCore.Infrastructure.AccessorExtensions.GetService[TService](IInfrastructure`1 accessor)
   at Microsoft.EntityFrameworkCore.Design.Internal.DbContextOperations.CreateContext(Func`1 factory)
   at Microsoft.EntityFrameworkCore.Design.Internal.DbContextOperations.CreateContext(String contextType)
   at Microsoft.EntityFrameworkCore.Design.Internal.MigrationsOperations.AddMigration(String name, String outputDir, String contextType)
   at Microsoft.EntityFrameworkCore.Design.OperationExecutor.AddMigrationImpl(String name, String outputDir, String contextType)
   at Microsoft.EntityFrameworkCore.Design.OperationExecutor.AddMigration.<>c__DisplayClass0_1.<.ctor>b__0()
   at Microsoft.EntityFrameworkCore.Design.OperationExecutor.OperationBase.<>c__DisplayClass3_0`1.<Execute>b__0()
   at Microsoft.EntityFrameworkCore.Design.OperationExecutor.OperationBase.Execute(Action action)

Further technical details

EF Core version: 2.1.3 Database Provider: Microsoft.EntityFrameworkCore.SqlServer Operating system: Windows 10 IDE: Visual Studio 2017 15.8.5

ajcvickers commented 6 years ago

@danjohnso Can you post a runnable project/solution or complete code listing that demonstrates the behavior you are seeing.

danjohnso commented 6 years ago

@ajcvickers Guess not. Tried the same scenario in a new test project but the migration seems to work ok. Not sure how I can extract this to something reproducible since it is a project that has been around a while ...any way I can track where it is picking up this reference in the model? Verbose mode on the migration generation doesnm't provide anything useful. Its probably 20 different tables we removed from the BaseContext and commenting them out individually just opens up for the next one in line to cause an issue so its not isolated to a single entity.

ajcvickers commented 6 years ago

@danjohnso My best guess is that there is a reference to the type somewhere in the model so it is still being pulled in. For example, a navigation property that points to the type. If it's something else, then I don't have any ideas as to what that might be.

danjohnso commented 6 years ago

@ajcvickers Spent a couple hours looking for navigation properties and possible indirect references and I couldn't find anything. I have my actual context setup pulled out of the main application and the issue still happens when trying to add the migration on an empty ConsoleApp. Do you have a way I can share without posting it publicly?

ajcvickers commented 6 years ago

@danjohnso You can email it to me. At "microsoft.com", I am "avickers".

danjohnso commented 6 years ago

Sent the repro Wednesday night, let me know if you need anything else. Spent some more time looking at dependencies and still haven't found anything that would indicate there is a reference to those classes in the TenantBContext

ajcvickers commented 6 years ago

@danjohnso Got the repro, thanks. I haven't had time to look at it yet.

ajcvickers commented 6 years ago

@danjohnso One connection is coming from Task which is mapped with both it's sub-types here:

entity.HasDiscriminator<int>("Discriminator")
        .HasValue<P****.Task>(1)
        .HasValue<G***.Task>(2);

This is one way that the reference to both tenants gets included in the model, which then results in further dependencies coming in, which ultimately brings in BasicMaterialDefinitionMasterModelCustomerInformation. I stopped here, so there may also be other references.

danjohnso commented 6 years ago

Thanks for clarifying the behavior, didn't think using TPH would trigger it being included in the base model...guess I will need to wait for TPC to split these databases up.

EDIT: In case anyone else comes across this in search with a similar scenario, I did find I can force remove the objects from the model by specifying which objects I didn't want in OnModelCreating of the derived context. Little bit of a pain, but this keeps the tables out of the database from what feels like an implicit reference.

modelBuilder.Ignore<BasicMaterialDefinitionMasterModelCustomerInformation>();