渴望加载和存储库模式
-
06-07-2019 - |
题
我想知道使用存储库模式时如何正确处理复杂对象图的急切加载问题。我猜这不是 ORM 特定的问题。
第一次尝试:
public interface IProductRepository : IRepository<Product>
{
Product GetById(int id);
IProductRepository WithCustomers();
}
这可以很好地工作,但这将涉及到一直重复自己(在任何地方的存储库实现中编写自定义“With”方法)。
下一个方法:
public interface IRepository<T> where T : IAggregateRoot
{
...
void With(Expression<Func<T, object>> propToExpand);
}
With
方法会将一个项目添加到私有集合中,稍后将使用该项目来找出在检索必要的实体时应该急切加载哪些道具。
这种方法有效并且很好。但我不喜欢使用:
productRepository.With(x=>x.Customer);
productRepository.With(x=>x.Price);
productRepository.With(x=>x.Manufacturer);
var product = productRepository.GetById(id);
基本上 - 问题是没有链接。我希望它是这样的:
var product = productRepository
.With(x=>x.Customer)
.With(x=>x.Price)
.With(x=>x.Manufacturer)
.GetById(id);
我无法实现这个目标. 。即使我可以 - 我不确定该解决方案是否优雅。
这导致我认为我错过了一些基本的东西(任何地方都缺乏例子)。有不同的方法来处理这个问题吗?什么是最佳实践?
解决方案
有趣的问题,我相信你不是第一个遇到麻烦的人(我绝对有)。
对我来说,真正的问题是:你想在哪里放置你急切的加载逻辑?
客户端代码中的存储库之外
var product = productRepository
.With(x=>x.Customer)
.With(x=>x.Price)
.With(x=>x.Manufacturer)
.GetById(id);
我认为这不是很好的软件设计:看起来这可能会导致“一千次切割导致死亡”。如果这些结构分散在整个应用程序中。
或在存储库中。例如:
interface IProductRepository {
Product GetById(int id);
Product GetByIdWithCustomers(int i);
}
所以你的客户端代码看起来像这样:
var product = productRepository.GetByIdWithCustomers(id);
通常我会创建一个BaseRepository,它只定义了基本的CRUD操作:
public class BaseRepository<TEntity, TPrimaryKey> {
public void Save(TEntity entity) { ... }
public void Delete(TEntity entity) { ... }
public TEntity Load(TPrimaryKey id) { ... } // just gets the entity by primary key
}
然后我扩展这个基类/接口,以便提供获取域对象的特定方法。你的方法似乎有点类似的方向。
public class MediaRepository : BaseRepository<Media, int> {
public long CountMediaWithCategories() { ... }
public IList<Media> MediaInCategories(IList<Category> categories) { .... }
}
好处:所有ORM内容(急切加载配置,获取深度等)都封装在Repository类中,客户端代码只获取结果集。
我尝试使用非常通用的存储库,就像您正在尝试的那样,但我最终为我的域对象编写了特定的查询和存储库。
其他提示
var product = productRepository
.With(x=>x.Customer)
.With(x=>x.Price)
.With(x=>x.Manufacturer)
.GetById(id);
我可以理解你希望如上所述确定对象图的查询深度,但我认为可能有一种更简单的方法。如何而不是选择通过ID返回产品(包括客户,价格和制造商),我只返回产品 - 所有其他东西都是产品的延迟加载属性?
我通过数据访问层中的POCO对象模型“链接”实现了“完全图形可访问性”。这样我就不需要知道在任何时候有多少急切加载的数据,我只需要从对象图中询问我需要什么,并且模型知道什么是加载以及需要从DAL中恢复的内容。看看这些 三个 答案 - 我试着在那里解释我的方法。如果您需要更多说明,请告诉我,我将编辑此答案。
这是一个老问题,但也许它可以帮助某人。我花了一些时间找到一个好的方法,这是我在C#中找到的:
IRepository.cs:
public interface IRepository<TEntity> where TEntity : class
{
IEnumerable<TEntity> GetAll(Expression<Func<TEntity, bool>> where
, params Expression<Func<TEntity, object>>[] properties);
}
Repository.cs
public class Repository<TEntity> : IRepository<TEntity> where TEntity : class
{
private readonly DbSet<TEntity> _dbset;
public Repository(DbSet<TEntity> dbset)
{
_dbset = dbset;
}
public virtual IEnumerable<TEntity> GetAll(Expression<Func<TEntity, bool>> where
, Expression<Func<TEntity, object>>[] properties)
{
if (where == null)
throw new ArgumentNullException(nameof(where));
if (properties == null)
throw new ArgumentNullException(nameof(properties));
var query = _dbset as IQueryable<TEntity>; // _dbSet = dbContext.Set<TEntity>()
query = properties
.Aggregate(query, (current, property) => current.Include(property));
return query.AsNoTracking().Where(where).ToList();
}
}
使用方法:
var repository = new Repository<User>();
var users = repository.GetAll(p => p.Id == 1, d => d.Address, d => d.Carts);
参考:链接
我可以理解您正在尝试做的事情,但您在某种程度上超出了基本存储库模式。
最小的存储库接口可能包括以下方法:
- 获取ID
- 添加
- 消除
如果您在此基础上添加其他方法,您就会开始遇到接口不一定对所有聚合根都有意义的情况。
有时拥有一个完全漂亮的 API 是不可行的。如果你所拥有的对你来说“足够好”,我就会同意。如果您需要摆脱存储库模式以提供更好的 API 来进行编程,那就去做吧!
存储库模式不是万能的/最终的解决方案。有时您需要不同的解决方案。
如果要在存储库之外指出所需的所有包含,可以列出每种通用方法的可选参数(C#):
TEntity Find(Func<TEntity, bool> expression, params string[] eagerLoads);
然后在您的客户端层:
IProductRepository.Find(x => x.Id == id, "Customer", "Price")
如果您想要类型安全,请枚举您的实体:
public enum BusinessEntities { Customer, Price, Manufacturer }
IProductRepository.Find(x => x.Id == id, BusinessEntities.Customer.ToString(), BusinessEntities.Price.ToString())
我认为客户有责任专门询问它想要什么。通用存储库应该只处理基本的CRUD。
在 BaseRepository.cs
中,您可以创建此方法:
public async Task<IEnumerable<T>> GetWithChild(string child)
{
return await _entities.Include(child).ToListAsync();
}
在我的API中,我还实现了一个服务层,但是从API中我只需调用此方法并将其传递给要加载的变量名称。
显然,在您的情况下,您需要添加更多字符串。
我之前发布了一个答案,但我对解决方案仍然不满意。所以这是一个更好的解决方案。
public async Task<IEnumerable<T>> GetAll(params Expression<Func<T, object>>[] properties)
{
IQueryable<T> query = _entities;
query = properties.Aggregate(query, (current, property) => current.Include(property));
return await query.AsNoTracking().ToListAsync();
}
您只需使用以下方法
即可await _service.GetAll(x => x.Customer, x => x.Price, x => x.Manufacturer);