【发布时间】:2019-02-19 15:55:10
【问题描述】:
我正在尝试实施一个更面向 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。
根据eShopOnWeb 示例中使用的Specification 模式,我可以相当轻松地使用Site 聚合并通过调用Infrastructure 层中的SiteRepository 来加载它的Signals 聚合集合:
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 类的版本,专门用于在Signal 实体上部分加载Sample 数据。
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;
}
}
这可能只是以新模式开发带来的最初不确定性,但这不知怎么感觉不对。
我使用两个聚合是否正确?
【问题讨论】:
标签: c# domain-driven-design ef-core-2.0 ef-core-2.1 ef-core-2.2