簡體   English   中英

LINQ 使用 EF6 進行查詢優化

[英]LINQ Query optimalisation using EF6

我第一次嘗試 LINQ 並且只是想發布一個小問題以確保這是否是 go 關於它的最佳方式。 我想要一個表中每個值的列表。 到目前為止,這就是我所擁有的,並且它有效,但這是 go 關於以 LINQ 友好的方式收集所有內容的最佳方式嗎?

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

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

對於簡單實體,即沒有引用其他實體(導航屬性)的實體,您的方法基本上沒問題。 它可以濃縮為:

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

但是,在大多數實際場景中,您將希望利用導航屬性之類的東西來建立實體之間的關系。 即,訂單引用具有地址詳細信息的客戶,並包含每個都引用產品等的訂單行。以這種方式返回實體會出現問題,因為任何接受此類方法返回的實體的代碼都應該是完整的或可完成的實體。

例如,如果我有一個返回訂單的方法,並且我有各種使用該訂單信息的代碼:其中一些代碼可能會嘗試獲取有關訂單客戶的信息,而其他代碼可能對產品感興趣。 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 的 scope 之外傳遞實體。

更好的解決方案是利用投影從適合您的代碼使用的實體中填充視圖模型。 WPF 經常使用 MVVM 模式,因此這意味着使用 EF 的Select方法或 Automapper 的ProjectTo方法來根據每個消費者的需求填充視圖模型。 當您的代碼使用包含數據視圖和此類需求的 ViewModel 時,然后根據需要加載和填充實體,這允許您生成更高效(快速)和彈性查詢來獲取數據。

如果我有一個視圖列出具有創建日期、客戶名稱和產品列表/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; }
}

然后在 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;
}

請注意,我們不需要擔心急切加載相關實體,如果以后有訂單或客戶或此類獲得新關系,上述查詢將繼續工作,只有在新關系信息對以下內容有用時才會更新它服務的視圖。 它可以組成一個更快、更少的 memory 密集型查詢,獲取更少的字段以通過網絡從數據庫傳遞到應用程序,並且可以使用索引來進一步調整它以用於高使用查詢。

更新:

其他性能提示:通常避免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()從數據庫中獲取 *所有記錄,只是將它們加載到應用程序中的 memory 中,以便稍后過濾或計數等。

如果您遵循通過方法將 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的存儲庫模式來簡化隔離業務邏輯。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM