I am having issue with caching data in memory with EF's 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();
Here every User
will have 0 to n Feedback
and every feedback has 1-1 user
associated with it, using FK AddedBy
.
When I iterate over data using foreach
the navigational properties get filled up and I can successfully return the result.
But when I use Parallel.ForEach
or .AsParallel().ForAll
, the Include()
fails for every feedback, and the navigational property User
for Feedback
Db set are null
.
Surprisingly, the above parallel operation doesn't fail every time, sometimes the requests are processed with Feedback
having User
, but sometimes it returns null
.
if (data.Any(r => r.f != null && r.f.Any(t => t.User == null)))
{
LogUtility.ErrorRoutine(new Exception("lastWeeklyFeedback is null:"));
}
But when I added the above if
block to debug, to my surprise my parallel operation works like a charm. And if I comment the above if
condition, then the parallel operation acts weirdly (ie. sometimes it works sometimes it fails).
I believe it is something related to EF's Include
.
Why is my code block behaving like this? What EF concept am I missing?.
Note: The above operation is used for read-only operations, it isn't used to add/update the DB, just to fetch data.
EDIT
Include the entire method. The conditions and business logic is irrelevant to context, But I added the entire snippet for just in case if you need it.
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()
};
}