实体框架设计器首先将导航属性作为任务获取

时间:2014-09-10 12:55:56

标签: c# .net entity-framework async-await

任务模式说为了保持一致,一切都必须完全异步或完全不同步。

首先使用实体​​框架设计器,我可以很容易地实现这一目标

var course = await db.Courses.FindAsync(CourseID);

课程是由实体框架生成的DbSet,因此具有所有Async方法。 问题是如果我向该类添加一个导航属性,后者不是DbSet,它不包含任何异步方法定义。

例如,如果我将导航属性添加到Students表,它将被创建为虚拟ICollection学生 这意味着我不能使用异步方法。 但我真正想要的是拥有实体框架来自动生成任务<>为了能够等待导航属性。

有可能吗?我的目标是实现这样的目标:

var course = await db.Courses.FindAsync(CourseID);
var student = await course.Students.FindAsync(StudentID);

目前我的选项是混合async / notAsync代码:

var course = await db.Courses.FindAsync(CourseID);
var student = course.Students.First(p => p.ID = StudentID);

或根本不使用导航属性:

var course = await db.Courses.FindAsync(CourseID);
var student = await db.Students.Where(p => p.CourseID == course.ID && p.ID == StudentsID).FirstAsync();

你能建议一个不需要代码的解决方案吗?

修改 根据{{​​3}}我正在搜索的内容被称为“异步延迟加载”并且还不是可用的功能(也许它永远不会是)。 您似乎可以使用延迟加载或异步功能,也许我应该通过等待Task.Run(course.Students.First(p => p.ID = StudentID))将属性包装在Task中但是我'我不确定这是个好主意。

1 个答案:

答案 0 :(得分:6)

在我看来,延迟加载(在我看来,枚举导航属性将触发数据库访问的情况)是一种糟糕的访问模式,因为它只是意味着数据库访问将发生在令人惊讶的地方,这可以使应用程序性能难以预测。

以下所有解决方案均使用System.Data.Entity的导入。

解决方案1 ​​:使用Include

的急切加载
var course = await db.Courses.Include(c => c.Students).FirstOrDefaultAsync(c => c.ID == CourseID);
var student = course.Students.First(p => p.ID == StudentID);

优点:

  • 加载所有对象所需的一个数据库访问 - 如果您想一次检索多个Course对象,此解决方案可以很好地扩展;
  • Students导航属性已加载,可以自由使用;

缺点:

  • 始终至少有一个数据库访问权限;
  • 即使您只需要一个,也会加载整套相关的Student个对象;

解决方案2 :使用具体集合类中存在的LoadAsync方法;

此解决方案依赖于延迟加载的集合来自EntityCollection<TEntity>类的事实。

首先,我将定义一个扩展方法:

public static async Task LoadAsync<T>(ICollection<T> collection)
    where T : class
{
    if (collection == null) throw new ArgumentNullException("collection");

    var entityCollection = collection as System.Data.Entity.Core.Objects.DataClasses.EntityCollection<T>;

    if (entityCollection == null || entityCollection.IsLoaded) return;
    await entityCollection.LoadAsync(CancellationToken.None).ConfigureAwait(false);
}

然后你可以这样写:

var course = await db.Courses.FindAsync(CourseID);
await course.Students.LoadAsync();
var student = course.Students.First(p => p.ID = StudentID);

优势:

  • 如果对象已在上下文中加载,则可能根本没有数据库访问;
  • 保证加载导航属性Students;

缺点:

  • 易受“N + 1查询”问题影响;
  • Course和一组相关的Student对象都可能变得过时,这可能会引发并发问题; (请注意,影响关系的并发问题比影响单个记录的并发问题更难解决)

解决方案3 在具体类上使用CreateSourceQuery方法仅加载所需的Student对象。

好的,这样做不起作用,实际上是一个非常糟糕的主意。

但是,可以编写具有相同优点/缺点的解决方案,但另一种方式是:

var course = await db.Courses.FindAsync(CourseID);
var studentsQuery = from c in db.Courses
                    where c.ID == CourseID
                    from s in c.Students
                    select s;
var student = await studentsQuery.FirstAsync(p => p.ID = StudentID);

优势:

  • 您只加载要使用的一个Student对象;

缺点:

  • 未加载Students导航属性,这意味着在未触发数据库访问的情况下无法使用它;
  • 第二行将始终触发数据库访问(容易受到“N + 1查询”问题的影响,甚至可能会运行方法时间);

解决方案4 :热切加载,更具选择性:在初始LINQ查询中加载您感兴趣的课程和学生。

我并非100%确定该解决方案可以按照书面形式运作。

var query = from c in db.Courses
            where c.ID == CourseID
            select new { course = c, student = c.Students.First(p => p.ID == StudentID) };

var result = await query.FirstOrDefaultAsync();
var course = result.course;
var student = result.student;

优点:

  • 只需一次数据库访问即可检索这两个对象;
  • 您只检索要处理的对象;

缺点:

  • 未加载Students导航属性,这意味着在未触发数据库访问的情况下无法使用它;

**何时使用哪种解决方案? **

  • 如果您需要填充导航属性(或者因为您知道您将使用其大部分元素,或者因为您想要将父实体传递给另一个允许使用该属性的组件,它想要),然后使用解决方案1或2;
  • 如果您不需要填写导航属性,请使用解决方案4.如果您明确地已经加载了Course对象,则仅使用解决方案3;