任务模式说为了保持一致,一切都必须完全异步或完全不同步。
首先使用实体框架设计器,我可以很容易地实现这一目标
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中但是我'我不确定这是个好主意。
答案 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
; 缺点:
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
导航属性,这意味着在未触发数据库访问的情况下无法使用它; 解决方案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
导航属性,这意味着在未触发数据库访问的情况下无法使用它; **何时使用哪种解决方案? **
Course
对象,则仅使用解决方案3;