关于域对象的存储库及其保存方法的问题

发布于 2024-07-15 18:59:34 字数 1929 浏览 4 评论 0原文

我有一个关于 DDD、存储库模式和 ORM 的有点荒谬的问题。 在此示例中,我有 3 个类:地址公司人员。 个人是公司的成员并拥有地址。 公司也有地址。

这些类反映了数据库模型。 我删除了模型的所有依赖项,因此它们不依赖于特定的 ORM 库,例如 NHibernate 或 LinqToSql。 这些依赖关系在存储库内处理。

在其中一个存储库中,有一个SavePerson(Person person)方法,该方法根据Person是否已存在于数据库中来插入/更新它。

由于 Person 对象有一个 Company,因此我当前在进行 SavePerson 调用时也会保存/更新 Company 属性的值。 在此过程中,我插入/更新了公司的所有数据 - 名称和地址。

然而,我真的很难想象在处理个人时公司的数据可能会发生变化的情况 - 我只想能够将公司分配给个人,或者将个人移动到另一个公司。 我认为我不想与新人一起创建一家新公司。 因此 SaveCompany 调用会引入不必要的数据库调用。 保存人员时,我应该能够更新 CompanyId 列。

但由于 Person 类有一个 Company 属性,我有点倾向于用它来更新/插入它。 从严格/纯粹的角度来看,SavePerson 方法应该保存整个 Person。

首选的方式是什么? 在保存 Person 或保存其所有数据时,仅插入/更新 Company 属性的 CompanyId 吗? 或者您会为这两种情况创建两个不同的方法(您会给它们命名什么?)

另外,另一个问题,我目前有不同的方法来保存人员、地址和公司,所以当我保存公司时,我也会调用 SaveAddress 。 假设我使用 LinqToSql - 这意味着我不会在同一 Linq 查询中插入/更新公司和地址。 我猜有 2 个 Select Calls(检查公司是否存在,检查地址是否存在)。 然后对两者进行两次插入/更新调用。 如果引入更多的复合模型类则更多。 LinqToSql 有没有办法优化这些调用?

public class Address
{
    public int AddressId { get; set; }
    public string AddressLine1 { get; set; }
    public string AddressLine2 { get; set; }
    public string City { get; set; }
    public string PostalCode { get; set; }        
}

public class Company
{
    public int CompanyId { get; set; }
    public string Name { get; set; }
    public Address Address { get; set; }
}


public class Person
{
    public int PersonId { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Email { get; set; }
    public Company Company { get; set; }
    public Address Address { get; set; }

}

编辑

另请参阅此后续问题。 值对象如何存储在数据库中?

I have a somewhat ridiculous question regarding DDD, Repository Patterns and ORM. In this example, I have 3 classes: Address, Company and Person. A Person is a member of a Company and has an Address. A Company also has an Address.

These classes reflect a database model. I removed any dependencies of my Models, so they are not tied to a particular ORM library such as NHibernate or LinqToSql. These dependencies are dealt with inside the Repositories.

Inside one of Repositories there is a SavePerson(Person person) method which inserts/updates a Person depending on whether it already exists in the database.

Since a Person object has a Company, I currently save/update the values of the Company property too when making that SavePerson call. I insert / update all of the Company's data - Name and Address - during this procedure.

However, I really have a hard time thinking of a case where a Company's data can change while dealing with a Person - I only want to be able to assign a Company to a Person, or to move a Person to another Company. I don't think I ever want to create a new Company alongside a new Person. So the SaveCompany calls introduce unnecessary database calls. When saving a Person I should just be able to update the CompanyId column.

But since the Person class has a Company property, I'm somewhat inclined to update / insert it with it. From a strict/pure point of view, the SavePerson method should save the entire Person.

What would the preferred way be? Just inserting/updating the CompanyId of the Company property when saving a Person or saving all of its data? Or would you create two distinct methods for both scenarios (What would you name them?)

Also, another question, I currently have distinct methods for saving a Person, an Address and a Company, so when I save a Company, I also call SaveAddress. Let's assume I use LinqToSql - this means that I don't insert/update the Company and the Address in the same Linq query. I guess there are 2 Select Calls (checking whether a company exists, checking whether an address exists). And then two Insert/Update calls for both. Even more if more compound model classes are introduced. Is there a way for LinqToSql to optimize these calls?

public class Address
{
    public int AddressId { get; set; }
    public string AddressLine1 { get; set; }
    public string AddressLine2 { get; set; }
    public string City { get; set; }
    public string PostalCode { get; set; }        
}

public class Company
{
    public int CompanyId { get; set; }
    public string Name { get; set; }
    public Address Address { get; set; }
}


public class Person
{
    public int PersonId { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Email { get; set; }
    public Company Company { get; set; }
    public Address Address { get; set; }

}

Edit

Also see this follow up question. How are Value Objects stored in a Database?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

终弃我 2024-07-22 18:59:34

我自己最近使用了 Keith 建议的 IRepository 方法。 但是,您不应该在这里关注这种模式。 相反,DDD 手册中还有一些内容可以在这里应用。

使用值对象作为您的地址

首先,您可以在此处应用值对象(VO)的概念。 在您的情况下,它将是地址。 值对象和实体对象之间的区别在于实体具有身份; VO 则不然。 VO 的身份实际上是其属性的总和,而不是唯一的身份。 在快速域驱动设计一书中(也是免费的 PDF 下载),他很好地解释了这一点,指出地址实际上只是地球上的一个点,不需要像人一样单独的类似社会保障的身份。 地球上的那个点是街道、号码、城市、邮政编码和国家/地区的组合。 它可以具有纬度和经度值,但根据定义,这些值仍然是 VO,因为它是两点的组合。

使用服务将您的实体组合成单个实体以进行操作。

另外,不要忘记 DDD 手册中的服务概念。 在您的示例中,该服务将是:

public class PersonCompanyService
{
  void SavePersonCompany(IPersonCompany personCompany)
  {
    personRepository.SavePerson();
    // do some work for a new company, etc.
    companyRepository.SaveCompany();
  }
}

当您有两个实体都需要类似的操作来协调其他操作的组合时,就需要服务。 在您的情况下,保存 Person() 并同时创建一个空白 Company() 。

ORM 通常需要一个身份、句点。

现在,您将如何将地址 VO 保存到数据库中? 显然您会使用 IAddressRepository。 但由于大多数 ORM(即 LingToSql)要求所有对象都有一个身份,因此技巧如下:将身份标记为模型中的内部身份,这样它就不会暴露在模型层之外。 这是史蒂文·桑德森自己的建议。

public class Address
{
  // make your identity internal
  [Column(IsPrimaryKey = true
    , IsDbGenerated = true
    , AutoSync = AutoSync.OnInsert)]
  internal int AddressID { get; set; }

  // everything else public
  [Column]
  public string StreetNumber { get; set; }
  [Column]
  public string Street { get; set; }
  [Column]
  public string City { get; set; }
  ...
}

I myself have used the IRepository approach lately that Keith suggests. But, you should not be focusing on that pattern here. Instead, there are a few more pieces in the DDD playbook that can be applied here.

Use Value Objects for your Addresses

First, there is the concept of Value Objects (VO) you can apply here. In you case, it would be the Address. The difference between a Value Object and an Entity Object is that Entities have an identity; VOs do not. The VO's identity really is the sum of it's properties, not a unique identity. In the book Domain-Drive Design Quickly (it's also a free PDF download), he explains this very well by stating that an address is really just a point on Earth and does not need a separate SocialSecurity-like identity like a person. That point on Earth is the combination of the street, number, city, zip, and country. It can have latitude and longitude values, but still those are even VOs by definition because it's a combination of two points.

Use Services for combining your entities into a single entity to act upon.

Also, do not forget about the Services concept in the DDD playbook. In your example, that service would be:

public class PersonCompanyService
{
  void SavePersonCompany(IPersonCompany personCompany)
  {
    personRepository.SavePerson();
    // do some work for a new company, etc.
    companyRepository.SaveCompany();
  }
}

There is a need for a service when you have two entities that need both need a similar action to coordinate a combination of other actions. In your case, saving a Person() and creating a blank Company() at the same time.

ORMs usualyl require an identity, period.

Now, how would you go about saving the Address VO in the database? You would use an IAddressRepository obviously. But since most ORMs (i.e. LingToSql) require all objects have an Identity, here's the trick: Mark the identity as internal in your model, so it is not exposed outside of your Model layer. This is Steven Sanderson's own advice.

public class Address
{
  // make your identity internal
  [Column(IsPrimaryKey = true
    , IsDbGenerated = true
    , AutoSync = AutoSync.OnInsert)]
  internal int AddressID { get; set; }

  // everything else public
  [Column]
  public string StreetNumber { get; set; }
  [Column]
  public string Street { get; set; }
  [Column]
  public string City { get; set; }
  ...
}
安静 2024-07-22 18:59:34

根据我最近使用存储库模式的经验,我认为您将从使用通用存储库(现在常见的 T 的 IRepository)中受益。这样您就不必添加像 SavePerson(Person person) 这样的存储库方法。 相反,您会得到类似的内容:

IRepository<Person> personRepository = new Repository<Person>();
Person realPerson = new Person();
personRepository.SaveOrUpdate(realPerson);

此方法也非常适合测试驱动开发和模拟。

我觉得您的描述中有关行为的问题将是域的问题,也许您应该在 Person 类中有一个 AddCompany 方法,并将 Company 属性更改为

public Company Company { get; private set; }

我的观点是; 对域进行建模,而不必担心数据如何保存到数据库中。 对于将使用您的域模型的服务来说,这是一个问题。

回到存储库,看看这个 帖子对 LinqToSql 上的 IRepository 进行了很好的解释。 Mike 的博客还有许多有关存储库的其他帖子。 当您确实要选择 ORM 时,我会推荐 HHibernate 而不是 LinqToSql,后者现已不复存在,并且 NHibernate 拥有强大的支持社区。

From my recent experience of using the repository pattern I think you would benefit from using a generic repository, the now common IRepository of T. That way you wouldn't have to add repository methods like SavePerson(Person person). Instead you would have something like:

IRepository<Person> personRepository = new Repository<Person>();
Person realPerson = new Person();
personRepository.SaveOrUpdate(realPerson);

This method also lends itself well to Test Driven Development and Mocking.

I feel the questions about behavior in your description would be concerns for the Domain, maybe you should have an AddCompany method in your Person class and change the Company property to

public Company Company { get; private set; }

My point is; model the domain without worrying about the how data will be persisted to the database. This is a concern for the service that will be using your domain model.

Back to the Repository, have a look at this post for good explanation of IRepository over LinqToSql. Mike's blog has many other posts on Repositories. When you do come to choose an ORM I can recommend HHibernate over LinqToSql, the latter is now defunct and NHibernate has a great support community.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文