【问题标题】:Multiple Common Fields CreatedOn and CreatedBy in every table of a database. How it can be without repeating for every table数据库的每个表中的多个公共字段 CreatedOn 和 CreatedBy。不重复每张桌子怎么办
【发布时间】:2017-09-22 15:05:37
【问题描述】:

场景:

public class Department
{
    public int DepartmentId { get; set; }
    public string DepartmentName { get; set; }
    public string Description { get; set; }
    public DateTime CreatedOn {get; set; }
    public string CreatedBy {get; set; }
}

public class TestItem
{
    public int TestItemId { get; set; }
    public string TestItemName { get; set; }
    public Department Department { get; set; }
    public int DepartmentId { get; set; }
    public DateTime CreatedOn {get; set; }
    public string CreatedBy {get; set; }
}

public class Patient
{
    public int PatientId { get; set; }
    public string PatientName { get; set; }
    public DateTime CreatedOn {get; set; }
    public string CreatedBy {get; set; }
}

问题是,每次创建表时,我都必须重复添加这两列。

但我想要这样-

public class EntryLog
{
   public int EntryLogId { get; set; }
   public DateTime CreatedOn {get; set; }
   public string CreatedBy {get; set; }
}

public class Department
{
    public int DepartmentId { get; set; }
    public string DepartmentName { get; set; }
    public string Description { get; set; }

    public EntryLog EntryLog { get; set; }
    public int EntryLogId { get; set; }
}

and so on...

class A { .. }

class B { .. }

但是在为部门或患者创建行时会出现问题 [显示与其他表的外键冲突错误]。

在 EF 核心中,有 Table Per Hierarchy (TPH),但在这种情况下,每个表都将合并为一个表。但这并没有给我任何解决方案。

期待专家的建议……

【问题讨论】:

    标签: entity-framework inheritance asp.net-core-mvc


    【解决方案1】:

    底线是:使用EntryLog 作为基本类型,不要告诉EF。让 EF-core 忽略基本类型很容易:只注册派生类型。这样做,EF-core 会将您的子类型映射到它们自己的表中,就像它们没有通用类型一样。

    现在EntryLog 将不再需要Id,它应该是抽象的:

    public abstract class EntryLog
    {
        public DateTime CreatedOnUtc { get; set; }
        public string CreatedBy { get; set; }
    }
    

    这是否足够取决于您的具体要求。有几种可能。

    1。无需额外配置

    如果您对 EF 将应用于公共属性的默认约定感到满意,那么您就完成了。 CreatedOnUtc 将映射到 DateTime2 列(在 Sql Server 中),CreatedBy 将映射到每个表中的 nvarchar(max) 列,用于 EntryLog 实体。

    但是,如果您确实需要自定义配置 - 例如,如果您想将 CreatedBy 映射到 nvarchar(50) 列 - 应该应用额外的映射说明。当然,您仍然希望只对公共属性进行一次映射——如果您确实在 TPH 方案中映射了基本类型,也会发生这种情况。该怎么做?

    2。基类型中的数据注解

    最简单的选择是添加数据注释:

    public abstract class EntryLog
    {
        public DateTime CreatedOnUtc { get; set; }
    
        [MaxLength(50)]
        public string CreatedBy { get; set; }
    }
    

    仅此而已。

    但有些开发团队不希望将数据注释用于映射指令。此外,EF 的流畅映射提供了比数据注释更多的选项。如果数据注释由于某种原因不符合要求,则必须应用流畅的配置。但是,您仍然只想配置公共属性一次。一种可行的方法是为每个EntryLog 使用IEntityTypeConfigurations,并让每个具体配置都派生自一个基类。这提供了另外两种选择。

    3。基类包含常规属性

    选项 4 将说明我为什么在这里谈论“常规属性”。这是它的样子:

    abstract class EntryLogConfiguration
    {
        public void ConfigureBase<TEntity>(EntityTypeBuilder<TEntity> builder)
            where TEntity : EntryLog
        {
            // Just an example of how to configure a base property.
            builder.Property(e => e.CreatedBy).HasMaxLength(50);
        }
    }
    
    class DepartmentConfiguration : EntryLogConfiguration, 
        IEntityTypeConfiguration<Department>
    {
        public void Configure(EntityTypeBuilder<Department> builder)
        {
            builder.Property(p => p.DepartmentName).HasMaxLength(100);
            ConfigureBase(builder);
        }
    }
    

    在上下文中:

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

    4。使用阴影属性

    Shadow properties 是 EF-core 的新特性。

    影子属性是未在 .NET 实体类中定义但在 EF Core 模型中为该实体类型定义的属性。这些属性的值和状态纯粹在 Change Tracker 中维护。

    假设您希望将 CreatedBy 作为类属性(因为您想在 UI 中显示它),但只需要 CreatedOnUtc 作为在后台设置且不应公开的属性。现在EntryLog 将如下所示:

    public abstract class EntryLog
    {
        public string CreatedBy { get; set; }
    }
    

    所以CreatedOnUtc 的属性消失了。它已作为影子属性移至基本配置:

    abstract class EntryLogConfiguration
    {
        public void ConfigureBase<TEntity>(EntityTypeBuilder<TEntity> builder)
            where TEntity : EntryLog
        {
            builder.Property(e => e.CreatedBy).HasMaxLength(50);
            builder.Property<DateTime>("CreatedOnUtc");
        }
    }
    

    现在你不能直接设置CreatedOnUtc,只能通过EF的变化跟踪器。最好的方法是在上下文中覆盖 SaveChanges

        public override int SaveChanges()
        {
            foreach (var entry in ChangeTracker.Entries<EntryLog>())
            {
                entry.Property<DateTime>("UpdatedOnUtc").CurrentValue = DateTime.UtcNow;
            }
            return base.SaveChanges();
        }
    

    当然,如果UpdatedOnUtc 是一个常规属性,这个覆盖也会派上用场,但你可以这样做

                entry.Entity.CreatedOnUtc = DateTime.UtcNow;
    

    我希望这会给您足够的思考,以找出最适合您的选项。

    【讨论】:

    • 太棒了!感谢 Gert Arnold 的精彩解释。根据您的详细回复,我已经解决了问题。
    • 我通常也为这些场景使用基类,效果很好。我还喜欢从我的业务逻辑中完全隐藏实体框架信息。我的业务模型通过接口定义其状态,并通过特定实现将其注入其中。现在我的业务模型不知道它是 EFCore、MongoDb、内存中的字典还是提供其状态的任何东西。
    猜你喜欢
    • 2010-12-22
    • 2013-12-14
    • 2019-05-20
    • 1970-01-01
    • 2010-11-16
    • 2012-06-15
    • 2020-07-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多