C# 如何通过Id以外的其他属性查询聚合根?

C# 如何通过Id以外的其他属性查询聚合根?,c#,.net,domain-driven-design,cqrs,event-sourcing,C#,.net,Domain Driven Design,Cqrs,Event Sourcing,需要澄清的是:BuckupableThing是一种硬件设备,其中写入了程序(已备份) 更新澄清:这个问题更多的是关于CQRS/ES实施,而不是关于DDD建模 假设我有3个聚合根: class BackupableThing { Guid Id { get; } } class Project { Guid Id { get; } string Description { get; } byte[] Data { get; } } class Backup {

需要澄清的是:BuckupableThing是一种硬件设备,其中写入了程序(已备份)

更新澄清:这个问题更多的是关于CQRS/ES实施,而不是关于DDD建模

假设我有3个聚合根:

class BackupableThing
{
    Guid Id { get; }
}

class Project
{
    Guid Id { get; }

    string Description { get; }
    byte[] Data { get; }
}

class Backup
{
    Guid Id { get; }

    Guid ThingId { get; }
    Guid ProjectId { get; }
    DateTime PerformedAt { get; }
}
每当我需要备份BackupableThing时,我都需要先创建新项目,然后创建新备份,并将ProjectId设置为新项目的Id。只要每个新备份都有新项目,一切都会正常工作

但实际上我需要创建一个项目,如果它不存在的话,现有项目的唯一id应该是它的数据属性(某种字节[]数组的散列)。因此,当备份任何其他备份设备时,系统会看到另一个备份设备已经备份,并且具有相同的结果(数据)-显示已创建并正在工作的项目,以及所有描述和所有设置

首先,我想通过某种方式在Guid中编码hash来解决这个问题,但这似乎很粗糙,也不简单,而且它增加了与随机生成的Guid发生冲突的机会

然后我想到了单独的表(带有单独的存储库),它包含两列:数据散列(一些int/long)和PlcProjectId(Guid)。但这看起来很像投影,事实上这将是一种投影,所以理论上我可以使用事件存储中的域事件来重建它。我读到,从域服务/聚合/存储库(从写端)查询读取端是不好的,我在一段时间内无法想出其他方法

更新

所以基本上我在只有域可以访问的域内创建读取端。我在添加新项目之前查询它,所以如果它已经存在,我只使用已经存在的一个?是的,我已经在一夜之间想到了这一点,似乎在创建新的聚合之前,我不仅必须创建这样的域存储并查询它,而且还必须引入一些补偿操作。例如,如果发送多个请求以同时创建同一个项目,则将创建两个相同的项目。所以我需要我的域存储成为一个事件处理程序,如果用户创建了同一个项目,我需要启动补偿命令来删除/移动/使用现有的项目重新创建这个项目

更新2

我也在考虑为此目的创建另一个聚合—为我的项目的唯一性范围(在这个特定场景中为GlobalScopeAggregate或DomainAggregate)创建聚合,它将保存{name,Guid}键值引用。单独的GlobalScopeHandler将负责ProjectCreated、ProjectArchived和ProjectRenamed事件,如果ProjectCreated事件与已创建的事件同名,则最终将触发补偿操作。但我对补偿行动感到困惑。如果用户已经进行了备份,并且在其界面相关视图中显示了项目,我应该如何反应?他可以更改错误项目的描述、名称等,这些项目已通过补偿措施删除。此外,我的补偿操作将删除Project备份聚合,并使用现有ProjectId创建新的备份聚合,因为我的备份聚合在ProjectId字段上没有setter(它是备份执行操作的不可变记录)。这正常吗

更新3-领域澄清

在广域网络上有许多工业设备(备份设备、可编程控制器),其中有一些已编程的固件。客户更新固件并将其上传到控制器(可备份的东西)。这个程序被备份了。但是有很多相同类型的控制器,客户很可能会反复将相同的程序上传到多个控制器以及同一个控制器(作为逆转某些更改的手段)。用户需要重复备份所有这些控制器。备份是一些二进制数据(存储在控制器、程序中)和备份发生的日期项目是封装二进制数据以及与备份相关的所有信息的实体。鉴于我无法将程序备份到之前上载的状态(我只能获取无法读取的原始二进制数据,我也可以将其重新上载回控制器),我需要单独的聚合项目,该项目包含数据属性以及附加文件数(例如,固件项目文件)、说明、名称和其他字段。现在,每当备份某个控制器时,我不想显示“没有任何描述的二进制数据”,并强迫用户再次填写所有描述字段。我想查找是否已经使用相同的二进制数据进行了备份,然后将此项目链接到此备份,以便备份另一个控制器的用户可以立即看到有关此控制器中的内容的大量信息:)

因此,我想这是基于集合的验证的情况,这种验证经常发生(与常规的唯一约束相反),而且我会有很多备份,因此单独的聚合将所有内容保存在内存中是不明智的

还有我只是觉得还有另一个问题。我不能计算二进制数据的散列,也不能容忍将两个不同的备份视为同一个项目的小风险。这是一个需要精确和可靠解决方案的行业领域。同时,我不能在二进制数据列(SQL中的varbinary)强制唯一约束,因为我的二进制数据可能相对较大。所以我想我需要为[int(二进制数据的散列)、Guid(项目的id)]关系创建单独的表,如果找到新备份的二进制数据的散列,我需要加载相关的聚合,并确保二进制数据是
class Handler : 
    IHandleMessages<Events.Added>,
    IHandleMessages<Events.Removed>,
    IHandleQueries<Queries.ObjectsByName>
{
    public void Handle(Events.Added e) {
       _orm.Add(new { ObjectId = e.ObjectId, Name = e.name });
    }
    public void Handle(Events.Removed e) {
       _orm.Remove(x => x.ObjectId == e.ObjectId && x.Name == e.Name);
    }
    public void Handle(Queries.ObjectsByName q) {
        _orm.Query(x => x.Name == q.Name);
    }

}
transaction {
    uniquenessService.reserve(uniquenessKey); //writes to a DB unique index

    //save aggregate that holds uniquenessKey
}