asp.net-core - 是否有用于在 EF7/.Net Core 3 中实现抽象 DbContext 以避免跨项目共享实体/配置重复的良好模式?

标签 asp.net-core ef-code-first entity-framework-core ef-fluent-api

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

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

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

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

抽象基类

角色库

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

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

以及基类的具体实现:

作用

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

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

在存在导航属性的情况下,我在基本配置类上遇到错误。在调用 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.'

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

最佳答案

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

关于asp.net-core - 是否有用于在 EF7/.Net Core 3 中实现抽象 DbContext 以避免跨项目共享实体/配置重复的良好模式?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58017611/

相关文章:

entity-framework - Entity Framework 4.3迁移可移动现有数据

entity-framework - 指定的 deps.json '$$$' 不存在

c# - .Net Core - 根据环境更改数据库迁移

c# - 最小起订量异常 : Expected invocation on the mock once, 但为 0 次... - .Net Core 3.1 with xUnit

c# - 全局异常处理程序 ASP.Net Core MVC

c# - 网络核心 : Program does not contain a static 'Main' method suitable for an entry point

c# - 为什么 Entity Framework 引用了一个根本不存在的列?

c# - 表值函数和 Entity Framework

c# - Entity Framework 无法在实体构造函数中绑定(bind)值对象

c# - 使用 ASP.NET 5 中的 cookie 身份验证重定向到使用属性授权的登录