簡體   English   中英

實體框架中的異步查詢和延遲加載

[英]Asynchronous queries and lazy loading in Entity Framework

我們正在將實體框架4.2與模型優先方法和DbContext代碼生成一起使用。

假設我們在實體框架中有以下數據模型:

class Person
{
    public string Name { get; set; }
    public Address Address { get; set; }
}

class Address
{
    public string City; { get; set; }
}

該場景如下:

  1. ViewModel要從數據庫加載一些數據
  2. 創建用於加載數據的任務(異步操作)。 這是因為我們不希望UI在加載數據時凍結。
  3. 任務(在單獨的線程中執行)創建新的數據庫上下文並從數據庫中加載數據(例如Person對象)
  4. 任務完成,數據庫上下文被破壞。
  5. 通知主線程有關任務的完成。 現在,主線程可以訪問已加載的Person對象。
  6. 視圖試圖通過數據綁定在文本框中顯示人員的姓名和地址
  7. 該視圖訪問Person.Name(此處沒有問題)
  8. 該視圖訪問Person.Address.City-> OOPS! 數據庫上下文已經被處置(因為加載是在單獨的線程中完成的),並且由於延遲加載,無法訪問Person.Address!

在階段3中,人員通過以下方式加載:

using (DatabaseContext context = new DatabaseContext())
{
    Person person = from p in context.Persons.FirstOrDefault();
    return person;
}

好的,我知道(理論上)我可以通過兩種方式強制加載Address對象:1)使用DbSet.Include,例如:

context.Persons.Include("Address").FirstOrDefault();

2)在數據庫上下文仍處於活動狀態時訪問Person.Address,因為這將強制加載地址

Person person = context.Persons.FirstOrDefault();
Address address = person.Address;
return person;

當然,第一個是首選的解決方案,因為它不如第二個那么丑陋(僅訪問該屬性以強制加載數據,然后丟棄結果是丑陋的)。 同樣,如果是一個集合(例如人員列表),我將不得不遍歷該集合並分別為每個人訪問該地址。 第一個解決方案的問題在於,只有DbSet具有Include方法,而從查詢返回的所有其他集合都沒有。 因此,假設我具有以下數據庫結構

class Resource {}
class Person : Resource { public Address Address { get; set; } }
class Appointment { public IList<Resource> Resources { get; set; } }

而且我想加載所有特定的約會,並在每個人的資源中包含地址,這給我帶來了麻煩(或者至少我沒有辦法為它編寫查詢)。 這是因為context.Appointments.Resources不是DbSet類型,而是一個沒有Include方法的ICollection。 (好吧,也許在這種情況下,我可以以某種方式從context.Persons而不是context.Appointments編寫查詢,這樣我可以使用Include,但是在許多情況下這是不可能的)


所以基本上問題是

  1. 首先,這是進行異步數據庫訪問的正確方法嗎?
  2. 如何解決延遲加載問題? 關閉延遲加載不是解決方案(除非只能對特定實體執行此操作?)

您可以在應用程序或測試啟動時設置包含策略的實現。

首先定義一個擴展方法:包含,它定義實際的包含機制(默認情況下不執行任何操作)

/// <summary>
/// Extension methods specifically for include since this is essential for DomainContext but not part of IQueryable by default
/// </summary>
public static class QueryableIncludeExtensions
{
    public static IIncluder Includer = new NullIncluder();

    public static IQueryable<T> Include<T, TProperty>(this IQueryable<T> source, Expression<Func<T, TProperty>> path)
         where T : class
    {
        return Includer.Include(source, path);
    }

    public interface IIncluder
    {
        IQueryable<T> Include<T, TProperty>(IQueryable<T> source, Expression<Func<T, TProperty>> path) where T : class;
    }

    internal class NullIncluder : IIncluder
    {
        public IQueryable<T> Include<T, TProperty>(IQueryable<T> source, Expression<Func<T, TProperty>> path)
             where T : class
        {
            return source;
        }
    }
}

然后創建一個EF特定的includer實現,例如:

internal class DbIncluder : QueryableIncludeExtensions.IIncluder
{
    public IQueryable<T> Include<T, TProperty>(IQueryable<T> source, Expression<Func<T, TProperty>> path)
        where T : class
    {
        return DbExtensions.Include(source, path);
    }
}

最后,將DbIncluder實現掛接到您需要的項目中,在我的情況下,我這樣做是:

public class DomainContext : DbContext, IDomainContext
{
    static DomainContext()
    {
        // register the DbIncluder for making sure the right call to include is made (standard is null)
        QueryableIncludeExtensions.Includer = new DbIncluder();
    }

現在,IQueryable始終具有擴展方法:包含可用。 如果需要,可以將其擴展為IEnumerable。 實際的實現只需通過QueryableIncludeExtensions.Includer設置即可

暫無
暫無

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

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