abpframework / abp

Open Source Web Application Framework for ASP.NET Core. Offers an opinionated architecture to build enterprise software solutions with best practices on top of the .NET and the ASP.NET Core platforms. Provides the fundamental infrastructure, production-ready startup templates, application modules, UI themes, tooling, guides and documentation.
https://abp.io
GNU Lesser General Public License v3.0
12.31k stars 3.32k forks source link

How to add navigation property to an entity of a dependen module #3807

Closed XuJin186 closed 4 years ago

XuJin186 commented 4 years ago

Version: 2.6.2 Entity:

public class Test : FullAuditedEntityWithUser<Guid, IdentityUser>
    {
        public string Name { get; set; }
    }

MigrationDbContext:

      protected override void OnModelCreating(ModelBuilder builder)
        {
            base.OnModelCreating(builder);
           builder.ConfigurePermissionManagement();
            builder.ConfigureSettingManagement();
            builder.ConfigureBackgroundJobs();
            builder.ConfigureAuditLogging();
            builder.ConfigureIdentity();
            builder.ConfigureIdentityServer();
            builder.ConfigureFeatureManagement();
            builder.ConfigureTenantManagement();

            /* Configure your own tables/entities inside the ConfigureWms method */

            builder.ConfigureWms();
        }

DbContext:

 protected override void OnModelCreating(ModelBuilder builder)
        {

            base.OnModelCreating(builder);

            /* Configure the shared tables (with included modules) here */

            builder.Entity<AppUser>(b =>
            {
                b.ToTable(AbpIdentityDbProperties.DbTablePrefix + "Users"); //Sharing the same table "AbpUsers" with the IdentityUser
                b.ConfigureByConvention();
                b.ConfigureAbpUser();

                /* Configure mappings for your additional properties
                 * Also see the WmsEfCoreEntityExtensionMappings class
                 */
            });

            builder.ConfigureWms();
        }

ConfigureWms:

public static void ConfigureWms(this ModelBuilder builder)
        {
            Check.NotNull(builder, nameof(builder));
            builder.Entity<Test>(
                b =>
                {
                    b.ToTable("tests");
                    b.ConfigureFullAudited();
                    b.Property(x => x.Name).IsRequired().HasMaxLength(200);

                });

        }

Exception Information:

The entity type 'IdentityUserLogin' requires a primary key to be defined. If you intended to use a keyless entity type call 'HasNoKey()'.
System.InvalidOperationException: The entity type 'IdentityUserLogin' requires a primary key to be defined. If you intended to use a keyless entity type call 'HasNoKey()'.
maliming commented 4 years ago

Can you use cli to create a project to reproduce the problem?

XuJin186 commented 4 years ago

@maliming Demo.zip

The main problem is that I use the FullAuditedEntityWithUser <Guid, IdentityUser> class. If I use IdentityUser, it can be migrated, but it will report an error when running, if I use Appuser, it will run normally, but it will be abnormal during migration.

maliming commented 4 years ago

What are the steps?

XuJin186 commented 4 years ago

Add Test entity in Demo.Domain public class Test : FullAuditedEntityWithUser<Guid, IdentityUser> { public string Name { get; set; } } I used the FullAuditedEntityWithUser base class

Then I wrote the ConfigureDemo method in OnModelCreating

public static void ConfigureDemo(this ModelBuilder builder) { Check.NotNull(builder, nameof(builder)); builder.Entity<Test>(b => { b.ToTable("Tests"); b.Property(nameof(Test.Name)).IsRequired(false).HasMaxLength(200); b.ConfigureFullAudited(); }); } } Then perform the migration, everything is normal Then execute the GetListAsync method in the TestApplicationService in the Demo.Application project, you will get an error message

The entity type 'IdentityUserLogin' requires a primary key to be defined. If you intended to use a keyless entity type call 'HasNoKey()'. System.InvalidOperationException: The entity type 'IdentityUserLogin' requires a primary key to be defined. If you intended to use a keyless entity type call 'HasNoKey()'.

XuJin186 commented 4 years ago

@maliming If I change FullAuditedEntityWithUser <Guid, IdentityUser> to FullAuditedEntityWithUser <Guid, AppUser>, it works fine, but adding the migration again after changing the entity properties will be abnormal

maliming commented 4 years ago

Then perform the migration, everything is normal Then execute the GetListAsync method in the TestApplicationService in the Demo.Application project, you will get an error message

I can't reproduce it.

XuJin186 commented 4 years ago

@maliming
You can open the demo project that I uploaded. It has been written and reproduced. You can update-databse directly, and then run GetListAsync in the Test service

gerryge commented 4 years ago

Hi @maliming , I can reproduce this issue. AppUser

PM> update-database Build started... Build succeeded. System.InvalidOperationException: The property 'AppUser.ExtraProperties' could not be mapped, because it is of type 'Dictionary<string, object>' which is not a supported primitive type or a valid entity type. Either explicitly map this property, or ignore it using the '[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in 'OnModelCreating'. at Microsoft.EntityFrameworkCore.Infrastructure.ModelValidator.ValidatePropertyMapping(IModel model, IDiagnosticsLogger1 logger) at Microsoft.EntityFrameworkCore.Infrastructure.ModelValidator.Validate(IModel model, IDiagnosticsLogger1 logger) at Microsoft.EntityFrameworkCore.Infrastructure.RelationalModelValidator.Validate(IModel model, IDiagnosticsLogger1 logger) at Microsoft.EntityFrameworkCore.SqlServer.Internal.SqlServerModelValidator.Validate(IModel model, IDiagnosticsLogger1 logger) at Microsoft.EntityFrameworkCore.Metadata.Conventions.ValidatingConvention.ProcessModelFinalized(IConventionModelBuilder modelBuilder, IConventionContext1 context) at Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal.ConventionDispatcher.ImmediateConventionScope.OnModelFinalized(IConventionModelBuilder modelBuilder) at Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal.ConventionDispatcher.OnModelFinalized(IConventionModelBuilder modelBuilder) at Microsoft.EntityFrameworkCore.Metadata.Internal.Model.FinalizeModel() at Microsoft.EntityFrameworkCore.ModelBuilder.FinalizeModel() 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.CallSiteVisitor2.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.CallSiteVisitor2.VisitCallSite(ServiceCallSite callSite, TArgument argument) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor2.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.CallSiteVisitor2.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](IInfrastructure1 accessor) at Microsoft.EntityFrameworkCore.Infrastructure.AccessorExtensions.GetService[TService](IInfrastructure1 accessor) at Microsoft.EntityFrameworkCore.Design.Internal.DbContextOperations.CreateContext(Func1 factory) at Microsoft.EntityFrameworkCore.Design.Internal.DbContextOperations.CreateContext(String contextType) at Microsoft.EntityFrameworkCore.Design.Internal.MigrationsOperations.UpdateDatabase(String targetMigration, String contextType) at Microsoft.EntityFrameworkCore.Design.OperationExecutor.UpdateDatabaseImpl(String targetMigration, String contextType) at Microsoft.EntityFrameworkCore.Design.OperationExecutor.UpdateDatabase.<>c__DisplayClass0_0.<.ctor>b__0() at Microsoft.EntityFrameworkCore.Design.OperationExecutor.OperationBase.Execute(Action action) The property 'AppUser.ExtraProperties' could not be mapped, because it is of type 'Dictionary<string, object>' which is not a supported primitive type or a valid entity type. Either explicitly map this property, or ignore it using the '[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in 'OnModelCreating'. PM>

maliming commented 4 years ago

The property 'AppUser.ExtraProperties' could not be mapped, because it is of type 'Dictionary<string, object>' which is not a supported primitive type or a valid entity type. Either explicitly map this property, or ignore it using the '[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in 'OnModelCreating'.

According to the current design, you cannot use AppUser in EntityFrameworkCore.DbMigrations.

AppUserand IdentityUserare shared tables, you can use IdentityUser(FullAuditedEntityWithUser<Guid, IdentityUser>).

gerryge commented 4 years ago

The property 'AppUser.ExtraProperties' could not be mapped, because it is of type 'Dictionary<string, object>' which is not a supported primitive type or a valid entity type. Either explicitly map this property, or ignore it using the '[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in 'OnModelCreating'.

According to the current design, you cannot use AppUser in EntityFrameworkCore.DbMigrations.

AppUserand IdentityUserare shared tables, you can use IdentityUser(FullAuditedEntityWithUser<Guid, IdentityUser>).

Yes, I see, there are also some issues using IdentityUser, I'll try to reproduce to you.

gerryge commented 4 years ago

Hi @maliming , I have reproduced the issue, please down the demo and do below steps:

  1. Extract the zip file
  2. Run "Update-Database"
  3. Debug Demo.HttpApi.Host project
  4. Execute ​/api​/app​/test
  5. Debug function public override Task<List> GetListAsync(bool includeDetails = false, CancellationToken cancellationToken = new CancellationToken()) in TestRepository.cs file

IdentityUser image

DemoIdentityUser_2.zip

XuJin186 commented 4 years ago

@GerryGe That's the problem

maliming commented 4 years ago

@XuJin186 @GerryGe

It is not recommended that you use Entity in other modules as the navigation property, because the module entity may exist in another database. Of course, you can use the Id of the entity.

Even if all modules exist in a database, EF Core will still throw an exception because of different DbContext, such as the above exception information. (IdentityUserLogin 'requires a primary key to be defined.). The configuration of the entity is associated with DbContext.

Maybe classes like FullAuditedEntityWithUsershould be removed from the framework.

In short, don't use FullAuditedEntityWithUser but use the Id of User entity to query User's information.

gerryge commented 4 years ago

@maliming Do you have any idea or background of why define these *WithUser class?

maliming commented 4 years ago

Maybe classes like FullAuditedEntityWithUser should be removed from the framework.

@hikalkan what do you think?

hikalkan commented 4 years ago

I implemented it and sending the modified source code:

demo-modified.zip

As a general principle; MigrationDbContext should not know the AppUser and ProjectDbContext should not know the IdentityUser. You must ignore related properties where needed.

DemoDbContext

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

    builder.Entity<AppUser>(b =>
    {
        b.ToTable("User", AbpIdentityDbProperties.DbTablePrefix + "Users"); //Sharing the same table "AbpUsers" with the IdentityUser

        b.ConfigureByConvention();
        b.ConfigureAbpUser();
    });

    builder.ConfigureDemo(); //Shared code between db contexts

    builder.Entity<Test>(b =>
    {
        //SET RELATIONS FOR THE PROJECT DBCONTEXT

        b.HasOne(x => x.Creator).WithMany().IsRequired(false);
        b.HasOne(x => x.LastModifier).WithMany().IsRequired(false);
        b.HasOne(x => x.Deleter).WithMany().IsRequired(false);
    });
}

DemoMigrationsDbContext

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

    /* Include modules to your migration db context */

    builder.ConfigurePermissionManagement();
    builder.ConfigureSettingManagement();
    builder.ConfigureBackgroundJobs();
    builder.ConfigureAuditLogging();
    builder.ConfigureIdentity();
    builder.ConfigureIdentityServer();
    builder.ConfigureFeatureManagement();
    builder.ConfigureTenantManagement();

    //IGNORE AppUser related properties

    builder.Entity<Test>(b =>
    {
        b.Ignore(x => x.Creator);
        b.Ignore(x => x.LastModifier);
        b.Ignore(x => x.Deleter);
    });

    builder.ConfigureDemo(); //Shared code between db contexts
}

Test entity

public class Test : FullAuditedEntityWithUser<Guid, AppUser>
{
    public string Name { get; set; }
    public string Code { get; set; }
}

This is how we can add navigation properties to an entity defined in a module.

@maliming, removing FullAuditedEntityWithUser doesn't solve problems, because people will then add navigation properties themselves. But I also don't like this class much to be honest :)

realLiangshiwei commented 3 years ago

After my testing, the solution is not available, I made some changes:

DemoDbContext

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

    builder.Entity<AppUser>(b =>
    {
        b.ToTable(AbpIdentityDbProperties.DbTablePrefix + "Users"); //Sharing the same table "AbpUsers" with the IdentityUser

        b.ConfigureByConvention();
        b.ConfigureAbpUser();
    });

    builder.ConfigureDemo(); //Shared code between db contexts

    builder.Entity<Test>(b =>
    {
        //SET RELATIONS FOR THE PROJECT DBCONTEXT

        b.HasOne(x => x.Creator).WithMany().HasForeignKey("CreatorId").IsRequired(false);
        b.HasOne(x => x.LastModifier).WithMany().HasForeignKey("LastModifierUserId").IsRequired(false);
        b.HasOne(x => x.Deleter).WithMany().HasForeignKey("DeleterUserId").IsRequired(false);
    });
}

DemoDbContextModelCreatingExtensions

public static class DemoDbContextModelCreatingExtensions
{
    public static void ConfigureDemo(this ModelBuilder builder)
    {
        Check.NotNull(builder, nameof(builder));

        builder.Entity<Test>(b =>
        {
            b.ToTable("Tests");

            b.ConfigureByConvention();

            b.Property(x => x.Name).IsRequired(false).HasMaxLength(200);
        });

        /* Configure your own tables/entities inside here */

        //builder.Entity<YourEntity>(b =>
        //{
        //    b.ToTable(DemoConsts.DbTablePrefix + "YourEntities", DemoConsts.DbSchema);

        //    //...
        //});
    }
}

DemoMigrationsDbContext

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

    /* Include modules to your migration db context */

    builder.ConfigurePermissionManagement();
    builder.ConfigureSettingManagement();
    builder.ConfigureBackgroundJobs();
    builder.ConfigureAuditLogging();
    builder.ConfigureIdentity();
    builder.ConfigureIdentityServer();
    builder.ConfigureFeatureManagement();
    builder.ConfigureTenantManagement();

    //IGNORE AppUser related properties

    builder.Entity<Test>(b =>
    {
          b.Ignore(x => x.Creator);
          b.Ignore(x => x.LastModifier);
          b.Ignore(x => x.Deleter);

           b.HasOne<IdentityUser>().WithMany().HasForeignKey("CreatorId").IsRequired(false);
           b.HasOne<IdentityUser>().WithMany().HasForeignKey("LastModifierUserId").IsRequired(false);
           b.HasOne<IdentityUser>().WithMany().HasForeignKey("DeleterUserId").IsRequired(false);
     });

    builder.ConfigureDemo(); //Shared code between db contexts
}

Test entity

public class Test : FullAuditedEntityWithUser<Guid, AppUser>
{
    public string Name { get; set; }
    public string Code { get; set; }
}

image

albutta commented 3 years ago

There is still an issue with the solution provided in the article https://community.abp.io/articles/abp-suite-how-to-add-the-user-entity-as-a-navigation-property-of-another-entity-furp75ex image

maliming commented 3 years ago

hi @albutta

Please create a new issue.

if it's related to abp commercial please get support via the support website.