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

Sim*_*one 4 .net c# entity-framework async-await

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

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

var course = await db.Courses.FindAsync(CourseID);
Run Code Online (Sandbox Code Playgroud)

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

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

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

var course = await db.Courses.FindAsync(CourseID);
var student = await course.Students.FindAsync(StudentID);
Run Code Online (Sandbox Code Playgroud)

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

var course = await db.Courses.FindAsync(CourseID);
var student = course.Students.First(p => p.ID = StudentID);
Run Code Online (Sandbox Code Playgroud)

或根本不使用导航属性:

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

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

编辑 根据https://entityframework.codeplex.com/wikipage?title=Task-based%20Asynchronous%20Pattern%20support%20in%20EF.#AsyncLazyLoading我搜索的内容称为"异步延迟加载"并且不是可用的功能然而(也许它永远不会).您似乎可以使用延迟加载或异步功能,也许我应该通过等待Task.Run(course.Students.First(p => p.ID = StudentID))将属性包装在Task中但是我是不确定这是个好主意.

Jea*_*nal 6

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

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

解决方案1:使用eager加载Include

var course = await db.Courses.Include(c => c.Students).FirstOrDefaultAsync(c => c.ID == CourseID);
var student = course.Students.First(p => p.ID == StudentID);
Run Code Online (Sandbox Code Playgroud)

好处:

  • 加载所有对象所需的一个数据库访问 - 如果您想一次检索多个对象,此解决方案可以很好地扩展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);
}
Run Code Online (Sandbox Code Playgroud)

然后你可以这样写:

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

优点:

  • 如果对象已经在上下文中加载,则可能根本没有数据库访问;
  • 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);
Run Code Online (Sandbox Code Playgroud)

优点:

  • 您只加载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;
Run Code Online (Sandbox Code Playgroud)

好处:

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

缺点:

  • Students导航属性没有被加载,这意味着它不能没有潜在触发数据库访问中使用;

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

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