.net 当使用EF的存储库模式时,是否应该跟踪返回的项目?

.net 当使用EF的存储库模式时,是否应该跟踪返回的项目?,.net,entity-framework,entity-framework-core,repository-pattern,.net,Entity Framework,Entity Framework Core,Repository Pattern,现在忽略关于存储库模式是否应该与EF一起使用的争论,我想问一下EF是否应该返回跟踪的实体。例如,下面的代码的Get()方法返回一个跟踪的实体 public virtual async Task<TSqlTable> Get(int id) { var result = await _dbContext.Set<TSqlTable>().Where(set => set.Id == id).SingleOrDefaultAsync(); return

现在忽略关于存储库模式是否应该与EF一起使用的争论,我想问一下EF是否应该返回跟踪的实体。例如,下面的代码的
Get()
方法返回一个跟踪的实体

public virtual async Task<TSqlTable> Get(int id)
{
    var result = await _dbContext.Set<TSqlTable>().Where(set => set.Id == id).SingleOrDefaultAsync();
    return result;
}

public async Task<TSqlTable> Update(TSqlTable item)
{
    _dbContext.Set<TSqlTable>().Update(item);
    await _dbContext.SaveChangesAsync();
    return item;
}
AsNoTracking()
添加到
Get()
方法中,这样就不会跟踪回购协议之外的内容了,这样会更有意义吗

public virtual async Task<TSqlTable> Get(int id)
{
    var result = await _dbContext.Set<TSqlTable>().Where(set => set.Id == id).AsNoTracking().SingleOrDefaultAsync();
    return result;
}

public async Task<TSqlTable> Update(TSqlTable item)
{
    _dbContext.Set<TSqlTable>().Update(item);
    await _dbContext.SaveChangesAsync();
    return item;
}
公共虚拟异步任务Get(int-id) { var result=await _dbContext.Set().Where(Set=>Set.Id==Id).AsNoTracking().SingleOrDefaultAsync(); 返回结果; } 公共异步任务更新(TSqlTable项) { _dbContext.Set().Update(项); wait_dbContext.saveChangesSync(); 退货项目; }
与在附加存储库层中包装EF DbContext的许多问题一样,您需要决定由谁来决定。决定是否跟踪实体的不是存储库,而是使用存储库的代码。因此,您的第二层存储库应该有一些方法,用于调用代码声明其实体跟踪意图

这就是为什么设计良好的存储库应该将实体公开为
IQueryable
,而不是
IEnumerable
tenty


因为您决定将查询设计从调用代码中移除并在存储库包装器中实现,所以现在需要为调用代码提供一种方法来配置查询的跟踪行为。

与在附加存储库层中包装EF DbContext的许多问题一样,您需要决定由谁来决定。决定是否跟踪实体的不是存储库,而是使用存储库的代码。因此,您的第二层存储库应该有一些方法,用于调用代码声明其实体跟踪意图

这就是为什么设计良好的存储库应该将实体公开为
IQueryable
,而不是
IEnumerable
tenty


因为您决定将查询设计从调用代码中移除并在存储库包装器中实现,所以现在需要为调用代码提供一种方法来配置查询的跟踪行为。

存储库模式可以作为业务逻辑和DbContext之间的良好抽象,用于测试目的。虽然可以模拟DbContext,但它并不漂亮,模拟存储库调用要简单得多。但是,我认为一个通用的存储库模式是一种反模式。他们要么以贫血告终,没有任何好处,要么过于复杂,试图从调用代码中抽象出EF ISM。您还将在控制器/服务中堆积大量的存储库依赖项,以完成任何非琐碎的工作

例如,如果我有一个CreateOrderController为订单创建屏幕提供服务,我希望能够创建订单、订单行、提供产品列表以及关联客户。使用通用存储库:

public CreateOrderController(IUnitOfWorkFactory unitOfWorkFactory, 
   IOrderRepository orderRepository,  
   IOrderLineRepository orderLineRepository, 
   IProductRepository productRepository, 
   ICustomerRepository customerRepository)
{  /* ... */ }
其中每个存储库都是
存储库
,我尝试利用通用方法。我总是会使用特定于订单或其他实体的方法,这些方法会偏离通用风格。我还打算在返回我不需要的细节方面做出潜在的妥协。例如,如果是ManageOrderController,我想知道是否有任何订单行包含产品: 我在OrderLineRepository中有一个特定的方法:

bool HasOrderLineWithProduct(int orderId, int productId);
。。。我的存储库中到处都是针对每个场景的类似方法,或者我的控制器代码中到处都是低效的“通用”方法,例如:

var orderLines = _orderLineRepository.GetOrderLinesForOrder(orderId);
var hasProduct = orderLines.Any(x => x.ProductId == productId);
这里的问题是,我的存储库已将该订单的所有订单行加载到内存中,这样我就可以对这些订单行执行带有进一步条件的exists检查。这并不理想

正如David指出的,要利用带有EF的存储库,您应该采用IQueryable和工作单元模式来管理DbContext的生命周期范围。最糟糕的情况是,DbContext的作用域可以限定为web请求,并注入到控制器、服务和存储库中。不过,我更喜欢使用明确的范围界定,这样就非常清楚谁负责提交工作单元。使用IQueryable,业务逻辑可以管理范围,并确定它希望如何使用数据,而不是在存储库中呈现大量类似的方法,或大量的条件复杂性

对于上面的CreateOrderController示例,我有一些更像:

public CreateOrderController(IUnitOfWorkFactory unitOfWorkFactory,,
     ICreateOrderRepository createOrderRepository)
{  /* ... */ }
。。。存储库为该控制器提供服务的位置。设置测试模拟更简单。当我需要检索客户或产品引用时,存储库可以提供以下服务:

IQueryable<Customer> GetCustomerById(int customerId);
IQueryable<Product> GetAllProducts();
通过返回一个
IQueryable
,消费代码可以利用Linq做它需要的任何事情。可以模拟GetOrderById进行测试,以返回一个存根实体,其中包含测试的相关细节、空列表等。我们不需要几十种基于场景的方法,也不需要执行昂贵的查询。存储库中没有复杂的表达式树参数来提供动态筛选、排序或分页。Linq和
IQueryable
已经提供了这一功能。它不会像复杂表达式那样“泄漏”EF isms,因为传递到存储库中的任何表达式等仍然需要符合EF的规则。其结果是能够利用严格高效的查询

存储库还充当新实体的工厂。当我开始创建新订单时,我不会让我的控制器/服务负责:

var newOrder = new Order();
// populate order lines.
context.Orders.Add(newOrder);
context.SaveChanges();
相反,my repository公开了一个CreateOrder方法:

Order CreateOrder(string OrderNumber, Customer customer, IEnumerable<Product> products);
Order CreateOrder(字符串OrderNumber、Customer、IEnumerable产品);
这可能是一个CustomerId和一组ProductID,这取决于我如何
var newOrder = new Order();
// populate order lines.
context.Orders.Add(newOrder);
context.SaveChanges();
Order CreateOrder(string OrderNumber, Customer customer, IEnumerable<Product> products);