在 EF7 / .Net Core 3 中实现抽象 DbContext 是否有一个很好的模式以避免跨项目共享实体/配置重复?

Is there a nice pattern for implementing an abstract DbContext in EF7 / .Net Core 3 to avoid duplication of shared entities / config across projects?

我有许多不同的项目,它们都实现了相同的配置、安全和审计模式,并且正在寻找一种模式,允许我将这些模式定义放在一个抽象的 classes(实体,配置和 dbcontext),如果需要可以在具体实现中扩展。当应用基本配置时,我当前的 POC 失败。我得到:

A key cannot be configured on 'UserRole' because it is a derived type. The key must be configured on the root type.

任何帮助/指点将不胜感激!

我有以下代码示例....

抽象基础classes

角色库

    public abstract class RoleBase
    {
        public RoleBase()
        {
            this.UserRoles = new List<UserRoles>();
        }

        public long Id { get; set; }       
        public string Name { get; set; }
        public virtual IEnumerable<UserRoleBase> UserRoles { get; set; }
    }

用户群

    public abstract class UserBase
    {
        public long Id { get; set; } 
        public string Username { get; set; }
        public string Email { get; set; }
        public virtual ICollection<UserRoleBase> UserRoles { get; set; }
    }

UserRoleBase

    public abstract class UserRoleBase
    {
        public long Id { get; set; } 
        public long RoleId { get; set; }
        public long UserId { get; set; }
        public bool Deleted { get; set; }
        public virtual RoleBase Role { get; set; }
        public virtual UserBase User { get; set; }
    }

其中每个都有一个抽象配置 class 用于基础 class...

角色库配置

public abstract class RoleConfiguration<T> : IEntityTypeConfiguration<T>
        where T : RoleBase
    {
        public virtual void Configure(EntityTypeBuilder<T> builder)
        {
            // Primary Key
            builder.HasKey(t => t.Id);

            // Properties
            builder.Property(t => t.Name)
                .IsRequired()
                .HasMaxLength(50);

            // Table & Column Mappings
            builder.ToTable("Role", "Security");
            builder.Property(t => t.Id).HasColumnName("Id");
            builder.Property(t => t.Name).HasColumnName("Name");
        }
    }

UserBase 配置

    public abstract class UserConfiguration<TBase> : IEntityTypeConfiguration<TBase>
        where TBase : UserBase
    {
        public virtual void Configure(EntityTypeBuilder<TBase> builder)
        {
            // Primary Key
            builder.HasKey(t => t.Id);

            // Properties
            builder.Property(t => t.Username).IsRequired().HasMaxLength(255);
            builder.Property(t => t.Email).IsRequired().HasMaxLength(255);

            // Table & Column Mappings
            builder.ToTable("User", "Security");
            builder.Property(t => t.Id).HasColumnName("Id");
            builder.Property(t => t.Username).HasColumnName("Username");
            builder.Property(t => t.Email).HasColumnName("Email");
        }
    }

UserRoleBase 配置

    public abstract class UserRoleConfiguration<T> : IEntityTypeConfiguration<T>
        where T : UserRoleBase
    {
        public virtual void Configure(EntityTypeBuilder<T> builder)
        {
            // Primary Key
            builder.HasKey(t => t.Id);

            // Properties
            builder.Property(t => t.RoleId).IsRequired();
            builder.Property(t => t.UserId).IsRequired();
            builder.Property(t => t.Deleted).IsRequired();

            // Table & Column Mappings
            builder.ToTable("UserRole", "Security");
            builder.Property(t => t.Id).HasColumnName("Id");
            builder.Property(t => t.RoleId).HasColumnName("RoleId");
            builder.Property(t => t.UserId).HasColumnName("UserId");
            builder.Property(t => t.Deleted).HasColumnName("Deleted");

            // Relationships
            builder.HasOne(t => t.Role)
                .WithMany(t => (ICollection<TBase>)t.UserRoles)
                .HasForeignKey(d => d.RoleId)
                .OnDelete(DeleteBehavior.Restrict);

            builder.HasOne(t => t.UserDetail)
                .WithMany(t => (ICollection<TBase>)t.UserRoles)
                .HasForeignKey(d => d.UserDetailId)
                .OnDelete(DeleteBehavior.Restrict);
        }

以及基础classes的具体实现:

角色

    public class Role : RoleBase
    {

    }

用户

    public class User : UserBase
    {
        // Extension properties
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string Phone { get; set; }
        public string Mobile { get; set; }
    }

用户角色

    public class UserRole : UserRoleBase
    {

    }

以及配置的具体实现

角色配置

    public class RoleConfiguration : Base.Configurations.RoleConfiguration<Role>
    {
        public override void Configure(EntityTypeBuilder<Role> builder)
        {
            base.Configure(builder);
            this.ConfigureEntity(builder);
        }

        private void ConfigureEntity(EntityTypeBuilder<Role> builder)
        {
        }
    }

用户配置

    public class UserConfiguration : Base.Configurations.UserConfiguration<User>
    {
        public override void Configure(EntityTypeBuilder<User> builder)
        {
            base.Configure(builder);
            this.ConfigureEntity(builder);
        }

        private void ConfigureEntity(EntityTypeBuilder<User> builder)
        {
            //Registration of extension properties
            builder.Property(t => t.FirstName).HasColumnName("FirstName");
            builder.Property(t => t.LastName).HasColumnName("LastName");
            builder.Property(t => t.Phone).HasColumnName("Phone");
            builder.Property(t => t.Mobile).HasColumnName("Mobile");
        }
    }

用户角色配置

    public class UserRoleConfiguration : Base.Configurations.UserRoleConfiguration<UserRole>
    {
        public override void Configure(EntityTypeBuilder<UserRole> builder)
        {
            base.Configure(builder);
            this.ConfigureEntity(builder);
        }

        private void ConfigureEntity(EntityTypeBuilder<UserRole> builder)
        {
        }
    }

和基础上下文

public abstract class BaseDbContext: DbContext
    {

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

        }

        // https://github.com/aspnet/EntityFramework.Docs/issues/594
        protected BaseDbContext(DbContextOptions options)
            : base(options)
        {
        }

        public DbSet<RoleBase> Roles { get; set; }
        public DbSet<UserBase> Users { get; set; }
        public DbSet<UserRoleBase> UserRoles { get; set; }

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

以及具体上下文

public class MyDbContext: BaseDbContext
    {
        public MyDbContext(DbContextOptions<MyDbContext> options)
        :base(options)
        {
        }

        protected MyDbContext(DbContextOptions options)
            : base(options)
        {
        }

        public new DbSet<Role> Roles { get; set; }
        public new DbSet<User> Users { get; set; }
        public new DbSet<UserRole> UserRoles { get; set; }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.ApplyConfiguration(new RoleConfiguration());
            modelBuilder.ApplyConfiguration(new UserConfiguration());
            modelBuilder.ApplyConfiguration(new UserRoleConfiguration());

            base.OnModelCreating(modelBuilder);
        }
    }

所以所有这一切都适用于没有导航属性的项目,只要没有导航属性就可以很好地迁移到数据库。只要我注释掉所有导航属性,我就可以看到用户的扩展属性正在完成。

在存在导航属性的情况下,基本配置出现错误 class。具体实施后调用 base.Configure(builder);

我在 builder.HasKey(t => t.Id); 上收到以下错误消息,对于上面的示例代码,它会在.. .

    public abstract class UserRoleConfiguration<T> : IEntityTypeConfiguration<T>
        where T : UserRoleBase
    {
        public virtual void Configure(EntityTypeBuilder<T> builder)
        {
            // Primary Key
            builder.HasKey(t => t.Id);

System.InvalidOperationException: 'A key cannot be configured on 'UserRole' because it is a derived type. The key must be configured on the root type 'UserRoleBase'. If you did not intend for 'UserRoleBase' to be included in the model, ensure that it is not included in a DbSet property on your context, referenced in a configuration call to ModelBuilder, or referenced from a navigation property on a type that is included in the model.'

有没有一种方法可以将这些关系配置保留在抽象基础 class 中,这样我就不需要在基础 classes 的每个具体实现中复制它?或者是否可以采用不同的方法来解决这个问题?

System.InvalidOperationException: 'A key cannot be configured on 'UserRole' because it is a derived type. The key must be configured on the root type 'UserRoleBase'. If you did not intend for 'UserRoleBase' to be included in the model, ensure that it is not included in a DbSet property on your context, referenced in a configuration call to ModelBuilder, or referenced from a navigation property on a type that is included in the model.'

根据错误,您可以在基本模型的 ID 上使用 Key 属性来指定主键。

根据 EF Core 3.0 中包含的重大更改,ToTable on a derived type throws an exception,目前将派生类型映射到不同的 table 是无效的。此更改避免在将来成为有效的事情时中断。

您可以在基本模型上使用数据注释来配置类型映射到的 table:

[Table("Role", Schema = "Security")]
public abstract class RoleBase
{
    public RoleBase()
    {
        this.UserRoles = new List<UserRoles>();
    }
    [Key]
    public long Id { get; set; }
    public string Name { get; set; }
    public virtual ICollection<UserRoleBase> UserRoles { get; set; }
}