领域实体跟踪数据的最佳实践是什么?使用基类还是组合?

4

大多数大型项目的一个共同点是需要在许多领域实体上跟踪常见的数据。例如,大多数大型项目会跟踪许多领域实体的以下属性:

DateTime DateCreated
User CreatedBy
DateTime LastModified
User LastModifiedBy

这些数据相当自我解释,用于跟踪对域对象做了什么以及何时做的操作。
问题是,在设计大型应用程序的域模型时,如何处理此跟踪数据最佳。
经典方法是使用基类,然后让相关的域类从该基类继承。但这引发了我的优先使用组合而非继承警报。我参与的更多大型项目,越来越拒绝继承,但这并不意味着有时候它不是最好的选择,例如在这种情况下。另一种继承解决方案是使用接口,但虽然这种解决方案耦合性较低,但我没有看到在域实体上使用此方法的示例。
第二种方法是使用组合为每个域实体添加某种跟踪对象。唯一的问题是,数据层必须明确地指示不将其表示为单独的表。这是一个小任务,但如果没有回报,很难证明其价值。
最后一种处理跟踪数据的方法是配置数据层以实现透明处理。我认为可能可以使用Entity-Framework来实现这一点,但由于过去没有实施过这个解决方案,这将是最前期时间密集型的解决方案。很难预见这种解决方案是否值得麻烦。
虽然这个问题可能看起来客观,但实际上大多数大型项目都必须以某种方式处理它。
如何设计一个域模型和/或大型项目以跟踪元数据?

1
对于组合,您可以添加一个复杂类型,它将映射到同一表中的列(即不是单独的表)。 - qujck
2个回答

2

最佳实践通常是主观的,并且可能会引起与解决同样多的问题。什么时候应该继承?什么时候应该组合?学者们花费多年时间就问题的细节进行争论。具有简单接口的基本继承是实用和有效的。如果对于所有您的实体来说这是一个标准功能,则继承可能是更好的选择。

我有一个包含审计属性并实现了这些属性接口的基类。 我使用以下代码拦截调用context.SaveChanges()。 这很简单并且有效。 如果任何跟踪的实体未实现IAudit接口,它可以扩展到失败。

    public override int SaveChanges()
    {
        var entities = this.GetChangedAuditDataEntities();

        foreach (var entity in entities)
        {
            this.SetModificationInfo(entity);
        }

        return base.SaveChanges();
    }

    private IEnumerable<IAuditData> GetChangedAuditDataEntities()
    {
        return (
            from entry in _context.ChangeTracker.Entries()
            where entry.State != EntityState.Unchanged
            select entry.Entity)
            .OfType<IAuditData>();
    }

    private void SetModificationInfo(IAuditData entity)
    {
        entity.lastModifiedBy = _currentUser.Name;
        entity.lastModified = System.DateTime.Now;
    }

这是一个没有自动“正确”答案的问题。如果Jon Skeet回答它,那么它将被视为最佳实践。唯一其他正确的答案将需要确认您特定的偏见或至少打出正确的智力音符。

多年前,继承很盛行,其中之一的许多反弹是“价值组合优于继承”的口号。好吧,但继承有它的位置。

我建议任何具有许多相同类型对象的架构层,例如域对象(单词“域对象”暗示了共同层),可以通过具有公共基类来大大增强。 System.Object 是一个很好的例子。我再给你一个例子。当我们为解决方案定义异常装饰器时,我们决定扩展 ToString() 方法以创建一个可以唯一标识对象的值。

public override string ToString()
{
    if (this is IAuditData)
    {
        IAuditDataidentifiable = this as IAuditData;
        return string.Format(@"{0} {{ id: {1}, ETag: {2} }}",
            identifiable.GetType().Name,
            identifiable.id,
            identifiable.ETag);
    }
    else return base.ToString();
}

7行代码 - 实用、简单和有效。从您的问题中可以听出您完全反对继承,这往往会带来许多困难。我也是,但我仍然坚称在这种情况下它是更好的选择。


虽然我对最佳实践持有不同的看法,但我能理解你的观点,它们可能会导致困境。然而,如果大多数,但不是所有领域实体都需要此跟踪数据,您仍然会使用基类吗? - Mark Rogers
1
是的,但我只会继承那些需要审计数据的类。 - qujck
你是否为所有实体使用基类(entity)?这会使你的查询变得非常缓慢,因为它将连接继承树中的所有表。 - Jehof
我们使用自定义的T4模板创建类/映射/数据库,因此每个实体只有一个表。继承仅在对象层中进行。 - qujck

0

我喜欢qujck在他的回答和评论中所说的内容,但我想补充一些内容。如果跟踪信息代表基础架构概念,我会将它们放在负责审计或日志记录等基础架构层中。否则,如果它们是领域概念,我真的很喜欢将它们保留在领域模型层中,并且在这种情况下,我将使用一个复杂对象(值对象)来表示它们,并减轻手动设置跟踪对象时的负担。我将定义一个方法,由我的工厂或IoC容器在请求跟踪对象时调用,我的观点是集中变化并将概念保留在它们所属的位置。


1
我同意工厂/IoC的观点,但Entity Framework不支持使用自己的容器。可以捕获基础ObjectContext.ObjectMaterialized事件,但我更喜欢让IoC容器注入一个装饰类来管理审计日志 - 我有一个调用context.SubmitChanges的UnitOfWork类和一个为UnitOfWork管理审计日志的装饰器。 - qujck

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接