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

问题描述

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

无法在“UserRole”上配置密钥,因为它是派生类型。必须在根类型上配置密钥。

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

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

抽象基类

角色库

    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; }
    }

用户角色库

    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; }
    }

它们中的每一个都有一个用于基类的抽象配置类......

角色库配置

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");
        }
    }

用户基础配置

    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");
        }
    }

用户角色基础配置

    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);
        }

以及基类的具体实现:

角色

    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);
        }
    }

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

在存在导航属性的情况下,我在基本配置类上出现错误。在具体实现之后调用 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:“无法在“UserRole”上配置密钥,因为它是派生类型。必须在根类型“UserRoleBase”上配置密钥。如果您不打算将“UserRoleBase”包含在模型中,请确保它不包含在您的上下文的 DbSet 属性中、在对 ModelBuilder 的配置调用中引用或从包含的类型的导航属性中引用在模型中。

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

标签: asp.net-coreef-code-firstentity-framework-coreef-fluent-api

解决方案


System.InvalidOperationException:“无法在“UserRole”上配置密钥,因为它是派生类型。必须在根类型“UserRoleBase”上配置密钥。如果您不打算将“UserRoleBase”包含在模型中,请确保它不包含在您的上下文的 DbSet 属性中、在对 ModelBuilder 的配置调用中引用或从包含的类型的导航属性中引用在模型中。

从错误中,您可以使用Key基本模型 id 上的属性来指定主键。

由于 EF Core 3.0 中包含的重大更改,派生类型上的 ToTable 会引发异常,目前将派生类型映射到不同的表是无效的。此更改可避免在将来成为有效的事情时中断。

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

[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; }
}

推荐阅读