如何在领域驱动设计中处理Includes with Entity Framework Core

How to handle Includes with Entity Framework Core in Domain Driven Design

我对域驱动设计的概念还很陌生,只需要在正确的方向上轻推一下。我在互联网上找不到任何令我满意的问题。我有一个按照域驱动设计构建的应用程序。现在我想知道如何在我的应用程序层中不使用 EFC 来实现包含。我有一个表示层(Web API),一个由命令和查询组成的应用层(我正在使用 CQRS),一个存储我的模型并具有核心业务逻辑的域层和我的持久层,实现 Entity Framework 核心和通用存储库,如下所示:

public class Repository<T> : IRepository<T> where T : class, IEntity
    {
        private readonly HeimdallContext _context;

        public Repository(HeimdallContext context)
        {
            _context = context;
        }

        public IQueryable<T> Get()
        {
            return _context.Set<T>();
        }

        public async Task<T> FindAsync(Guid id)
        {
            return await _context.Set<T>().SingleOrDefaultAsync(x => x.Id == id);
        }

        public T Add(T item)
        {
            return _context.Add(item).Entity;
        }

        public T Delete(T item)
        {
            return _context.Remove(item).Entity;
        }

        public T Update(T item)
        {
            return _context.Update(item).Entity;
        }

        public async Task SaveChangesAsync()
        {
            await _context.SaveChangesAsync();
        }
    }

查询如下所示:

 public class FindFlowQueryHandler : IRequestHandler<FindFlowQuery, Result<GetUserFlow>>
    {
        private readonly IRepository<UserFlow> _userFlowRepository;
        private readonly IMapper _mapper;

        public FindFlowQueryHandler(IRepository<UserFlow> userFlowRepository, IMapper mapper)
        {
            _userFlowRepository = userFlowRepository;
            _mapper = mapper;
        }

        public async Task<Result<GetUserFlow>> Handle(FindFlowQuery request, CancellationToken cancellationToken)
        {
            var userFlow = await Task.FromResult(_userFlowRepository
                .Get()
                //.Include(x => x.UserFlowQuestionAnswers)
                //    .ThenInclude(x => x.FlowQuestion)
                //        .ThenInclude(x => x.Localization)
                //.Include(x => x.UserFlowQuestionAnswers)
                //    .ThenInclude(x => x.Image)
                .SingleOrDefault(x => x.Id == request.UserFlowId));

            if (userFlow == null)
            {
                return new Result<GetUserFlow>(new UserFlowNotFoundError(request.UserFlowId));
            }

            if (userFlow.UserId != request.UserId)
            {
                return new Result<GetUserFlow>(new ForbiddenError());
            }

            var mappedUserFlow = _mapper.Map<GetUserFlow>(userFlow);

            return new Result<GetUserFlow>(mappedUserFlow);
        }
    }

我注释掉了使用 EFC 中的 Include 方法的行。我不希望应用层知道任何有关 EFC 的信息,我使用什么 ORM 应该无关紧要。也许有一天我什至不想再使用 ERM。我知道通用存储库可能被认为是过度概括,但我真的很喜欢它的想法并且我不想编写数百个存储库以便我可以使用“GetFlowWithQuestionsAndAnswersAndLocalizationAndImages()”等方法。

我考虑过使用查询规范模式 (described here),但这似乎过于复杂,而且对我来说它看起来非常特定于 EFC。理想情况下,我根本不想在我的应用程序层中指定包含。我有一个模型,我知道它有其他模型作为属性,我希望能够访问它们。它们来自何处并不重要,EFC 将它们视为导航属性。

此处的最佳做法是什么?只是停用 EFC 延迟加载?废弃通用存储库?一直在 Queryables 上工作?

提前致谢!

正如您在问题中提到的,大多数 DDD 从业者不推荐使用 Generic Repository,因为您失去了 Meaningful Contract DDD 中 Repository 的方面,但如果您坚持,您可以丰富 Generic Repository 以拥有 ORM 的必要方面,例如 includeEntity Framework.

在您的通用存储库中添加更多功能时要小心,因为它会逐渐转变为 DAO。

您的通用存储库可能是这样的:

public class Repository<TContext>
    where TContext : DbContext
{
    protected readonly TContext context;

    public Repository(TContext context)
    {
        this.context = context;
    }

    protected virtual IQueryable<TEntity> GetQueryable<TEntity>(
        Expression<Func<TEntity, bool>> filter = null,
        Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
        string includeProperties = null,
        int? skip = null,
        int? take = null)
        where TEntity : class
    {
        includeProperties ??= string.Empty;
        IQueryable<TEntity> query = context.Set<TEntity>();

        if (filter != null)
        {
            query = query.Where(filter);
        }

        query = includeProperties.Split(new char[] {','}, StringSplitOptions.RemoveEmptyEntries)
            .Aggregate(query, (current, includeProperty) => current.Include(includeProperty));

        if (orderBy != null)
        {
            query = orderBy(query);
        }

        if (skip.HasValue)
        {
            query = query.Skip(skip.Value);
        }

        if (take.HasValue)
        {
            query = query.Take(take.Value);
        }

        return query;
    }

    public virtual IEnumerable<TEntity> GetAll<TEntity>(
        Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
        string includeProperties = null,
        int? skip = null,
        int? take = null)
        where TEntity : class
    {
        return GetQueryable<TEntity>(null, orderBy, includeProperties, skip, take).ToList();
    }

    public virtual IEnumerable<TEntity> Get<TEntity>(
        Expression<Func<TEntity, bool>> filter = null,
        Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
        string includeProperties = null,
        int? skip = null,
        int? take = null)
        where TEntity : class
    {
        return GetQueryable<TEntity>(filter, orderBy, includeProperties, skip, take).ToList();
    }
}

有关此 Repository 实现的更多信息,请查看 A Truly Generic Repository