【问题标题】:Entity Framework Code First Common Database Audit Fields实体框架代码优先公共数据库审计字段
【发布时间】:2013-05-25 02:05:20
【问题描述】:

我是 Entity Framework 的新手,过去我直接使用 Enterprise Library 或 ADO.NET 将模型映射到数据库表。我使用的一种模式是将出现在每个表中的公共审计字段放在基类中,然后为每个对象继承该基类。

我采取两个步骤来保护两个字段(Created、CreatedBy):

  1. 在 Base Enitity 上拥有一个私有的无参数构造函数,并创建第二个构造函数,该构造函数需要在创建时传递 Created 和 CreatedBy。
  2. 将设置器设为私有,以便在创建对象后无法更改值。

基类:

using System;

namespace App.Model
{
    [Serializable()]
    public abstract class BaseEntity
    {
        public bool IsActive { get; private set; }
        public DateTimeOffset Created { get; private set; }
        public string CreatedBy { get; private set; }
        public DateTimeOffset LastUpdated { get; protected set; }
        public string LastUpdatedBy { get; protected set; }

        private BaseEntity() { }

        protected BaseEntity(DateTimeOffset created, string createdBy)
        {
            IsActive = true;
            Created = created;
            CreatedBy = createdBy;
            LastUpdated = created;
            LastUpdatedBy = createdBy;
        }
    }
}

继承类:

using System;

namespace App.Model
{
    [Serializable()]
    public class Person : BaseEntity
    {
        public int Id { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string Email { get; set; }

        public Person(DateTimeOffset created, string createdBy) : 
            base(created, createdBy) {  }
    }
}

我在这两个方面都遇到了问题。 EF 需要无参数构造函数来创建对象。 EF 不会创建具有私有 setter 的数据库列。

我的问题是是否有更好的方法来实现我的 EF 目标:

  1. 要求在实例化时填充 Created 和 CreatedBy 的值。
  2. Created 和 CreatedBy 的值无法更改。

【问题讨论】:

  • 还有另一种选择:在您的上下文中覆盖 SaveChanges 并在那里执行所有与审计相关的操作(设置新实体的创建日期、设置更新实体的修改日期等)。这一切都将在保存之前完成。
  • 我会实现一个接口IAuditiable,这样你仍然可以拥有不应该被审计的实体,而那些你应该可以在你的 SaveChanges() 调用中实现特定于该接口的逻辑的实体,例如如果那是您想要的,请在以下答案之一中提出建议。
  • @FRoZeN,您不能在接口上使用非公共设置器,这样就达不到我的第二个目标。

标签: c# entity-framework oop entity-framework-5


【解决方案1】:

您可以使用接受字符串createdBy 的构造函数来实例化上下文。然后覆盖SaveChanges():

public override int SaveChanges()
{
    foreach( var entity in ChangeTracker.Entries()
                                        .Where(e => e.State == EntityState.Added)
                                        .Select (e => e.Entity)
                                        .OfType<BaseEntity>())
    {
        entity.SetAuditValues(DateTimeOffset.Now, this.CreatedBy);
    }
    return base.SaveChanges();
}

SetAuditValues()

internal void SetAuditValues(DateTimeOffset created, string createdBy)
{
    if (this.Created == DateTimeOffset.MinValue) this.Created = created;
    if (string.IsNullOrEmpty(this.CreatedBy)) this.CreatedBy = createdBy;
}

实体从数据库中具体化后,当有人调用SetAuditValues 时,值不会被覆盖。

【讨论】:

  • 这看起来很接近,但我仍然有未创建列的问题,因为设置器是私有的。我可以使它们受到保护,但随后有人可以调用 Person.Created = new-value。由于 SetAuditValues,它不会被保存到数据库中,但在保存之前它会在内存中处于无效状态。
  • 你应该可以通过流畅的映射将属性添加到模型中,不是吗?
  • 是和否——您必须使用别名只读字段来解决使用流畅映射的 Private Setter 问题。解决方案有点麻烦。
  • 我的模型与数据项目(包含上下文)不同。这意味着我必须将 SetAuditValues 函数设为 Public,以便任何进程都可以更新审计字段。
  • 虽然这是一种有趣的方法,但我看到的问题是 Created 和 CreatedBy 属性的值可以在调用 SetAuditValues() 之前设置,因为它们不能设置为私有。然后传递给 SetAuditValues() 的参数将被忽略,因为实体属性已经设置。
【解决方案2】:

您不应尝试直接在实体/数据层上控制访问权限,而应在应用层中执行此操作。这样您就可以更好地控制用户可以做什么。

此外,您可能希望将审计记录存储在另一个表中,而不是在每个表上重复审计字段。这很容易用代码先完成:

public class AuitRecord
{
    public bool IsActive { get; set; }
    public DateTimeOffset Created { get; set; }
    public string CreatedBy { get; set; }
    public DateTimeOffset LastUpdated { get; set; }
    public string LastUpdatedBy { get; set; }
}

然后您将基类与审计记录链接到它:

public abstract class BaseEntity
{
    public AuditRecord Audit { get; set; }
}

最后是你的实体

public class Person : BaseEntity
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Email { get; set; }
}

您无法通过以下方式访问审计数据:

Person.Audit.IsActive 

【讨论】:

  • 很有趣,但它不符合要求在创建对象时填充 Created 和 CreatedBy 的目标。此外,拥有一个单独的 Audit 表意味着您需要一个可以引用任何其他实体键的键(未显示在 AuditClass 中)。这不是第三范式。我遇到的另一个常见审计表问题是性能问题,因为它成为了性能瓶颈。
  • @Josh:您实际上可以通过向 AuditRecord 类添加一个键并让所有其他实体包含一个 List&lt;AuditRecord&gt;(这将与所有您的被审计实体和审计表)。这只会成为插入和更新的瓶颈(无论如何都应该优化为尽可能快),因此根据您的要求/实现,这可能不是问题。
猜你喜欢
  • 2013-07-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-01-06
  • 2013-09-22
  • 2012-03-15
相关资源
最近更新 更多