Я пытаюсь реализовать более ориентированное на 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; }
}
В качестве первого прохода и сражаться без Эвансаили доступные книги Вернона (они есть в посте), я остановился на том, что есть два AggregateRoots с Site
, более выдающимся.То есть Signal
агрегат действительно должен быть доступен через Site
.
Основная проблема, которую я обнаружил, заключается в загрузке подмножеств Samples
в Signal
.
Согласно шаблону Specification
, используемому в примерах eShopOnWeb , я могу довольно легко работать с агрегатом Site
и загружать его совокупность Signals
с вызовом против SiteRepository
в Infrastructure
layer:
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;
}
}
Это, вероятно,только первоначальная неопределенность, которая возникает при разработке нового шаблона, но это почему-то кажется неправильным.
Правильно ли, что я использую два Агрегата?