2

I have a following query:

public User GetByEmail(string email, bool includeRoles = true, bool enableLazyLoading = false, bool includeDeleted = true)
{
    IQueryable<User> dbQuery = null;
    Context.Configuration.LazyLoadingEnabled = enableLazyLoading;

    if (includeRoles)
    {
       dbQuery = Content.Include(x => x.Roles).Include(x => x.Regions).Where(x => x.Email == email);
    }
    else
    {
       dbQuery = Content.Where(x => x.Email == email);
    }

    if (!includeDeleted)
    {
       dbQuery = dbQuery.Where(x => !x.IsDeleted);
    }

    return dbQuery.FirstOrDefault();
}

It is called periodically from the Quartz job with default parameters like this:

var user = _userRepository.GetByEmail(email);

So including roles, excluding deleted users and without lazy loading. And most of the time it's working fine: both user roles and regions collections are loaded. But sometimes randomly(there should be some cause but I has not found it) user is returned with empty collections(not null). The same user, same query, same data in the database but the result is different.

I found couple articles about problems with includes(like this) but it seems not relevant to me because I don't use projections or groupings here.

Roles and Regions collections are marked virtual and has ICollection<T> type:

 public virtual ICollection<Role> Roles { get; set; }

Content is just a shorthand for a set:

 public DbSet<T> Content
 {
    get { return Context.Set<T>(); }
 }

DbContext is registered as instance per request or per thread. In case of Quartz job executions it should be per thread(Ninject):

kernel.Bind<DbContext>().To<PhotoContext>()
        .InScope(context => HttpContext.Current ?? StandardScopeCallbacks.Thread(context));

We have a lot of jobs which are registered like this:

private static void ScheduleJob<T>(int hour, int minute, ScheduleType type, string jobName = null, string groupName = REPORT_GROUP_DEFAULT, DayOfWeek? dayOfWeek = null, int dayOfMonth = 1, bool test = false)
{
    Type jobType = typeof(T);
    jobName = jobName ?? $"{type}{jobType.Name}";

    IJobDetail existingJob = _Scheduler.GetJobDetail(new JobKey(jobName, groupName));

    if (existingJob != null)
        _Scheduler.DeleteJob(existingJob.Key);

    JobDetailImpl jobDefinition = new JobDetailImpl(jobName, groupName, jobType);

    ITrigger trigger = CreateTrigger(jobName, jobDefinition, groupName, hour, minute, type, dayOfWeek, dayOfMonth, test);

    _Scheduler.ScheduleJob(jobDefinition, trigger);
}

public static void Init()
{
    _Scheduler = DependencyResolver.Current.GetService<IScheduler>();
    ScheduleJob<UserLastActivityUpdateJob>(1, 0, ScheduleType.Hourly);
    //schedule other jobs
    _Scheduler.Start();
}

Inside jobs dependencies are retrieved from the DependencyResolver:

_userRepository = DependencyResolver.Current.GetService<IUserRepository>();

What can cause this issue?

Roman Koliada
  • 4,286
  • 2
  • 30
  • 59
  • What is Content object (static class?) in your method? – Alexbogs Jan 31 '20 at 15:29
  • @Alexbogs I've updated my question. Content is just a `Context.Set()` – Roman Koliada Jan 31 '20 at 15:47
  • Look this post: https://stackoverflow.com/questions/28055327/entity-framework-include-is-not-working-within-complex-query Also attention to "(or by preventing lazy loading by disabling proxy creation or by not making Navprop1 virtual.)" line on Gert Arnold's answer – curiousBoy Jan 31 '20 at 20:04
  • 2
    "Random" failures usually point at context instances being used in multiple threads. What's the life cycle of `Context`? – Gert Arnold Feb 01 '20 at 14:10
  • @GertArnold I'm also concerned about threads but not sure how to locate the issue. I've updated my question with DbContext registration details. It should be registered as instance per thread. – Roman Koliada Feb 03 '20 at 08:38

0 Answers0