我正在尝试实施一个更面向 DDD 的解决方案来管理时间序列数据。以下代码示例和模式可在此处找到eShopOnWeb。本质上存在三个实体。Site
,Signal
和Sample
. 一个Site
可以有一个集合,Signals
一个Signal
可以有一个样本集合。
public class Site: BaseEntity, IAggregateRoot
{
// Collection loaded by EFCore through Repository
private List<Signal> signals = new List<Signal>();
// Public read only access
public IEnumerable<Signal> Signals => this.signals.AsReadOnly();
}
public class Signal: BaseEntity, IAggregateRoot
{
// Signal has to belong to Site
public int SiteId { get; private set; }
// Typical EF Nav property removed
// Signal should have no access to it's 'parent' properties
// public Site Site { get; set;}
private List<Sample> samples = new List<Sample>();
public IEnumerable<Sample> Samples => this.samples.AsReadOnly();
}
public class Sample : BaseEntity
{
public int SignalId { get; private set; }
public DateTime TimeStamp { get; set; }
public double? Value { get; set; }
}
作为第一遍,在没有 Evans 或 Vernon 书籍的情况下苦苦挣扎(它们在帖子中),我已经确定有两个 AggregateRoots 和Site
更突出的一个。那是一个Signal
真正应该通过Site
.
我发现的主要问题是将子集加载Samples
到Signal
.
根据eShopOnWebSpecification
示例中使用的模式,我可以相当轻松地使用聚合并通过调用层中的a 来加载它的聚合集合:Site
Signals
SiteRepository
Infrastructure
public sealed class SiteFilterSpecification : BaseSpecification<Site>
{
public SiteFilterSpecification(int id)
: base(s => s.Id == id)
{
this.AddInclude(s => s.Signals);
}
}
如果我在一个Service
课程中,我提供了一个站点和一段时间来计算某些东西,通常涉及多个Signals
规范模式会建议如下:
public double GetComplexProcess(Site site, DateTime start, DateTime end)
{
var specification = new SiteSignalsWithSamplesSpec(site.Id, start, end);
var signals = this.SignalRepository.List(specification);
// signals should be loaded with the appropriate samples...
}
我在这里发现的问题是,在规范中无法过滤Samples
包含在Signal
public sealed class SiteSignalsWithSamplesSpecification : BaseSpecification<Signal>
{
public SiteSignalsWithSamplesSpecification(int siteId, DateTime from, DateTime end)
: base(s => s.SiteId == siteId)
{
// This throws exception at runtime
this.AddInclude(s => s.Samples.Where(sa => sa.TimeStamp >= from && sa.TimeStamp <= end));
}
}
您可以使用这种方法并加载所有数据,Samples
但在处理时间序列数据时,这可能意味着数十万个实体,而我们真正需要的是集中选择它们。
我目前在做什么;并且这感觉不是特别“干净”,是实现一个版本的 Generic Repository 类,专门用于在实体上部分加载Sample
数据。Signal
public interface ISignalRepository : IAsyncRepository<Signal>
{
Task<IEnumerable<Signal>> GetBySiteIdWithSamplesAsync(int siteId, DateTime from, DateTime to);
}
public class SignalRepository : EfRepository<Signal>, ISignalRepository
{
public SignalRepository(ForecastingContext dbContext) : base(dbContext)
{
}
public async Task<IEnumerable<Signal>> GetBySiteIdWithSamplesAsync(int siteId, DateTime from, DateTime to)
{
var signals = await this.dbContext.Signals.Where(s => s.SiteId == siteId).ToListAsync();
foreach (var signal in signals)
{
this.dbContext.Entry(signal)
.Collection(s => s.Samples)
.Query()
.Where(s => s.TimeStamp >= from && s.TimeStamp <= to)
.Load();
}
return signals;
}
}
这可能只是以新模式开发所带来的最初的不确定性,但这在某种程度上感觉是错误的。
我使用两个聚合是否正确?