【问题标题】:Entity relationship validation in 3-tier architecture三层架构中的实体关系验证
【发布时间】:2013-02-18 13:26:51
【问题描述】:

我正在开发一个 ASP.NET MVC C# 应用程序,其中 MVC 应用程序作为 3 轮胎应用程序中的 UI 层,EF CodeFirst 5 作为 DAL。

DAL -> DTL
DTL <- BLL -> DAL
DTL <- UI -> BLL

箭头表示用途。 (所以 DAL 使用 DTL 等等...)

我使用 POCO 作为数据传输对象,并根据 EF Code First 进行数据相关验证。 我的 POCO 有关系,而关系中的 POCO 也有关系。很标准...

BL 中的所有类都根据单一职责规则处理单一 POCO 类型。 令我困惑的事情是,当 POCO 的关系是其他 BL 关注的(以保持单一责任)时,我如何在 POCO 上应用业务规则和验证?

我将尝试给出一个简化的示例:(请将此作为伪代码阅读)

public class Customer{
    public int Id { get; set; }
    public string Name { get; set; }
    public virtual Enumerable<Person> Users { get; set; }
}

public class Person{
    public int Id { get; set; }
    public string Name { get; set; }
    public Customer BelongsTo { get; set; }
    public virtual Enumerable<Property> Properties { get; set; }
}

public class Property{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Value { get; set; }
}
public class CustomerBL{

    private DALContext = new DALContext();        

    public void Add(Customer customer){
        DALContext.Customers.Add(customer);
        DALContext.SaveChanges();
    }
}

public class PersonBL{

    private DALContext = new DALContext();        

    public void Add(Person person){
        if(person.Properties.Count() < 3)
             throw new ApplicationException("Each person must have at least three properties");

        DALContext.People.Add(person);
        DALContext.SaveChanges();
    }
}

所以我的问题是,在添加新客户时,如何确保所有用户(如果在客户对象上设置了任何用户)都按照 PersonBL 的 Add 方法中的说明进行验证,而不会破坏单一责任规则 (或者至少以一种好的方式打破它)?

请记住,Person 可以拥有其他关系,这些关系具有其他关系,并且都具有特定的 BL 和业务规则。我提供了一个“特定”示例,但我正在寻找更通用的解决方案。谢谢!

【问题讨论】:

    标签: .net validation architecture orm domain-driven-design


    【解决方案1】:

    您可能需要查看您的聚合根设计。

    请记住,AR 应始终处于一致状态。如果您发现需要验证不应该验证的内容,那么您的聚合根扩展得太远了。当您从导航的角度考虑您的对象模型时,可能会发生这种情况:我想从 A 到 B,然后到 C。这是一个很容易出错的地方。如果您不使用域模型进行查询,则不需要导航。延迟加载是使用域模型进行查询/导航的另一个症状。

    在您的示例中,Customer 有一个名为 UsersPerson 实体列表。 Person 似乎是另一个 AR。 AR 不应包含其他 AR 的实例,因为这会使您的生活变得痛苦。打破这种联系。

    所以Customer 可能有一个Users 列表(或在您的域中有意义的任何内容)。一个用户可能有这样的结构:

    public class User{
        public int PersonId { get; set; }
    }
    

    就这么简单。现在您不必再担心Person,因为它在其他地方维护。因此,请尝试将其他聚合根表示为相关 AR 中的值对象。

    【讨论】:

      【解决方案2】:

      建立正确的关系方向非常重要。

      一个好的方法通常是消除实体类中的集合属性。在您的特定情况下,这些是Customer.UsersPerson.Properties。让我们关注Customer.Users。创建Users 属性,我们指出用户列表对于Customer 实体是必不可少的。但随后我们看到了一个矛盾

      确保所有用户(如果在客户对象上设置了任何用户) 验证

      我们看到客户可以在没有用户列表的情况下存在。现在的问题是User 可以在没有Customer 的情况下存在。可能不是(如果是,可能是不同类型的用户)。因此,翻转关系的方向将为User 实体创建一个Customer 属性,并为Customer 实体消除Users 集合属性。

      这将改进您的 POCO 的设计,因为有些人可能会认为集合属性不适合 POCO,尤其是虚拟成员。

      所以,我们可以改进设计,但我们应该如何验证关系本身呢?答案是我们需要一个不同的实体来执行这个任务。这可能是CustomerRegistrationJournal,例如它可能有一个方法RegisterCustomer,有两个重载:一个在没有关联用户的情况下采用Customer 实体,另一个采用Customer 和相关User 帐户的列表。此方法将为每个实体调用验证并调用 DAL。这样,它将控制创建两种类型实体的事务。

      在这种情况下,DAL 应建模为在引用 CustomerUser 列上强制执行外键约束。这样,它将提供弱关系。这意味着加载User 不会加载Customer,不会有级联更新或删除。 User 实体将有一个IdCustomer,与User 表相同。这个Id 对于User 是必不可少的,但Customer 不是User 的一个组件。当然,您可以将其建模为将整个 Customer 作为属性,但恕我直言,这将是不必要的开销(即使使用 NHibernate 延迟加载)。

      不同的设计策略应该应用建模Person.Properties。由于某种原因,Person 的某些属性必须由Propertyvalue objects 的列表表示。没有实体,值对象就不存在。值对象是其他实体的组件,它们必须与实体一起加载和更新。这是一个强大的一对多关系。这就是需要充分发挥 ORM 潜力的地方。

      【讨论】:

      • 感谢您的回复。我明白你的观点并同意你所说的,但这是否意味着我不能使用 EF 或具有相同关系模式的任何 ORM 的“全部”潜力?那么基本上在任何实体中描述任何关系有什么意义呢?
      • 我明白你的意思,这就是我过去一直这样做的方式。我正在尝试查看 ORM 在具有“真实”/ 3 层架构的主要应用程序中是否有任何用途?它完全可行吗? (没有“降低实体等级”)我认为带有迁移的 EF Code First 是一种很好的方法,使用它的所有功能会非常好。但是在考虑更复杂的系统(比我上面举例说明的更复杂)中的业务规则验证和实体的正确性时,使用它们是一个挑战。
      【解决方案3】:

      创建一个单独的验证器类来进行验证怎么样?然后,当您需要对特定实体进行验证时,您可以将该工作传递给验证器,然后验证器将返回打破规则。这将允许您不仅在 Person 存储库中重用验证器,还可以在其他可能添加 parson 作为工作单元的一部分的区域中重用验证器。这种技术已在多个业务对象框架中使用,我已经看到这些框架使验证独立于其他主要业务逻辑,并且还允许更轻松的测试。 Here's a good post on the subject.

      【讨论】:

      • 感谢您的回复,但我有点担心此解决方案可能暗示的循环验证。例如。如果客户存储库将通过客户验证器验证客户,那么客户验证器必须通过个人验证器验证用户(个人),而个人验证器又必须通过客户验证器验证 BelongsTo 属性,并且循环循环,死锁。我怎样才能避免这种情况?
      • 你想在那里验证什么?验证器单独用于每个实体,但您不一定要让它递归地触发验证。由于您使用的是 EF CodeFirst,因此您还可以创建自己的 System.ComponentModel.DataAnnotations.CustomValidationAttribute 并使用验证注释您的属性,以保持独立。起初我没有注意到您使用的是 Codefirst,但这是更好(首选)的属性验证方法。您是否阅读过 EF Codefirst 中的 DataAnnotations?
      • 我正在使用 DataAnnotations 来验证实体(不在示例中),但我正在尝试验证/应用 DataAnnotations “不能”用于的业务规则。这是与整个实体及其关系有关的验证,而不是与实体每个属性有关的验证
      • 哦,那我觉得把那些放到仓库里就好了。存储库不一定必须与 DAL 中的表 1:1 映射。如果您对实体本身进行了实体验证,然后实际 DbContext 检查外键关系,那么我觉得将任何额外的东西放入存储库中是完全可以的,甚至可以使用 UnitOfWork 模式包含/重用多个存储库。 (asp.net/mvc/tutorials/getting-started-with-ef-using-mvc/…)
      • 我实际上正在使用该模式。但这不包括业务规则,如果我将业务规则验证放在存储库中,那么它不是在错误的地方吗?也许我最初的问题具有误导性,但这就是为什么我选择不把这些信息放在那里,因为我想避免它与模式有关,更多地与业务规则的验证有关,而不是破坏 SOLID 中的 S在主要方面:)
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-01-31
      • 1970-01-01
      • 2011-03-16
      • 1970-01-01
      • 1970-01-01
      • 2011-06-02
      相关资源
      最近更新 更多