从数据库检索部分聚合根 - DDD 方法 C#、.NET、Entity Framework

问题描述 投票:0回答:2

首先我想描述一下我的“问题”。它更像是担忧而不是问题,因为我还看到了一些文章,建议从数据库检索所有聚合根的数据,然后使用 dtos 向用户显示。

尽管有很多文章关注优化查询等。其中一点集中于仅检索稍后需要使用/向用户显示的数据。

使用 DDD 方法,我的聚合根(称为

Shop
)具有内部构造函数(以及用于 EF 目的的第二个私有构造函数)。实体创建是通过静态工厂方法完成的。

控制器方法之一返回商店列表 - 我需要的唯一字段是

id, shopname
address
,但使用传统的数据检索方法,我获取整个实体,然后仅使用那些必需的字段将其映射到 dto。

存储库接口 (

IShopRepository
) 位于域层中,无法访问该模块周围的任何其他接口。

该接口的实现当然是在基础设施层和应用程序层中具有处理程序的 DTO 中。

我的问题是如何在不破坏架构和 DDD 方法的情况下从数据库中仅获取这 3 个字段?我不想通过在域层中创建类似

ShopPartialDto
类之类的内容来创建另一层抽象,我也不会将接口移动到应用程序层。

在仓库里也是无法使用的

Select/SelectMany(x => new Shop(x.Id, x.ShopName, x.Address).ToList() 

由于

  • 构造函数无法访问或
  • 构造函数不能仅使用 3 个参数

这就是我的代码的样子:

Shop.cs

public class Shop : Entity, IAggregateRoot
{
    public ShopId Id { get; private set; }
    public Email Email { get; private set; }
    public PasswordHash PasswordHash { get; private set; }
    public Name OwnerName { get; private set; }
    public LastName OwnerLastName { get; private set; }
    public ShopName ShopName { get; private set; }
    public Address ShopAddress { get; private set; }
    public TaxNumber TaxNumber { get; private set; }
    public TelephoneNumber ContactNumber { get; private set; }
    public Roles Role { get; private set; } = Roles.shop;

    private Shop() { }

    internal Shop(Email email,
                  PasswordHash passwordHash,
                  Name ownerName,
                  LastName ownerLastName,
                  ShopName shopName,
                  Address shopAddress,
                  TaxNumber taxNumber,
                  TelephoneNumber contactNumber)
    {
        Id = new ShopId(Guid.NewGuid());
        Email = email;
        PasswordHash = passwordHash;
        OwnerName = ownerName;
        OwnerLastName = ownerLastName;
        ShopName = shopName;
        ShopAddress = shopAddress;
        TaxNumber = taxNumber;
        ContactNumber = contactNumber;
        Role = Roles.shop;
    }

    public static Shop Create(Email email,
                              PasswordHash passwordHash,
                              Name ownerName,
                              LastName ownerLastName,
                              ShopName shopName,
                              Address shopAddress,
                              TaxNumber taxNumber,
                              TelephoneNumber contactNumber)
    {
        var shop = new Shop(email,
                        passwordHash,
                        ownerName,
                        ownerLastName,
                        shopName,
                        shopAddress,
                        taxNumber,
                        contactNumber);
        shop.AddDomainEvent(new ShopCreatedDomainEvent(shop));

        return shop;
    }
}

存储库

public async Task<IEnumerable<Shop>> GetAllShops()
{
    return await _dbContext.Shops.ToListAsync();
}

到店

public record ShopDto
{
    public Guid ShopId { get; init; }
    public string ShopName { get; init; }
    public Address ShopAddress { get; init; }

    internal static IEnumerable<ShopDto> CreateDtoFromObject(List<Shop> shops)
    {
        var shopList = new List<ShopDto>();

        foreach(var shop in shops)
        {
            var shopDto = new ShopDto()
            {
                ShopId = shop.Id,
                ShopName = shop.ShopName,
                ShopAddress = shop.ShopAddress
            };

            shopList.Add(shopDto);
        }

        return shopList;
    }
}

有什么好的方法可以在不破坏架构的情况下优化这个查询吗?

c# .net repository domain-driven-design
2个回答
0
投票

您还需要实现上下文。如果你尝试应用DDD并使用Aggregate Root,你会遇到“过度查询”的问题。有两种方法可以解决这个问题:

  1. 在您正在构建的特定功能中重点关注通用语言 (UL)。

    在您的具体示例中,您尝试显示的实际上是商店地址(这是它自己的AggregateRoot)。您可以使用

    Shop Address Repository
    提取此地址。此存储库拉取的“商店”与您在需要时拉取的商店对象不同,例如,列出商店中的所有商品。因此,在定义实体和聚合时您只需要小心即可。您对商店聚合的定义过于宽泛。使用您的 UL 来“聚焦”它。

  2. 有界上下文

    这与上面#1 类似,但规模更大。例如,您的应用程序可能需要考虑商店客户和商店员工。每个实体都会有大量的功能(例如:员工可以添加商店产品,而客户可以购买)。您不希望在所有这些功能中使用同一个商店(这将是一个糟糕的主意)。相反,您想要的是围绕功能定义有界上下文。例如,OrderContext 中的商店将包含商店中的产品和订单(此聚合可能包含客户)。 ManagementContext 中的商店将具有管理商店所需的员工、规模和其他属性(此聚合可能会包装员工)。 https://martinfowler.com/bliki/BoundedContext.html


0
投票

在一定程度上解决这个问题的一种方法可能是在存储库选择语句中使用匿名类型,即投影。 我很想听到更多替代方案或想法。

我们可以在

GetShopsNameWithAddress
中再定义一种方法(
IShopRepository
)来满足特定的用例。

Task<IEnumerable<dynamic>> GetShopsNameWithAddress(...); 

GetShopsNameWithAddress
中实施
ShopRepository

public async Task<IEnumerable<dynamic>> GetShopsNameWithAddress()
{
      /* This code sample is only for reference.
         Return type will match to the ShopDto with its own risk of future maintenance
         If feasible, we may replace select type anonymous & return type dynamic 
         with more specific type ShopDto!! 
         If not feasible, then dynamic type will do the job! */
     return await _dbContext.Shops.AsNoTracking()
         .Select/SelectMany(x => new (x.Id, x.ShopName, 
            x.Address)).ToListAsync();
}
© www.soinside.com 2019 - 2024. All rights reserved.