3层架构中的实体关系验证

时间:2013-03-04 12:43:26

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

我正在开发一个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类型。 让我感到困惑的是,当他们拥有其他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。我提供了一个“具体”的例子,但我正在寻找一个更通用的解决方案。谢谢!

3 个答案:

答案 0 :(得分:2)

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

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

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

因此,Person可能包含Customer列表(或您网域中的任何内容)。用户可能具有以下结构:

Users

就这么简单。现在你不用再担心public class User{ public int PersonId { get; set; } } 了,因为它在其他地方得到了维护。因此,尝试将其他聚合根表示为相关AR中的值对象。

答案 1 :(得分:1)

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

答案 2 :(得分:1)

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

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

  

确保所有用户(,如果在客户对象上设置)是   验证

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

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

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

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

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