简体   繁体   English

连接C#中最佳实践的类

[英]Class linking best practices in C#

First off, EF is not an option for our development environment so please no "just use EF" answers ... 首先,EF不是我们开发环境的选项,所以请不要“只使用EF”答案......

I think this is a pretty standard dilemma so I'm sure there must be a way that most Pros do it that I just have not stumbled across ... so I'm out here hoping y'all can show me what it is. 我认为这是一个相当标准的困境,所以我确信大多数专业人士都必须采取一种方式,我只是没有偶然发现...所以我在这里希望你们都能告诉我它是什么。

Let's say you have the following database tables: 假设您有以下数据库表:

tblCompanies 
ID 
NAME

tblDepartments 
ID 
COMPANY_ID 
NAME

tblEmployees
ID 
DEPARTMENT_ID 
FIRSTNAME 
LASTNAME

... what's the best way to represent this in Classes within your code? ...在代码中的类中表示这个的最佳方法是什么?

I assume the best way is like this: 我认为最好的方法是这样的:

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; }
}

I believe that to the be the "OOP Proper approach" to this. 我相信这是对此的“OOP正确方法”。 However, what seems to always happens is something like this: 然而,似乎总是发生的事情是这样的:

public class Department
{
     public int ID { get; set; }
     public string Name { get; set; }
     public int CompanyID { get; set; }
     public List<Employee> Employees { get; set; }
}

... mainly because when you pull just a Department from the database you are only going to have Company ID, not all the other attributes needed to fully populated an instance of the Company class. ...主要是因为当你从数据库中提取一个部门时,你只会拥有公司ID,而不是完全填充公司类实例所需的所有其他属性。

(I've used a pretty vanilla example here but the one I'm actually tackling in my current project has 3 fields that it uses to link the data together so the thought of having the same 3 fields in several classes seems wrong to me) (我在这里使用了一个漂亮的例子,但我在当前项目中实际处理的那个有3个字段用于将数据链接在一起所以想到在几个类中拥有相同的3个字段对我来说似乎不对)

Is there a Best Practice for these scenarios? 这些场景是否有最佳实践 As much as I don't like the thought of storing the same data in multiple classes just out of laziness, I also don't like returning an instance of a class with just one of its fields populated because that's all I had at the time. 尽管我不喜欢只是出于懒惰而将相同数据存储在多个类中的想法,但我也不喜欢只填充其中一个字段的类的实例,因为这是我当时的所有内容。 。

This is a common problem, and one that ORMs try to solve. 这是一个常见问题,也是ORM试图解决的问题。 To be sure it isn't an easy one depending on what your wants are and what your constraints are. 根据您的需求约束条件 ,确保它不是一件容易的事。

There are only two fundamental options to keep one copy of the information. 保留一份信息只有两个基本选项。 Lazily load the data as requested or load it all to begin with (Greedy load). 懒惰地按要求加载数据或者将其全部加载(Greedy load)。 Otherwise you have to duplicate the data. 否则你必须复制数据。

With lazy loading you basically set things up such that when navigating into a property you make a call to the database and grab the information needed to load the entity representing the property you are accessing. 使用延迟加载,您基本上可以进行设置,以便在导航到属性时调用数据库并获取加载表示您正在访问的属性的实体所需的信息。 The tricky part to watch with this is the SELECT N + 1 problem. 需要注意的棘手部分是SELECT N + 1问题。 You experience this problem when you end up iterating a set of parent entities and trigger lazy loads on every child entity, thus resulting in N+1 calls to the database to load a set of entities (1) and their children (N). 当您最终迭代一组父实体并在每个子实体上触发延迟加载时遇到此问题,从而导致对数据库的N + 1次调用以加载一组实体(1)及其子项(N)。

Greedy loading basically says load everything you need to start with. 贪心负载基本上表示加载您需要的所有内容。 ORMs (where they work) are nice because they take care of many of the details via LINQ and create solutions that can be performant and maintainable usually along with the ability of allowing you to manipulate the usage of Greedy and Lazy Loading. ORM(它们工作的地方)很好,因为它们通过LINQ处理许多细节,并创建可以高性能和可维护的解决方案,通常还允许您操纵Greedy和Lazy Loading的使用。

Another important gotcha is many to many relationships. 另一个重要的问题是多对多的关系。 You need to make sure not to have circular initialization, and get all the baggage of circular dependencies. 您需要确保不进行循环初始化,并获得循环依赖的所有包袱。 There are surely many more I have missed. 肯定有更多我错过了。

In my humble opinion I am not so sure there is a best practice as much as there are practices with some of them bad - nothing is perfect. 在我的拙见中,我不太确定有最好的做法 ,因为有些做法很糟糕 - 没有什么是完美的。 You can: 您可以:

  1. Start rolling your own object relational mapper allowing you to get rid of the duplicate ID 开始滚动自己的对象关系映射器, 允许您删除重复的ID

  2. Use a lighter ORM framework to handle some of this allowing you to get rid of the duplicate ID 使用更轻的ORM框架来处理其中一些, 使您可以摆脱重复的ID

  3. Create specialized queries to load aggregations of data allowing you to get rid of the duplicate ID (* cough * DDD) 创建专门的查询以加载数据聚合, 使您可以摆脱重复的ID (* cough * DDD)

  4. Just keep the duplication of the ID like you mention above and not worry about creating an explicit relational model in your domain. 只需像上面提到的那样保留ID的重复,而不必担心在您的域中创建显式关系模型。

This one is on you to choose what is best based on your constraints. 可以根据自己的约束选择最佳选择。 This is a deep topic and my experience is limited... so take what I am saying with alot of salt . 这是一个很深刻的话题,我的经验是有限的... 所以我要说的很多盐

I don't think there's a "best practices" manual for this kind of things, and surely it depends on how your classes are going to be used. 我不认为有这种事情的“最佳实践”手册,当然这取决于你的课程将如何使用。 But in my personal experience, I have ended up following this approach: 但根据我的个人经验,我最终采用了这种方法:

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
   }
}

In some cases I have exposed some of the "navigation" properties of my classes as public (like CompanyID and DepartmentID) to prevent the instantiation of a new class to get a value that has been loaded already. 在某些情况下,我将类的一些“导航”属性publicpublic (如CompanyID和DepartmentID),以防止实例化新类以获取已经加载的值。

As others have noted, you could also simulate "lazy loading", but this will require some extra effort from your part. 正如其他人所说,你也可以模拟“延迟加载”,但这需要你的一些额外努力。

I would think it depends on requirements. 我认为这取决于要求。 Do you need to traverse upward (get company from department, department from employee, etc). 您是否需要向上遍历(从部门获取公司,从员工获取部门等)。 If you do, then it is best that you provide a means of doing that. 如果你这样做,那么你最好提供一种方法。 Ideally that would be something like a Company or Department property, of course you wouldn't want to get data you don't really need, so you'd likely keep a private company id and have a public getCompany function which queries for the data. 理想情况下,这将是公司或部门属性,当然你不希望得到你真正不需要的数据,所以你可能会保留一个私人公司ID,并有一个公共getCompany函数查询数据。

I believe that this is not a really OOP question, in your case you just have an database model (database representation in classes) which does not contain any logic and all the classes are used as structs, and this is a right way to map your database to classes - structs. 我相信这不是一个真正的OOP问题,在你的情况下,你只有一个数据库模型(类中的数据库表示),它不包含任何逻辑,所有类都用作结构,这是映射你的正确方法数据库到类 - 结构。 So in your next module which will represent the logic of your program you have to map your database module to the real classes which will contain the logic (I mean methods which will implement it) of course if you really need them. 因此,在您的下一个代表程序逻辑的模块中,您必须将数据库模块映射到包含逻辑的实际类(我的意思是实现它的方法),当然如果您确实需要它们。 So in my opinion the OO question should be in the logic part of your application. 因此,在我看来,OO问题应该在您的应用程序的逻辑部分。 On the other hand you could take a look on nhibernate and how the mapping done in there it will give you a hint for the bes database model implementation. 另一方面,您可以查看nhibernate以及如何在其中完成映射,它将为您提供bes数据库模型实现的提示。

I believe this is what your classes would look like in NHibernate: 我相信这就是你的类在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; }
}

Note that there is a way to navigate from Employee to Department and from Department to Company (in addition to what you already specified). 请注意,有一种方法可以从Employee导航到Department,从Department导航到Company(除了您已经指定的内容)。

NHibernate has all kinds of features to make that just work. NHibernate具有各种功能,可以使它正常工作。 And it works very, very well. 而且效果非常非常好。 The main trick is run-time proxy objects to allow for lazy loading. 主要技巧是运行时代理对象以允许延迟加载。 Also, NHibernate supports a lot of different ways to eager and lazy load just exactly how you want to do it. 此外,NHibernate支持许多不同的方式来急切和延迟加载,正是你想要的方式。

Sure, you can get these same features without NHibernate or a similar ORM, but why wouldn't use just use a feature rich mainstream techology instead of hand coding your own feature poor custom ORM? 当然,你可以在没有NHibernate或类似ORM的情况下获得这些相同的功能,但为什么不使用功能丰富的主流技术而不是手工编写自己的功能差的自定义ORM?

There is another option. 还有另一种选择。 Create a 'DataController' class which handles the loading and 'memoization' of your objects. 创建一个'DataController'类来处理对象的加载和“memoization”。 The dataController maintains a dictionary of [CompanyIDs, Company objects] and [DepartmentIDs, Department objects]. dataController维护[CompanyIDs,Company objects]和[DepartmentIDs,Department objects]的字典。 When you load a new Department or Company, you keep a record in this DataController dictionary. 加载新的部门或公司时,您在此DataController字典中保留一条记录。 Then when you instantiate a new Department or Employee you can either directly set the references to the parent objects OR you can use a Lazy[Company/Department] object and set it using a lambda (in the constructor) which will maintain the scope of the DataController without it being referenced directly inside the objects. 然后,当您实例化新的Department或Employee时,您可以直接设置对父对象的引用,也可以使用Lazy [Company / Department]对象并使用lambda(在构造函数中)设置它,这将保持对象的范围DataController没有直接在对象内引用它。 One thing I forgot to mention, you can also place logic in the getter / get method for the Dictionaries that queries the database if a particular ID is not found. 有一点我忘了提到,如果找不到特定的ID,你也可以在查询数据库的字典的getter / get方法中放置逻辑。 Using all of this together allows your Classes (Models) to be very clean while still being fairly flexible as to when / how their data is loaded. 将所有这些结合使用可以使您的类(模型)非常干净,同时对于何时/如何加载数据仍然相当灵活。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM