【发布时间】:2017-03-16 08:28:35
【问题描述】:
我在使用 EF 的 Include() 在内存中缓存数据时遇到问题。
var data = _context.Users
.Where(x => x.IsTrainee)
.GroupJoin(_context.Feedbacks.Include(x => x.User), u => u.UserId, f => f.AddedFor, (u, f) => new {u, f})
.ToList();
这里每个User 将有0 到n 个Feedback,每个反馈都有1-1 个user 与之关联,使用FK AddedBy。
当我使用 foreach 遍历数据时,导航属性会被填满,我可以成功返回结果。
但是当我使用Parallel.ForEach 或.AsParallel().ForAll 时,Include() 对每个反馈都失败,User 的导航属性Feedback Db 集是null。
令人惊讶的是,上述并行操作并非每次都失败,有时处理请求时使用Feedback 具有User,但有时它返回null。
if (data.Any(r => r.f != null && r.f.Any(t => t.User == null)))
{
LogUtility.ErrorRoutine(new Exception("lastWeeklyFeedback is null:"));
}
但是当我添加上面的if 块进行调试时,令我惊讶的是,我的并行操作就像一个魅力。如果我评论上面的if 条件,那么并行操作的行为就会很奇怪(即有时它会工作,有时它会失败)。
我认为这与 EF 的 Include 有关。
为什么我的代码块会这样?我缺少什么 EF 概念?
注意:以上操作为只读操作,不用于添加/更新数据库,仅用于获取数据。
编辑
包括整个方法。条件和业务逻辑与上下文无关,但我添加了整个 sn-p 以防万一。
public DashboardVm GetAllTraineeDataForDashbaord(int teamId)
{
var data = _context.Users.Where(x => x.IsTrainee == true && (teamId == 0 || x.TeamId == teamId) && x.IsActive == true)
.GroupJoin(_context.Feedbacks.Include(x => x.User), u => u.UserId, f => f.AddedFor, (u, f) => new {u, f})
.OrderBy(x=>x.u.UserName).AsNoTracking()
.ToList();
//if (data.Any(r => r.f != null && r.f.Any(t => t.User == null)))
//{
// LogUtility.ErrorRoutine(new Exception("lastWeeklyFeedback is null:"));
//}
UserConverter userConverter = new UserConverter();
FeedbackConverter feedbackConverter = new FeedbackConverter();
DateTime mondayTwoWeeksAgo = Common.Utility.UtilityFunctions.GetLastDateByDay(DayOfWeek.Monday, DateTime.Now.AddDays(-14));
DateTime lastFriday = Common.Utility.UtilityFunctions.GetLastDateByDay(DayOfWeek.Friday, DateTime.Now);
DateTime lastMonday = lastFriday.AddDays(-5);
var concurrentTrainee = new ConcurrentQueue<UserData>();
// data.AsParallel().ForAll(traineeLocal=>
Parallel.ForEach(data, traineeLocal =>
// foreach(var trainee in data)
{
var trainee = traineeLocal;
bool lastWeekFeedbackAdded = trainee.u.DateAddedToSystem >= lastFriday
|| (trainee.f.Any(x => x.FeedbackType == (int)Common.Enumeration.FeedbackType.Weekly
&& ( x.StartDate >= lastMonday || (x.EndDate <= lastFriday && x.EndDate >= lastMonday))));
Feedback lastWeeklyFeedback = lastWeekFeedbackAdded ? (trainee.f.OrderByDescending(feedback => feedback.FeedbackId)
.FirstOrDefault(x => x.FeedbackType == (int)Common.Enumeration.FeedbackType.Weekly))
: null;
concurrentTrainee.Enqueue(new UserData
{
User = userConverter.ConvertFromCore(trainee.u),
IsCodeReviewAdded = trainee.u.DateAddedToSystem >= lastFriday
|| trainee.f.Any(x => x.FeedbackType == (int)Common.Enumeration.FeedbackType.CodeReview && x.AddedOn >= mondayTwoWeeksAgo),
LastWeekFeedbackAdded = lastWeekFeedbackAdded,
WeeklyFeedback = lastWeeklyFeedback == null ? new List<Common.Entity.Feedback>() :
new List<Common.Entity.Feedback> { feedbackConverter.ConvertFromCore(lastWeeklyFeedback)},
RemainingFeedbacks = feedbackConverter.ConvertListFromCore(trainee.f.Where(x => x.FeedbackType == (int)Common.Enumeration.FeedbackType.CodeReview
|| x.FeedbackType == (int)Common.Enumeration.FeedbackType.Weekly
|| x.FeedbackType == (int)Common.Enumeration.FeedbackType.Assignment
|| x.FeedbackType == (int)Common.Enumeration.FeedbackType.Skill
|| x.FeedbackType == (int)Common.Enumeration.FeedbackType.RandomReview)
.OrderByDescending(x=>x.FeedbackId)
.Take(5)
.ToList()),
WeekForFeedbackNotPresent = new List<string>(),
AllAssignedCourses = new List<CourseTrackerDetails>()
});
}
);
return new DashboardVm
{
Trainees = concurrentTrainee.ToList()
};
}
【问题讨论】:
-
我没有对实体进行任何操作,我正在使用该实体来构建新的 DTO。在循环@CodeCaster 中没有任何属性的状态被改变。我也用过 .
.AsNoTracking但没有帮助 -
我想知道你为什么首先使用
.Include。因为在.Where调用之后,您仍在针对IQueryable<T>进行编码,EF 能够解析对 SQL 的关系引用,而无需手动要求它包含。我认为这本身并不能解决您的问题,我只想知道原因;也许他们之间有某种联系。 -
还请澄清您是在调用并行扩展 before 还是 after
ToList()-- 这很重要,因为如果你之前这样做,您正在并行化(简单地说)在幕后使用的DataReader,这可能会导致问题,但如果您在之后ToList()调用它,则您正在并行化一个已经完全加载的-内存收集。 -
@Balázs,我在做
hit n trail,所以我添加了Include以确保相关实体正在加载。除此之外,Include在这里没有什么重要的事情要做。其次,我在.ToList()之后做并行操作 -
@CodeCaster 结果对象是一个投影 (
new {u, f}),因此 EF6 忽略了Include。这在 ef-core 中是不同的。
标签: c# entity-framework parallel-processing entity-framework-6 asp.net-4.5