简体   繁体   中英

EF Core Migration not working when using `ApplyConfiguration`

I have the following two classes in my model:

public class MyState
{
    private HashSet<MyTransition> _myTransitions;
    private HashSet<MyTransition> _usedBy;
    public MyState()
    {
        _myTransitions = new HashSet<MyTransition>();
        _usedBy = new HashSet<MyTransition>();
    }

    public Guid Id { get; set; }
    public IEnumerable<MyTransition> MyTransitions  => _myTransitions?.ToList();
    public IEnumerable<MyTransition> UsedBy => _usedBy?.ToList();
}

public class MyTransition
{
    public Guid Id { get; set; }

    public Guid ParentId { get; set; }
    public MyState Parent { get; set; }

    public Guid StateId { get; set; }
    public MyState State { get; set; }
}

When I create my DbContext like this, the add-migration command works:

public class ApplicationDbContext : DbContext
{
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
        : base(options)
    {

    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        modelBuilder.Entity<MyTransition>()
            .HasOne(x => x.Parent)
            .WithMany(x => x.MyTransitions)
            .HasForeignKey(x => x.ParentId)
            .OnDelete(DeleteBehavior.Restrict);

        modelBuilder.Entity<MyTransition>()
            .HasOne(x => x.State)
            .WithMany(x => x.UsedBy)
            .HasForeignKey(x => x.StateId)
            .OnDelete(DeleteBehavior.Restrict);

        modelBuilder.Entity<MyState>().Metadata
            .FindNavigation(nameof(MyState.UsedBy))
            .SetPropertyAccessMode(PropertyAccessMode.Field);

        modelBuilder.Entity<MyState>().Metadata
            .FindNavigation(nameof(MyState.MyTransitions))
            .SetPropertyAccessMode(PropertyAccessMode.Field);
    }
}

But when I try to move the configuration to separated classes, like this, the add-migration command fails:

public class ApplicationDbContext : DbContext
{
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
        : base(options)
    {

    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        modelBuilder.ApplyConfiguration(new ItemStateConfig());
        modelBuilder.ApplyConfiguration(new TransitionConfig());
    }
}

public class TransitionConfig : IEntityTypeConfiguration<MyTransition>
{
    public void Configure(EntityTypeBuilder<MyTransition> entity)
    {
        entity.HasOne(x => x.Parent)
            .WithMany(x => x.MyTransitions)
            .HasForeignKey(x => x.ParentId)
            .OnDelete(DeleteBehavior.Restrict);

        entity.HasOne(x => x.State)
            .WithMany(x => x.UsedBy)
            .HasForeignKey(x => x.StateId)
            .OnDelete(DeleteBehavior.Restrict);
    }
}



public class ItemStateConfig : IEntityTypeConfiguration<MyState>
{
    public void Configure(EntityTypeBuilder<MyState> entity)
    {
        entity.Metadata
            .FindNavigation(nameof(MyState.UsedBy))
            .SetPropertyAccessMode(PropertyAccessMode.Field);

        entity.Metadata
            .FindNavigation(nameof(MyState.MyTransitions))
            .SetPropertyAccessMode(PropertyAccessMode.Field);
    }
}

The exception thrown is the following:

System.ArgumentNullException: Value cannot be null. (Parameter 'property') at Microsoft.EntityFrameworkCore.Utilities.Check.NotNull[T](T value, String parameterName) at Microsoft.EntityFrameworkCore.MutablePropertyBaseExtensions.SetPropertyAccessMode(IMutablePropertyBase property, Nullable 1 propertyAccessMode) at WebApplication5.Data.ItemStateConfig.Configure(EntityTypeBuilder 1 entity) in C:\\Users\\rbasn\\source\\repos\\WebApplication5\\WebApplication5\\Data\\ClassS.cs:line 29 at Microsoft.EntityFrameworkCore.ModelBuilder.ApplyConfiguration[TEntity](IEntityTypeConfiguration 1 configuration) at WebApplication5.Data.ApplicationDbContext.OnModelCreating(ModelBuilder modelBuilder) in C:\\Users\\rbasn\\source\\repos\\WebApplication5\\WebApplication5\\Data\\ApplicationDbContext.cs:line 21 at Microsoft.EntityFrameworkCore.Infrastructure.ModelCustomizer.Customize(ModelBuilder modelBuilder, DbContext context) at Microsoft.EntityFrameworkCore.Infrastructure.ModelSource.CreateModel(DbContext context, IConventionSetBuilder conventionSetBuilder) at Microsoft.EntityFrameworkCore.Infrastructure.ModelSource.GetModel(DbContext context, IConventionSetBuilder conventionSetBuilder) at Microsoft.EntityFrameworkCore.Internal.DbContextServices.CreateModel() at Microsoft.EntityFrameworkCore.Internal.DbContextServices.get_Model() at Microsoft.EntityFrameworkCore.Infrastructure.EntityFrameworkServicesBuilder.<>c.<TryAddCoreServices>b__7_3(IServiceProvider p) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitFactory(FactoryCallSite factoryCallSite, RuntimeResolverContext context) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor 1 configuration) at WebApplication5.Data.ApplicationDbContext.OnModelCreating(ModelBuilder modelBuilder) in C:\\Users\\rbasn\\source\\repos\\WebApplication5\\WebApplication5\\Data\\ApplicationDbContext.cs:line 21 at Microsoft.EntityFrameworkCore.Infrastructure.ModelCustomizer.Customize(ModelBuilder modelBuilder, DbContext context) at Microsoft.EntityFrameworkCore.Infrastructure.ModelSource.CreateModel(DbContext context, IConventionSetBuilder conventionSetBuilder) at Microsoft.EntityFrameworkCore.Infrastructure.ModelSource.GetModel(DbContext context, IConventionSetBuilder conventionSetBuilder) at Microsoft.EntityFrameworkCore.Internal.DbContextServices.CreateModel() at Microsoft.EntityFrameworkCore.Internal.DbContextServices.get_Model() at Microsoft.EntityFrameworkCore.Infrastructure.EntityFrameworkServicesBuilder.<>c.<TryAddCoreServices>b__7_3(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.CallSiteRuntimeReso lver.VisitScopeCache(ServiceCallSite singletonCallSite, 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 singletonCallSite, RuntimeResolverContext context) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVi sitor 2.VisitCallSite(ServiceCallSite callSite, TArgument argument) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.Resolve(ServiceCallSite callSite, ServiceProviderEngineScope scope) 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.Infrastructure.Internal.InfrastructureExtensions.GetService[TService](IInfrastructure 2.VisitCallSite(ServiceCallSite callSite, TArgument argument) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.Resolve(ServiceCallSite callSite, ServiceProviderEngineScope scope) 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.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(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__D isplayClass0_0.<.ctor>b__0() at Microsoft.EntityFrameworkCore.Design.OperationExecutor.OperationBase.<>c__DisplayClass3_0`1.b__0() at Microsoft.EntityFrameworkCore.Design.OperationExecutor.OperationBase.Execute(Action action) Value cannot be null. (Parameter 'property')

The two codes should do the exactly same thing, but the 2nd doesn't work. What am I missing here?

You must apply configuration for Transition first, then for ItemState .

modelBuilder.ApplyConfiguration(new TransitionConfig());
modelBuilder.ApplyConfiguration(new ItemStateConfig());

Otherwise the navigations are not defined yet and FindNavigation returns null , hence the exception.

That's why it's better to configure the navigation properties at the same place where you configure the relationship, using the Metadata property of the relationship builder to get the associated IMutableForeignKey , and then PrincipalToDependent or DependentTiProncipal properties to get the corresponding IMutableNavigation .

eg

public class TransitionConfig : IEntityTypeConfiguration<MyTransition>
{
    public void Configure(EntityTypeBuilder<MyTransition> entity)
    {
        entity.HasOne(x => x.Parent)
            .WithMany(x => x.MyTransitions)
            .HasForeignKey(x => x.ParentId)
            .OnDelete(DeleteBehavior.Restrict)
            .Metadata.PrincipalToDependent // x.MyTransitions
            .SetPropertyAccessMode(PropertyAccessMode.Field);

        entity.HasOne(x => x.State)
            .WithMany(x => x.UsedBy)
            .HasForeignKey(x => x.StateId)
            .OnDelete(DeleteBehavior.Restrict)
            .Metadata.PrincipalToDependent // x.UsedBy
            .SetPropertyAccessMode(PropertyAccessMode.Field);
    }
}

In general relationships don't fit well in IEntityTypeConfiguration concept because relationship does not belong to any of the involved entity types. For instance, the same relationships can be configured in other entity config:

public class ItemStateConfig : IEntityTypeConfiguration<MyState>
{
    public void Configure(EntityTypeBuilder<MyTransition> entity)
    {
        entity.HasMany(x => x.MyTransitions)
            .WithOne(x => x.Parent)
            .HasForeignKey(x => x.ParentId)
            .OnDelete(DeleteBehavior.Restrict)
            .Metadata.PrincipalToDependent // x.MyTransitions
            .SetPropertyAccessMode(PropertyAccessMode.Field);

        entity.HasMany(x => x.UsedBy)
            .WithOne(x => x.State)
            .HasForeignKey(x => x.StateId)
            .OnDelete(DeleteBehavior.Restrict)
            .Metadata.PrincipalToDependent // x.UsedBy
            .SetPropertyAccessMode(PropertyAccessMode.Field);
    }
}

Because of that, I prefer configuring relationships directly in OnModelCreating rather than in separate IEntityTypeConfiguration , thus avoiding "ordering" problems like this.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM