我们使用 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; }
}
场景如下:
- ViewModel 想要从数据库加载一些数据
- 创建一个任务(异步操作)来加载数据。这 是因为我们不希望 UI 在加载数据时卡住。
- 任务(在单独的线程中执行)创建新的 数据库上下文并从数据库加载数据(例如 Person 对象)
- 任务完成,数据库上下文被销毁。
- 主线程收到任务完成通知。主线程可以 现在访问加载的 Person 对象。
- View 试图通过数据绑定(bind)在文本框中显示人的姓名和地址
- View 访问 Person.Name(这里没问题)
- 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,但在很多情况下这是不可能的)
所以基本上问题是:
- 首先,这是执行异步数据库访问的正确方法吗?
- 如何解决延迟加载的问题?关闭延迟加载不是解决方案(除非它只能针对特定实体完成?)
最佳答案
您可以在应用程序或测试启动时设置包含策略的实现。
首先定义一个扩展方法: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/