asp.net-core - 在 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 的配置调用中引用或从包含的类型的导航属性中引用在模型中。
有没有一种方法可以将这些关系配置保留在抽象基类中,这样我就不需要在基类的每个具体实现中复制它?或者是否可以采用不同的方法来克服这个问题?
解决方案
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; }
}
推荐阅读
- excel - 选择超链接目标单元格的整行并向左滚动目标单元格
- aws-amplify - 从分叉分支构建的 AWS Ampify 控制台预览版
- postgresql - 如何使用 pgAdmin 从 .pg_dump 文件中恢复数据库
- apache-spark - 在笔记本上使用 pyspark 从表中删除行
- javascript - 在循环中,使所有其他值除了匹配条件的值被赋予样式
- java - 当我导入 JPA 注释并仅使用单个休眠属性时,我是否在用例中处理 JPA 或休眠?
- api - 处理 ErrorException (E_NOTICE) 未定义的偏移量:0
- java - 如何通过 Gradle 7.0+ 制作具有依赖关系的 Jar 文件?
- ios - 如何在页脚中使用 segue 将标签数据传递到不同的部分?
- github - 将 slack 与 github 集成以获取推送到特定分支的通知