c# - Entity Framework 中的异步查询和延迟加载

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

我们使用 Entity Framework 4.2 模型优先方法和 DbContext 代码生成。

假设我们在 Entity Framework 中有以下数据模型:

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. View 试图通过数据绑定(bind)在文本框中显示人的姓名和地址
  7. View 访问 Person.Name(这里没问题)
  8. View 访问 Person.Address.City -> 糟糕!数据库上下文已被处理(因为加载是在单独的线程中完成的)并且由于延迟加载 Person.Address 不可访问!

第三阶段,person加载方式如下:

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. 如何解决延迟加载的问题?关闭延迟加载不是解决方案(除非它只能针对特定实体完成?)

最佳答案

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

首先定义一个扩展方法: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 设置

关于c# - Entity Framework 中的异步查询和延迟加载,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9109869/

相关文章:

c# - Entity Framework 'table-per-concrete-class' 映射到错误的表

c# - 如何在不运行异步的情况下返回任务类型<mytype>

javascript - 使用异步/等待不起作用

python 和数据库异步请求(又名即发即弃): how to?

c# - 如何避免 split ?

C# 在特定情况下使用小数位格式化百分比

c# - Entity Framework 以不确定的方式未经许可删除有效内容?

javascript - 异步代码和 mongodb 的问题

c# - 在具有相同命名空间的多个项目的解决方案中找不到命名空间

c# - .NET 加密类的线程安全性?