我想知道如何在使用Repository模式时正确处理复杂对象图的急切加载问题。我想这不是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);
I couldn't achieve this。即使我可以 - 我不确定该解决方案是否优雅。
这导致我缺少一些基本的想法(缺乏任何例子)。有没有不同的方法来处理这个?什么是最佳实践?
答案 0 :(得分:9)
有趣的问题,我相信你不是第一个遇到麻烦的人(我绝对有)。
对我来说,真正的问题是:你想在哪里放置你急切的加载逻辑?
客户端代码中的存储库之外
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类中,客户端代码只获取结果集。
我尝试使用非常通用的存储库,就像您正在尝试的那样,但我最终为我的域对象编写了特定的查询和存储库。
答案 1 :(得分:2)
var product = productRepository
.With(x=>x.Customer)
.With(x=>x.Price)
.With(x=>x.Manufacturer)
.GetById(id);
我可以理解你希望如上所述确定对象图的查询深度,但我认为可能有一种更简单的方法。如何而不是选择通过ID返回产品(包括客户,价格和制造商)我只返回产品 - 所有其他东西都是产品的延迟加载属性?
我通过数据访问层中的POCO对象模型“链接”实现了“完全图形可访问性”。这样我就不需要知道在任何时候有多少急切加载的数据,我只需要从对象图中询问我需要什么,并且模型知道什么是加载的以及需要从DAL中恢复的内容。看看these three answers - 我试着解释一下我的方法。如果您需要更多说明,请告诉我,我将编辑此答案。
答案 2 :(得分:2)
这是一个古老的问题,但也许它可以帮助某人。我花了一些时间找到一个好的方法,这是我在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);
参考:Link
答案 3 :(得分:0)
我很欣赏你要做的事情,但是你有点超出了基本的存储库模式。
最小存储库接口可能包含以下方法:
如果您在其上添加其他方法,则会开始遇到接口不一定对所有聚合根有意义的情况。
有时候拥有一个完全漂亮的API是不可行的。如果你的作品对你来说“足够好”,我会坚持下去。如果您需要远离存储库模式以提供更好的API来编程,请执行此操作!
存储库模式不是be-all / end-all解决方案。有时你需要一个不同的解决方案。
答案 4 :(得分:0)
如果要在存储库之外指出所需的所有包含,可以列出每种通用方法的可选参数(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。
答案 5 :(得分:0)
在$ fn_exists() { test x$(type -t $1) = xfunction; }
$ fn_exists func1 && echo yes || echo no
no
$ func1() { echo hi from func1; }
$ func1
hi from func1
$ fn_exists func1 && echo yes || echo no
yes
中,您可以创建此方法:
BaseRepository.cs
在我的API中,我还实现了一个服务层,但是从API中我只需调用此方法并将其传递给要加载的变量名称。
显然,在您的情况下,您需要添加更多字符串。
答案 6 :(得分:0)
我之前发布了一个答案,但我对解决方案仍然不满意。所以这是一个更好的解决方案。
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);