使用 EF6 的 LINQ 查询优化

时间:2021-04-19 12:10:58

标签: c# wpf entity-framework linq entity-framework-6

我第一次尝试使用 LINQ,只是想提出一个小问题,以确保这是否是解决问题的最佳方式。我想要一个表中每个值的列表。到目前为止,这就是我所拥有的,并且有效,但这是以 LINQ 友好方式收集所有内容的最佳方式吗?

    public static List<Table1> GetAllDatainTable()
    {
        List<Table1> Alldata = new List<Table1>();

        using (var context = new EFContext())
        {
           Alldata = context.Tablename.ToList();
        }
        return Alldata;
    }

1 个答案:

答案 0 :(得分:0)

对于简单实体,即没有对其他实体(导航属性)的引用的实体,您的方法基本上没问题。可以简化为:

public static List<Table1> GetAllDatainTable()
{
    using (var context = new EFContext())
    {
       return context.Table1s.ToList();
    }
}

但是,在大多数实际场景中,您将希望利用导航属性之类的东西来处理实体之间的关系。 IE。订单引用带有地址详细信息的客户,并包含每个引用产品等的订单行。以这种方式返回实体会出现问题,因为任何接受由此类方法返回的实体的代码都应该获取完整或可完成的实体。< /p>

例如,如果我有一个返回订单的方法,并且我有使用该订单信息的各种代码:其中一些代码可能会尝试获取有关订单客户的信息,其他代码可能对产品感兴趣。 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;
}

但是,这种方法有两个缺点。首先,这意味着每次我们获取一个订单都会加载更多的数据。消费代码可能不关心客户或订单行,但我们已经加载了所有内容。其次,随着系统的发展,可能会引入新的关系,当越来越多的相关数据被包含时,旧代码不一定会被注意到更新以包含潜在的NullReferenceException、错误或性能问题。视图或最初使用该实体的任何东西可能不希望引用这些新关系,但是一旦您开始将实体传递给视图、从视图和其他方法,任何接受实体的代码都应该依赖于以下事实:实体完整的或可以变得完整的。无论是否加载数据,以各种级别的“完整性”和代码处理潜在地加载订单可能是一场噩梦。作为一般性建议,我建议不要在加载它们的 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< /em> 新的关系信息对其服务的视图很有用。它可以组成一个更快、内存占用更少的查询,获取更少的字段,以便通过线路从数据库传递到应用程序,并且可以使用索引进一步调整这一点,以用于高使用率的查询。

更新:

其他性能提示:通常避免将 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 的存储库模式来简化业务逻辑的隔离。