实体框架中的异步查询和延迟加载

时间:2012-02-02 09:28:10

标签: c# entity-framework asynchronous lazy-loading dbcontext

我们正在使用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. 任务(在单独的线程中执行)创建新的 数据库上下文并从数据库
  4. 加载数据(例如Person对象)
  5. 任务完成,数据库上下文被破坏。
  6. 主线程被通知任务完成。主线可以 现在访问加载的Person对象。
  7. 视图尝试通过数据绑定在文本框中显示人员的姓名和地址
  8. 视图访问Person.Name(此处没有问题)
  9. 视图访问Person.Address.City - > OOPS!数据库上下文已被处理(因为加载是在单独的线程中完成的),并且由于延迟加载,Person.Address无法访问!
  10. 在第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类型,而是ICollection没有Include方法。 (好吧,也许在这种情况下,我可以从context.Persons而不是context.Appointments以某种方式编写一个查询,以便我可以使用Include,但有很多场景,这是不可能的)


    基本上问题是

    1. 这是首先进行异步数据库访问的正确方法吗?
    2. 如何解决延迟加载的问题?关闭延迟加载不是一个解决方案(除非它只能针对特定实体进行?)

1 个答案:

答案 0 :(得分:2)

您可以在启动应用程序或测试时设置包含策略的实现。

首先定义一个Extension方法:Include定义实际的包含机制(默认为无效)

/// <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特定的包含器实现,如:

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总是有扩展方法:Include available。如果需要,可以将其扩展为IEnumerable。实际的实现只是由QueryableIncludeExtensions.Includer

设置