【问题标题】:Ef's .Include with ParallelForEf 的 .Include 与 ParallelFor
【发布时间】: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&lt;T&gt; 进行编码,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


【解决方案1】:

这里发生了一些事情。首先,您在 GroupJoin 内部执行 Include。您必须在 ToList 之前包含正确的内容,否则它会丢失。

其次,源 DbContext 与 Parallel.Foreach 位于不同的线程上。我的直觉告诉我你打开了 LazyLoading。当您遍历每个项目时,它正在执行数据库命中以加载它。但是,原始上下文位于不同的线程上。我有点惊讶它甚至让你这样做。但最终,当它运行时,我想它不会等待 DbContext 回来。现在,一些用户可能是共享的,所以最终一些用户会在之后回来并且能够正确映射而无需在 DbContext 中进行数据库调用。当您调试时,您最终会导致它等待比正常情况更长的时间,然后它才能正确加载。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-02-12
    • 2017-10-23
    • 1970-01-01
    • 1970-01-01
    • 2021-02-15
    • 1970-01-01
    • 2021-07-04
    • 1970-01-01
    相关资源
    最近更新 更多