C#中的类链接最佳实践
本文关键字:最佳 链接 | 更新日期: 2023-09-27 18:26:16
首先,EF不是我们开发环境的一个选项,所以请不要回答"只使用EF"。。。
我认为这是一个相当标准的困境,所以我相信大多数Pros都会有一种我没有偶然发现的方法。。。所以我在这里希望你们能告诉我它是什么。
假设您有以下数据库表:
tblCompanies
ID
NAME
tblDepartments
ID
COMPANY_ID
NAME
tblEmployees
ID
DEPARTMENT_ID
FIRSTNAME
LASTNAME
在代码中的类中表示这一点的最佳方式是什么?
我认为最好的方式是这样的:
public class Company
{
public int ID { get; set; }
public string Name { get; set; }
public List<Department> Departments { get; set; }
}
public class Department
{
public int ID { get; set; }
public string Name { get; set; }
public List<Employee> Employees { get; set; }
}
public class Employee
{
public int ID { get; set; }
public string FirstName { get; set;}
public string LastName { get; set; }
}
我相信这就是"OOP正确的方法"。然而,似乎总是会发生这样的事情:
public class Department
{
public int ID { get; set; }
public string Name { get; set; }
public int CompanyID { get; set; }
public List<Employee> Employees { get; set; }
}
主要是因为当你从数据库中提取一个Department时,你只会有Company ID,而不是完全填充Company类实例所需的所有其他属性。
(我在这里使用了一个非常普通的例子,但我在当前项目中实际处理的这个例子有3个字段,它用来将数据链接在一起,所以在几个类中拥有相同的3个字段的想法对我来说似乎是错误的)
这些场景是否有最佳实践?尽管我不喜欢出于懒惰而将相同的数据存储在多个类中,但我也不喜欢返回一个只填充了一个字段的类的实例,因为这就是我当时所拥有的。
这是一个常见的问题,也是ORM试图解决的问题。当然,这并不容易,这取决于您的想要是什么以及的限制是什么。
只有两个基本选项可以保留一份信息。按照请求懒散地加载数据,或者从一开始就全部加载(贪婪加载)。否则,您必须复制数据。
使用惰性加载,您基本上可以进行设置,以便在导航到属性时调用数据库,并获取加载表示您正在访问的属性的实体所需的信息。这个问题的棘手之处在于SELECT N+1问题。当您最终迭代一组父实体并在每个子实体上触发延迟加载时,就会遇到这个问题,从而导致对数据库的N+1调用来加载一组实体(1)及其子实体(N)。
贪婪加载基本上说加载所有你需要开始的东西。ORM(它们工作的地方)很好,因为它们通过LINQ处理了许多细节,并创建了可执行和可维护的解决方案,通常还允许您操纵贪婪和懒惰加载的使用。
另一个重要的问题是多对多的关系。您需要确保没有循环初始化,并获得循环依赖关系的所有包袱。我肯定还错过了很多。
在我看来,我不太确定是否有最佳实践,就像有实践一样,其中一些是糟糕的——没有什么是完美的。您可以:
-
开始滚动您自己的对象关系映射器,从而消除重复的ID
-
使用一个更轻的ORM框架来处理其中的一些,允许您消除重复的ID
-
创建专门的查询以加载数据聚合,从而消除重复的ID(*咳嗽*DDD)
-
只需像上面提到的那样保留ID的重复,就不用担心在域中创建显式关系模型。
这个是在上,您可以根据您的约束条件选择最佳选项。这是一个很深的话题,我的经验有限所以我说的话要谨慎一些。
我认为这类事情没有"最佳实践"手册,当然这取决于如何使用您的类。但根据我个人的经验,我最终采用了这种方法:
public class Company
{
public int ID { get; set; }
public string Name { get; set; }
public IEnumerable<Department> GetDepartments()
{
// Get departments here
}
}
public class Department
{
public int ID { get; set; }
public string Name { get; set; }
protected int CompanyID { get; set; }
private Company _Company;
public Company Company
{
get
{
// Get company here
}
}
public IEnumberable<Employee> GetEmployees()
{
// Get employees here
}
}
public class Employee
{
public int ID { get; set; }
public string Name { get; set; }
protected int DepartmentID { get; set; }
private Department _Department;
public Department Department
{
get
{
// Get department here
}
}
public IEnumberable<Employee> GetEmployees()
{
// Get employees here
}
}
在某些情况下,我将类的一些"导航"属性公开为public
(如CompanyID和DepartmentID),以防止实例化新类以获得已经加载的值。
正如其他人所指出的,您也可以模拟"延迟加载",但这需要您的部分付出一些额外的努力。
我认为这取决于需求。你需要向上遍历吗(从部门获得公司,从员工获得部门,等等)。如果你这样做了,那么最好你提供一种方法来做到这一点。理想情况下,这将是一个类似于公司或部门财产的东西,当然你不想获得你并不真正需要的数据,所以你可能会保留一个私人公司id,并有一个查询数据的公共getCompany函数。
我相信这不是一个真正的OOP问题,在您的情况下,您只有一个数据库模型(类中的数据库表示),它不包含任何逻辑,所有的类都用作structs,这是将数据库映射到类-structs的正确方法。因此,在代表程序逻辑的下一个模块中,如果你真的需要,你必须将数据库模块映射到包含逻辑的真实类(我指的是实现它的方法)。因此,在我看来OO问题应该在应用程序的逻辑部分。另一方面,您可以查看nhibernate,以及其中的映射是如何完成的,它将为bes数据库模型的实现提供提示。
我相信这就是你的类在NHibernate:中的样子
public class Company
{
public int ID { get; set; }
public string Name { get; set; }
public IList<Department> Departments { get; set; }
}
public class Department
{
public int ID { get; set; }
public string Name { get; set; }
public Company Company { get; set; }
public IList<Employee> Employees { get; set; }
}
public class Employee
{
public int ID { get; set; }
public string FirstName { get; set;}
public string LastName { get; set; }
public Department Department { get; set; }
}
请注意,有一种方法可以从员工导航到部门,从部门导航到公司(除了您已经指定的方法之外)。
NHibernate有各种各样的功能可以让它发挥作用。而且效果非常好。主要技巧是运行时代理对象允许延迟加载。此外,NHibernate支持许多不同的方式来快速加载和懒惰加载,这正是你想要的。
当然,你可以在没有NHibernate或类似的ORM的情况下获得这些相同的功能,但为什么不使用功能丰富的主流技术,而不是手工编码自己的功能贫乏的自定义ORM呢?
还有另一个选项。创建一个"DataController"类,用于处理对象的加载和"内存化"。dataController维护一个[公司ID,公司对象]和[部门ID,部门对象]的字典。当您加载一个新的部门或公司时,您需要在此DataController字典中保留一条记录。然后,当您实例化一个新的Department或Employee时,您可以直接设置对父对象的引用,也可以使用Lazy[Company/Document]对象并使用lambda(在构造函数中)进行设置,lambda将维护DataController的作用域,而无需在对象内部直接引用它。有一件事我忘了提,您还可以在Dictionaries的getter/get方法中放置逻辑,如果找不到特定的ID,该方法将查询数据库。将所有这些结合使用可以使类(模型)非常干净,同时在加载数据的时间/方式方面仍然相当灵活。