对于简单实体,即没有引用其他实体(导航属性)的实体,您的方法基本上没问题。可以浓缩为:
public static List<Table1> GetAllDatainTable()
{
using (var context = new EFContext())
{
return context.Table1s.ToList();
}
}
但是,在大多数实际场景中,您将希望利用导航属性之类的东西来建立实体之间的关系。 IE。订单引用了具有地址详细信息的客户,并包含每个都引用产品等的 OrderLines。以这种方式返回实体会出现问题,因为任何接受此类方法返回的实体的代码都应该是完整的或可完成的实体。
例如,如果我有一个返回订单的方法,并且我有各种使用该订单信息的代码:其中一些代码可能会尝试获取有关订单客户的信息,而其他代码可能会对产品感兴趣。 EF 支持延迟加载,因此可以在需要时提取相关数据,但这仅在 DbContext 的生命周期内有效。像这样的方法会处理 DbContext,因此无法使用延迟加载。
一种选择是预先加载所有内容:
using (var context = new EFContext())
{
var order = context.Orders
.Include(o => o.Customer)
.ThenInclude(c => c.Addresses)
.Include(o => o.OrderLines)
.ThenInclude(ol => ol.Product)
.Single(o => o.OrderId == orderId);
return order;
}
但是,这种方法有两个缺点。首先,这意味着每次我们获取订单时都会加载相当多的数据。消费代码可能不关心客户或订单行,但无论如何我们已经加载了它。其次,随着系统的发展,可能会引入新的关系,当包含越来越多的相关数据时,旧代码不一定会被更新以包含导致潜在的NullReferenceExceptions、错误或性能问题。视图或最初使用该实体的任何东西可能不希望引用这些新关系,但是一旦您开始将实体传递给视图、视图和其他方法,任何接受实体的代码都应该期望依赖于以下事实:实体是完整或可以完成。无论是否加载数据,都可能以不同级别的“完整性”和代码处理加载订单,这可能是一场噩梦。作为一般建议,我建议不要在加载它们的 DbContext 范围之外传递实体。
更好的解决方案是利用投影从适合您的代码使用的实体中填充视图模型。 WPF 通常使用 MVVM 模式,因此这意味着使用 EF 的 Select 方法或 Automapper 的 ProjectTo 方法来根据每个消费者的需求填充视图模型。当您的代码与包含数据视图和此类需求的 ViewModel 一起使用时,然后根据需要加载和填充实体,这使您可以生成更高效(快速)和有弹性的查询来获取数据。
如果我有一个视图列出了带有创建日期、客户名称和产品/w 数量列表的订单,我们为该视图定义一个视图模型:
[Serializable]
public class OrderSummary
{
public int OrderId { get; set; }
public string OrderNumber { get; set; }
public DateTime CreatedAt { get; set; }
public string CustomerName { get; set; }
public ICollection<OrderLineSummary> OrderLines { get; set; } = new List<OrderLineSummary>();
}
[Serializable]
public class OrderLineSummary
{
public int OrderLineId { get; set; }
public int ProductId { get; set; }
public string ProductName { get; set; }
public int Quantity { get; set; }
}
然后在 Linq 查询中投影视图模型:
using (var context = new EFContext())
{
var orders = context.Orders
// add filters & such /w Where() / OrderBy() etc.
.Select(o => new OrderSummary
{
OrderId = o.OrderId,
OrderNumber = o.OrderNumber,
CreatedAt = o.CreatedAt,
CustomerName = o.Customer.Name,
OrderLines = o.OrderLines.Select( ol => new OrderLineSummary
{
OrderLineId = ol.OrderLineId,
ProductId = ol.Product.ProductId,
ProductName = ol.Product.Name,
Quantity = ol.Quantity
}).ToList()
}).ToList();
return orders;
}
请注意,我们不需要担心急切加载相关实体,如果以后有订单或客户或此类获得新关系,上述查询将继续工作,只有在更新 if 新的关系信息对于它所服务的视图很有用。它可以构建一个更快、内存占用更少的查询,获取更少的字段以通过网络从数据库传递到应用程序,并且可以使用索引来进一步调整这一点,以应对高使用率的查询。
更新:
其他性能提示:通常避免将GetAll*() 等方法作为最低公分母方法。我在使用此类方法时遇到的太多性能问题是:
var ordersToShip = GetAllOrders()
.Where(o => o.OrderStatus == OrderStatus.Pending)
.ToList();
foreach(order in ordersToShip)
{
// do something that only needs order.OrderId.
}
其中GetAllOrders() 返回List<Order> 或IEnumerable<Order>。有时会有GetAllOrders().Count() > 0之类的代码。
这样的代码效率极低,因为 GetAllOrders() 从数据库中获取 *所有 记录,只是将它们加载到应用程序的内存中,以便以后过滤或计数等。
如果您遵循通过方法将 EF DbContext 和实体抽象到服务/存储库中的路径,那么您应该确保服务公开方法以产生高效的查询,或者放弃抽象并直接在数据所在的位置利用 DbContext需要。
var orderIdsToShip = context.Orders
.Where(o => o.OrderStatus == OrderStatus.Pending)
.Select(o => o.OrderId)
.ToList();
var customerOrderCount = context.Customer
.Where(c => c.CustomerId == customerId)
.Select(c => c.Orders.Count())
.Single();
EF is extremely powerful and when selected to service your application should be embraced as part of the application to give the maximum benefit.我建议避免编码以纯粹为了抽象而将其抽象出来,除非您希望使用单元测试来隔离对数据的依赖与模拟。在这种情况下,我建议利用 DbContext 的工作单元包装器和利用 IQueryable 的存储库模式来简化隔离业务逻辑。