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

[Can't use AbpUser as navigation property] The property 'AppUser.ExtraProperties' could not be mapped #1517

Closed holyrong closed 4 years ago

holyrong commented 4 years ago

I'm working with 0.18.1.I get the error message when add-migration. The relation of entities like this:

image The Entity "SchoolUsers" inherits from Entity like this:

public class  SchoolUsers : Entity
    {

        public Guid SchoolId { get; set; }

        public Guid UserId { get; set; }

        public virtual Users.AppUser User { get; set; }

        public virtual School School { get; set; }

        public override object[] GetKeys()
        {
            return new object[2]
            {
                SchoolId,UserId
            };
        }
    }

Config:

public override void Configure(ModelBuilder builder)
        {
            builder.Entity<Holyschool.Schools.SchoolUsers>(b =>
            {
                b.ToTable(HolyschoolConsts.DbTablePrefix + "SchoolUsers", HolyschoolConsts.DbSchema);

                b.HasKey(cs => new { cs.SchoolId, cs.UserId });

                b.HasOne(su => su.School)
                .WithMany()
                .HasForeignKey(su => su.SchoolId);

                b.HasOne(su => su.User)
                .WithMany()
                .HasForeignKey(su => su.UserId);

               // b.ConfigureFullAuditedAggregateRoot();
            });
        }
builder.Entity<AppUser>(b =>
            {
                b.ToTable("AbpUsers"); //Sharing the same table "AbpUsers" with the IdentityUser

                //b.ConfigureFullAudited();
                //b.ConfigureExtraProperties();
                //b.ConfigureConcurrencyStamp();
                b.ConfigureFullAuditedAggregateRoot();
                b.ConfigureAbpUser();

                //Moved customization to a method so we can share it with the HolyschoolMigrationsDbContext class
                b.ConfigureCustomUserProperties();
            });

when add-migration, I've received a error:

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'.

if replaced

b.HasOne(su => su.User)
                .WithMany()
                .HasForeignKey(su => su.UserId);

with

b.Ignore(t => t.User); add-migration succeed, but when I execute Repository.WithDetails() in SchoolAppService, I received another error:

[Error] The property 'User' is not a navigation property of entity type 'SchoolUsers'. The 'Include(string)' method can only be used with a '.' separated list of navigation property names. System.InvalidOperationException: The property 'School' is not a navigation property of entity type 'SchoolUsers'. The 'Include(string)' method can only be used with a '.' separated list of navigation property names.

This only occured when Relation with AbpUser

I've tried #1414 ,It's not working.

@hikalkan , @maliming ,can you help me?

yekalkan commented 4 years ago

try b.ConfigureExtraProperties();

holyrong commented 4 years ago

try b.ConfigureExtraProperties();

The SchoolUsers is inherit from Entity, there is not any b.ConfigureExtraProperties(); for object 'b'.

I've tried b.ConfigureFullAuditedAggregateRoot(); , the error still exists.

if i delete b.HasOne(su => su.User) .WithMany() .HasForeignKey(su => su.UserId); and add b.Ignore(t => t.User); the error disappeared.

gdlcf88 commented 4 years ago

Dont you have this code in your MyProjectDbContext.cs?

            builder.Entity<AppUser>(b =>
            {
                b.ToTable("AbpUsers"); //Sharing the same table "AbpUsers" with the IdentityUser
                b.ConfigureFullAuditedAggregateRoot();
                b.ConfigureAbpUser();

                //Moved customization to a method so we can share it with the MyProjectMigrationsDbContext class
                b.ConfigureCustomUserProperties();
            });
holyrong commented 4 years ago

Dont you have this code in your MyProjectDbContext.cs? builder.Entity(b => { b.ToTable("AbpUsers"); //Sharing the same table "AbpUsers" with the IdentityUser b.ConfigureFullAuditedAggregateRoot(); b.ConfigureAbpUser();

            //Moved customization to a method so we can share it with the MyProjectMigrationsDbContext class
            b.ConfigureCustomUserProperties();
        });

This is my code:

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

                b.ConfigureFullAudited();
                b.ConfigureExtraProperties();
                b.ConfigureConcurrencyStamp();
                b.ConfigureAbpUser();

                //Moved customization to a method so we can share it with the HolyschoolMigrationsDbContext class
                b.ConfigureCustomUserProperties();
            });
hikalkan commented 4 years ago

This is a common scenario. While we don't suggest to have navigation properties to other aggregate roots in DDD, it is a common habit among EF Core developers :) We will reproduce this and will see what we can suggest.

hikalkan commented 4 years ago

@yekalkan can you create a new application from the startup template, create the same entities and reproduce the problem.

yekalkan commented 4 years ago

try this:

               if (isMigrationDbContext)
                {
                    b.Ignore(t => t.User);
                }
                else
                {
                    b.HasOne(su => su.User)
                        .WithMany()
                        .HasForeignKey(su => su.UserId);
                }

Send isMigrationDbContext as paramater from MyProjectMigrationsDbContext and MyProjectDbContext.

in MyProjectMigrationsDbContext: image in MyProjectDbContext: image

tolemac commented 4 years ago

I wrote a question about this issue on stack overflow, any help is welcome, thanks!

wakuflair commented 4 years ago

Sorry I don't understand why this issue is closed, I think it has not been resolved.

@yekalkan I tried your way, but I got only one foreign key ---- no user foreign key, but it's supposed to be two(SchoolId and UserId for the @holyrong 's sample).

Whenever an entity has relationship with AppUser(one to many, or many to many), this migration will fail with the "The property 'AppUser.ExtraProperties' could not be mapped" message.

gdlcf88 commented 4 years ago

Sorry I don't understand why this issue is closed, I think it has not been resolved.

@yekalkan I tried your way, but I got only one foreign key ---- no user foreign key, but it's supposed to be two(SchoolId and UserId for the @holyrong 's sample).

Whenever an entity has relationship with AppUser(one to many, or many to many), this migration will fail with the "The property 'AppUser.ExtraProperties' could not be mapped" message.

I agree with you, and this is a temporary solution I found, I hope it can help you in current situation. https://stackoverflow.com/questions/49986756/efcore-map-2-entities-to-same-table https://github.com/abpframework/abp/issues/1414#issuecomment-507395552

saYRam commented 4 years ago

This is a common scenario. While we don't suggest to have navigation properties to other aggregate roots in DDD, it is a common habit among EF Core developers :) We will reproduce this and will see what we can suggest.

is that meaning never ever use navigation properties in DDD in all conditions?

github-ingenium commented 3 years ago

I was not using ExtraProperties so below solved my issue..

public class AppUser : FullAuditedAggregateRoot<Guid>, IUser
{
// Omitted for brevity 
[NotMapped]
public override Dictionary<string, object> ExtraProperties { get; protected set; }

}
Znow commented 3 years ago

@github-ingenium Thanks for answer, mine came with this error:

image

so I have to used the below:

[NotMapped]
public override ExtraPropertyDictionary ExtraProperties { get; protected set; }
ocodista commented 3 years ago

@github-ingenium Thanks for answer, mine came with this error:

image

so I have to used the below:

[NotMapped]
public override ExtraPropertyDictionary ExtraProperties { get; protected set; }

This doesn't work for me because at the next Migration, a new table of AppUser is created.

Don't think this should be closed, there still no way to use a Navigation property of AppUser in another entity, without it, EF won't create the FK

Znow commented 3 years ago

@CAIOHOBORGHI

This is the solution I'm using which is working:

YourProject.Domain/SomeEntities/SomeEntity.cs

public class SomeEntity: AuditedAggregateRoot<Guid>
    {
        // OTHER PROPERTIES
        public Guid UserId { get; set; }
    }

YourProject.EntityFrameworkCore/EntityFrameworkCore/YourProjectDbContextModelCreatingExtensions.cs

builder.Entity<SomeEntity>(b =>
            {
                b.ToTable("SomeEntity", YourProjectConsts.DbSchema);
                b.ConfigureByConvention(); //auto configure for the base class props
                b.HasOne<IdentityUser>().WithMany().HasForeignKey(x => x.UserId);
            });

YourProject.EntityFrameworkCore/EntityFrameworkCore/YourProjectDbContext.cs

[ConnectionStringName("Default")]
    public class YourProjectDbContext : AbpDbContext<YourProjectDbContext>
    {
        public DbSet<AppUser> Users { get; set; }

        public DbSet<SomeEntity> SomeEntities { get; set; }

        /* Add DbSet properties for your Aggregate Roots / Entities here.
         * Also map them inside YourProjectDbContextModelCreatingExtensions.ConfigureYourProject
         */

        public YourProjectDbContext(DbContextOptions<YourProjectDbContext> options)
            : base(options)
        {

        }

        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();
                b.ConfigureExtraProperties();

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

            // BUG: Below is a hack to overcome IdentityUser issues

            builder.Entity<IdentityUser>().Ignore(x => x.ExtraProperties);
            builder.Entity<IdentityUserLogin>().HasKey(x => new { x.UserId, x.LoginProvider });
            builder.Entity<IdentityUserRole>().HasKey(x => new { x.UserId, x.RoleId });
            builder.Entity<IdentityUserToken>().HasKey(x => new { x.UserId, x.LoginProvider });
            builder.Entity<IdentityUserOrganizationUnit>().HasKey(x => new { x.UserId, x.OrganizationUnitId });

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

            builder.ConfigureYourProject();
        }
    }

YourProject.Domain/Users/AppUser.cs

[NotMapped]
        public override ExtraPropertyDictionary ExtraProperties { get; protected set; }
ocodista commented 3 years ago

Thanks @Znow , with this the FK is created.

Still can't use the navigation property but it's better than no FK at all.

ModarNa commented 3 years ago

I haven't tested this yet but I have used a solution based on both @Znow and @yekalkan answers

if (isMigrationDbContext)
{
    t.Ignore(x => x.AppUser); // 1st point
    t.HasOne<IdentityUser>().WithMany().HasForeignKey(x => x.AppUserId); // 2nd point
}
else
    t.HasOne(x => x.AppUser).WithMany().HasForeignKey(x => x.AppUserId); // 3rd point
  1. avoids the Can't use AbpUser as navigation property error
  2. creates the foreign key relation (without creating the AppUser table
  3. should solve the navigation property issue (not tested yet)

check @yekalkan answer to get the full picture

Fitmavincent commented 3 years ago

I have the same issue here while my AppUser still my aggregate root, I don't even put AppUser as Navigation property inside my entity.

builder.Entity<UserSession>(u =>
            {
                u.ToTable(
                    ProtocloudConsts.DbTablePrefix + "UserSession", 
                    ProtocloudConsts.DbSchema);

                u.HasOne<AppUser>().WithOne().HasForeignKey<AppUser>(x => x.Id);
                u.ConfigureByConvention();                
                u.Property(x => x.AutodeskUserID).IsRequired();
                u.Property(x => x.UserName).IsRequired();
                u.Property(x => x.MacID).IsRequired();
            });

Do we have an official solution for this? Or I have to do a workaround?

johan-developer commented 3 years ago

I have the same issue without any solution yet.

johan-developer commented 3 years ago

I haven't tested this yet but I have used a solution based on both @Znow and @yekalkan answers

if (isMigrationDbContext)
{
    t.Ignore(x => x.AppUser); // 1st point
    t.HasOne<IdentityUser>().WithMany().HasForeignKey(x => x.AppUserId); // 2nd point
}
else
    t.HasOne(x => x.AppUser).WithMany().HasForeignKey(x => x.AppUserId); // 3rd point
  1. avoids the Can't use AbpUser as navigation property error
  2. creates the foreign key relation (without creating the AppUser table
  3. should solve the navigation property issue (not tested yet)

check @yekalkan answer to get the full picture

I already do this and navigation property display: Invalid object name 'AppUser' Then not found for me.

ghost commented 2 years ago

I don’t use navigation properties, just want to set a foreign key in database. I have the same issue, is there a solution?

sukeshchand commented 2 years ago

Is there any official solution for the issue yet?

abu7midan commented 2 years ago

hello

try this after entity configuration

        builder.ConfigureByConvention();