简体   繁体   English

LINQ 使用 EF6 进行查询优化

[英]LINQ Query optimalisation using EF6

I'm trying my hand at LINQ for the first time and just wanted to post a small question to make sure if this was the best way to go about it.我第一次尝试 LINQ 并且只是想发布一个小问题以确保这是否是 go 关于它的最佳方式。 I want a list of every value in a table.我想要一个表中每个值的列表。 So far this is what I have, and it works, but is this the best way to go about collecting everything in a LINQ friendly way?到目前为止,这就是我所拥有的,并且它有效,但这是 go 关于以 LINQ 友好的方式收集所有内容的最佳方式吗?

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

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

For simple entities, that is an entity that has no references to other entities (navigation properties) your approach is essentially fine.对于简单实体,即没有引用其他实体(导航属性)的实体,您的方法基本上没问题。 It can be condensed down to:它可以浓缩为:

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

However, in most real-world scenarios you are going to want to leverage things like navigation properties for the relationships between entities.但是,在大多数实际场景中,您将希望利用导航属性之类的东西来建立实体之间的关系。 Ie an Order references a Customer with Address details, and contains OrderLines which each reference a Product, etc. Returning entities this way becomes problematic because any code that accepts the entities returned by a method like this should be getting either complete, or completable entities.即,订单引用具有地址详细信息的客户,并包含每个都引用产品等的订单行。以这种方式返回实体会出现问题,因为任何接受此类方法返回的实体的代码都应该是完整的或可完成的实体。

For instance if I have a method that returns an order, and I have various code that uses that order information: Some of that code might try to get info about the order's customer, other code might be interested in the products.例如,如果我有一个返回订单的方法,并且我有各种使用该订单信息的代码:其中一些代码可能会尝试获取有关订单客户的信息,而其他代码可能对产品感兴趣。 EF supports lazy loading so that related data can be pulled if, and when needed, however that only works within the lifespan of the DbContext. EF 支持延迟加载,因此可以在需要时提取相关数据,但这仅在 DbContext 的生命周期内有效。 A method like this disposes the DbContext so Lazy Loading is off the cards.像这样的方法会处理 DbContext,因此延迟加载是不可能的。

One option is to eager load everything:一种选择是急切加载所有内容:

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;
}

However, there are two drawbacks to this approach.但是,这种方法有两个缺点。 Firstly, it means loading considerably more data every time we fetch an order.首先,这意味着每次我们获取订单时都要加载更多的数据。 The consuming code may not care about the customer or order lines, but we've loaded it all anyways.消费代码可能不关心客户或订单行,但无论如何我们已经加载了它。 Secondly, as systems evolve, new relationships may be introduced that older code won't necessarily be noticed to be updated to include leading to potential NullReferenceException s, bugs, or performance issues when more and more related data gets included.其次,随着系统的发展,可能会引入新的关系,当包含越来越多的相关数据时,旧代码不一定会被更新以包括导致潜在的NullReferenceException 、错误或性能问题。 The view or whatever is initially consuming this entity may not expect to reference these new relationships, but once you start passing around entities to views, from views, and to other methods, any code accepting an entity should expect to rely on the fact that the entity is complete or can be made complete.视图或最初使用该实体的任何东西可能不希望引用这些新关系,但是一旦您开始将实体传递给视图、视图和其他方法,任何接受实体的代码都应该期望依赖于以下事实:实体完整的或可以完成的。 It can be a nightmare to have an Order potentially loaded in various levels of "completeness" and code handling whether data is loaded or not.无论是否加载数据,都可能以不同级别的“完整性”和代码处理加载订单,这可能是一场噩梦。 As a general recommendation, I advise not to pass entities around outside of the scope of the DbContext that loaded them.作为一般建议,我建议不要在加载它们的 DbContext 的 scope 之外传递实体。

The better solution is to leverage projection to populate view models from the entities suited to your code's consumption.更好的解决方案是利用投影从适合您的代码使用的实体中填充视图模型。 WPF often utilizes the MVVM pattern, so this means using EF's Select method or Automapper's ProjectTo method to populate view models based each of your consumer's needs. WPF 经常使用 MVVM 模式,因此这意味着使用 EF 的Select方法或 Automapper 的ProjectTo方法来根据每个消费者的需求填充视图模型。 When your code is working with ViewModels containing the data views and such need, then loading and populating entities as needed this allows you to produce far more efficient (fast) and resilient queries to get data out.当您的代码使用包含数据视图和此类需求的 ViewModel 时,然后根据需要加载和填充实体,这允许您生成更高效(快速)和弹性查询来获取数据。

If I have a view that lists orders with a created date, customer name, and list of products /w quantities we define a view model for the view:如果我有一个视图列出具有创建日期、客户名称和产品列表/w 数量的订单,我们为该视图定义一个视图 model:

[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; }
}

then project the view models in the Linq query:然后在 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;
}

Note that we don't need to worry about eager loading related entities, and if later down the road an order or customer or such gains new relationships, the above query will continue to work, only being updated if the new relationship information is useful for the view(s) it serves.请注意,我们不需要担心急切加载相关实体,如果以后有订单或客户或此类获得新关系,上述查询将继续工作,只有在新关系信息对以下内容有用时才会更新它服务的视图。 It can compose a faster, less memory intensive query fetching fewer fields to be passed over the wire from the database to the application, and indexes can be employed to tune this even further for high-use queries.它可以组成一个更快、更少的 memory 密集型查询,获取更少的字段以通过网络从数据库传递到应用程序,并且可以使用索引来进一步调整它以用于高使用查询。

Update:更新:

Additional performance tips: Generally avoid methods like GetAll*() as a lowest common denominator method.其他性能提示:通常避免GetAll*()等方法作为最低公分母方法。 Far too many performance issues I come across with methods like this are in the form of:我用这样的方法遇到的太多性能问题是:

var ordersToShip = GetAllOrders()
    .Where(o => o.OrderStatus == OrderStatus.Pending)
    .ToList();
foreach(order in ordersToShip)
{
    // do something that only needs order.OrderId.
}

Where GetAllOrders() returns List<Order> or IEnumerable<Order> .其中GetAllOrders()返回List<Order>IEnumerable<Order> Sometimes there is code like GetAllOrders().Count() > 0 or such.有时会有GetAllOrders().Count() > 0之类的代码。

Code like this is extremely inefficient because GetAllOrders() fetches * all records from the database, only to load them into memory in the application to later be filtered down or counted etc.这样的代码效率极低,因为GetAllOrders()从数据库中获取 *所有记录,只是将它们加载到应用程序中的 memory 中,以便稍后过滤或计数等。

If you're following a path to abstract away the EF DbContext and entities into a service / repository through methods then you should ensure that the service exposes methods to produce efficient queries, or forgo the abstraction and leverage the DbContext directly where data is needed.如果您遵循通过方法将 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. EF is extremely powerful and when selected to service your application should be embraced as part of the application to give the maximum benefit. I recommend avoiding coding to abstract it away purely for the sake of abstraction unless you are looking to employ unit testing to isolate the dependency on data with mocks.我建议避免编码以纯粹为了抽象而将其抽象出来,除非您希望使用单元测试来隔离对数据的依赖与模拟。 In this case I recommend leveraging a unit of work wrapper for the DbContext and the Repository pattern leveraging IQueryable to make isolating business logic simple.在这种情况下,我建议利用 DbContext 的工作单元包装器和利用IQueryable的存储库模式来简化隔离业务逻辑。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM