.net 当使用EF的存储库模式时,是否应该跟踪返回的项目?
现在忽略关于存储库模式是否应该与EF一起使用的争论,我想问一下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
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);