【问题标题】:POCO - Flatten or Use Complex TypesPOCO - 展平或使用复杂类型
【发布时间】:2014-02-28 21:47:55
【问题描述】:

我在 ASP.Net MVC 项目中使用 POCO 作为我的模型类。到目前为止,这运行良好,但在大多数情况下,这些 POCO 包含简单类型。我现在有一种情况,我正在考虑使用一些复杂的类型,但我不确定这种方法的好处。此外,由于我需要将单个数据库表拆分为多个 POCO,我的情况变得更加复杂。下面的例子将解释。我有一个包含大约 40 个字段的员工表。我不想总是传递这个带有所有属性的大对象,所以我创建了 3 个 POCO 类,它们代表数据的逻辑分组,如下所示:

public class EmployeeProfile
{
    public int EmployeeID { get; set; }
    public string FirstName { get; set; }        
    public string LastName { get; set; }
    public string Gender { get; set; }
    etc ...    
}

public class EmployeeContact
{
    public Address Address { get; set; }
    public PhoneNumber Phone { get; set; }        
    public string Email { get; set; }
}

public class EmploymentInfo
{
    public decimal Salary { get; set; }       
    public string Occupation { get; set; }
    etc ....
}

到目前为止一切都很好,但是我有时也确实需要将整个员工表作为一个对象,这就是我不确定的地方。我可以通过将其他类型放入其中来创建员工类,如下所示:

public class EmployeeDetail
{
    public EmployeeProfile EmployeeProfile { get; set; }
    public EmployeeContact EmployeeContact { get; set; }
    public EmploymentInfo EmploymentInfo { get; set; }        

}

或者我可以将三个 POCO 的所有属性单个属性(包括其中包含的复杂类型)复制到 EmployeeDetail 类中,使其平坦,具有如下简单属性:

public class EmployeeDetail
{
    public int EmployeeID { get; set; }
    public string FirstName { get; set; }        
    public string LastName { get; set; }
    public string Gender { get; set; }
    etc ...        
    public Address Address { get; set; }
    public PhoneNumber Phone { get; set; }        
    public string Email { get; set; }
    public decimal Salary { get; set; }       
    public string Occupation { get; set; }
    etc ....

}

除了包含复杂类型的版本需要更短的时间来创建 POCO 之外,使用一种方法比另一种方法有什么好处?我认为重点是面向对象,但我不确定在这种情况下“嵌套”这些对象是否有任何好处。在我看来,访问嵌套对象并在我的视图模型和域模型之间来回转换它们似乎会有更多的工作。

编辑:我想指出,我正在考虑的子模型并不特定于特定视图。它们很可能在整个应用程序中的多个地方使用。这是一个人力资源/员工福利应用程序。我需要这些类是可重用的,否则我会把它们放在我已经在使用的 ViewModel 中。

【问题讨论】:

    标签: c# asp.net-mvc poco cqrs complextype


    【解决方案1】:

    您实际上想要做的是将您的业务概念Employee 划分为 3 个部分,基于它们将被使用的视图。因此,您想将其拆分为 EmployeeProfile 以在与例如连接的视图中显示它档案管理等

    通过这样做,您将业务域与视图域混合在一起 - 这是您应该避免的。

    拆分域模型是一个很好的解决方案,以防出现逻辑域拆分 + 很有可能单独需要某些部分。所以一个很好的例子可能是(虽然不是在每种情况下都必须)为AddressEmployee 定义一个单独的域模型。

    如果我处于你的位置,我会怎么做:

    • 我会留下一个域模型Employee 而不将其拆分为与视图相关的部分。
    • 我会根据我在某个视图上需要Employee 模型数据的哪一部分来创建单独的视图模型。
    • 我想我是否可以将Employee 模型拆分为一些与域相关的子模型(例如,将地址与Employee 分开)。

    从性能的角度来看,上述解决方案也可能是有益的。向数据库发送几个额外的字段并没有那么大的麻烦,而在需要整个子模型的情况下需要加入子模型在某些情况下可能会损害您的性能(尤其是在有数千名员工的情况下)。

    更重要的是:从可维护性的角度来看,我还建议使用上述解决方案。从领域的角度来看,如果使用过多的嵌套对象并不合适,可能会带来一些额外的挑战,例如CRUD 操作(例如,在保存/更新整个 Employee 等时需要提供提交/回滚机制)。

    【讨论】:

    • 我已经在使用 ViewModels。我的观点不是我需要视图的“子模型”,而是我不想一直传递这 40 个属性类。我认为将 Employee 模型的一部分视为域模型的合法方面是非常合理的。它在哪里说只有“整个”实体应该在域中?
    • 我已经更新了我的答案并添加了更多解释。
    • 在我的场景中不需要模型加入,因为这是在 DAL 中完成的。我正在使用 SQL 查询来填充我的对象。所以我需要的所有属性都来自一个查询,我一次填充所有子模型。
    • 我编辑了我的问题。拆分模型的一个原因是可重用性。我需要在可能的地方单独的类,而不仅仅是一个特定的视图。这与您的答案部分相符,即当很有可能单独需要某些部分时,提到子模型是有意义的。确实是这样,我在最初的问题中没有说清楚。
    【解决方案2】:

    @bejger 有一个很好的答案,但我想补充一点,有点太长了,需要评论。

    划分数据是个坏主意。无论您的表是否包含 40 个属性或 400 个属性,查询单个表总是比进行连接更好。现在,我刚才所说的有一些警告。可以重复使用的数据的逻辑分组应该被分解。将地址之类的东西组合成一个需要该信息的类比一遍又一遍地重复更有意义。

    不过,对于组合,您有两种选择:

    1. 复杂类型:复杂类型允许您抽象出一组要在多个类上使用的属性,但最终,每个表都有自己的副本那些属性。这样做的好处是您不需要使用连接来访问数据,但它的限制是您不能包含外键或其他导航属性。

    2. 外键:使用外键可以让您拥有一个表来保存该属性组的所有实例。同样,缺点是需要连接。如果您可以预见与这些属性进行交互,或者您需要它们的一对多集合,这确实是唯一合适的选择。以地址为例,如果您希望能够只提取所有地址而无需担心员工关联,或者您希望能够为员工分配可变数量的地址,那么这是一个可行的选择。

    除此之外,不要担心您的桌子大小。即使是包含大量列的表仍然比连接更有效,如果您只需要这些列的子集,您可以简单地选择您需要的列(您可以在 LINQ 中使用 Select 方法执行此操作,该方法是 Entity Framework查询数据库时会遵守)。

    【讨论】:

    • 请看我的编辑。这些确实是我的应用程序中许多地方都需要的可重用子模型。这在我最初的问题中并不清楚。
    • 那么,您就有了答案。请参阅我关于是否使用复杂类型或外键的指南,但如果您想重用它们,则需要其中一个。
    【解决方案3】:

    最终,我没有将我的领域模型类拆分为子组件,而是采用了一种完全不同的方法,并选择将我的读取模型与我的领域模型分开,或者换一种方式将 R 与 CUD 分开。我这样做的原因是因为我的应用程序中有 65-75% 是关于读取和报告数据,而不是创建、更新或删除数据。

    所以在这种情况下,我的域实体将是没有组合的完整 Employee 模型:

    public class Employee
    {
        public int EmployeeID { get; set; }
        public string FirstName { get; set; }        
        public string LastName { get; set; }
        public string Gender { get; set; }
        etc ...        
        public Address Address { get; set; }
        public PhoneNumber Phone { get; set; }        
        public string Email { get; set; }
        public decimal Salary { get; set; }       
        public string Occupation { get; set; }
        etc ....
    }
    

    除了读取端的这个之外,我可能还有以下内容:

    public class EmployeeRoster
    {
        public int EmployeeID { get;set;}
        public string FullName { get;set; }   
        public decimal Salary { get;set; }       
        public string Occupation { get;set;}
        public DateTime DateOfHire { get;set;}
    }
    

    以及我在读取端需要的任何其他对象。这听起来很像 CQRS(Command Query Responsibility Segregation),我知道 CQRS 有一段时间了,但是我在 CQRS 上看到的资源令人失望。例如,通常建议使用不同的数据库进行读取操作和写入操作。虽然我理解为什么人们可能想要这样做(性能),但出于我的目的,这在这一点上是矫枉过正的。此外,CQRS 上的大多数资源都建议将读取查询直接映射到视图模型。我有几个问题。一,在很多情况下我需要重用这些读取模型。像 EmployeeRoster 这样的东西在我的应用程序中用于不同目的的多个地方。此外,在某些情况下,我需要在读取对象被视图模型使用之前对其应用业务逻辑。

    总而言之,在这一点上,我确定我需要一个单独的读取模型,但我不一定相信正式实践的 CQRS 是正确的方法。所以我在这一点上已经回答了我自己的问题,但没有接受这个作为我的答案,因为我认为其他在单独读取模型和/或 CQRS 方面有更多经验的人可以提供比我更好的答案。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-01-15
      • 1970-01-01
      • 2022-01-24
      • 1970-01-01
      • 1970-01-01
      • 2017-06-13
      相关资源
      最近更新 更多