8

I have async issue with my below query. I have singleton context and i am trying to execute below query:

var query = await (from parent in Context.ParentTable
                   join child in Context.ChildTable
                   on parent.ID equals child.ID
                   into allResult
                   from ResultValue in allResult.DefaultIfEmpty()
                   where ResultValue.TenantId == tenantId
                   select new Result
                   {
                      Code = parent.Code,
                      Type = parent.Type,
                      ID = ResultValue == null ? 0 : ResultValue.Id
                   }).ToListAsync();

My singleton context looks like this:

public class BaseRepository
{
    private readonly IConfigurationContextFactory configurationContextFactory;

    private IConfigurationContext context;

    protected IConfigurationContext Context
    {
        get
        {
            return context ?? (context = configurationContextFactory.Context);
        }
    }

    public BaseRepository(IConfigurationContextFactory configurationContextFactory)
    {
        this.configurationContextFactory = configurationContextFactory;
    }
}

The configuration context factory returns Context like this:

private ConfigurationContext Get()
{
    logger.WriteEntrySync(LogLevel.Information,
                          null != context ? "Config Context: Using existing context." : "Config Context: Wiil create new context.");
    return context ?? (context = new ConfigurationContext(connectionString));
}

In this i get intermittent issue with following error:

A second operation started on this context before a previous asynchronous operation completed. Use 'await' to ensure that any asynchronous operations have completed before calling another method on this context. Any instance members are not guaranteed to be thread safe.

Rusty
  • 1,303
  • 2
  • 14
  • 28
  • 4
    EF context as a singleton is a big no-no. – haim770 Apr 21 '15 at 06:15
  • Possible duplicate of [Multi-async in Entity Framework 6?](http://stackoverflow.com/questions/20628792/multi-async-in-entity-framework-6) – Mick Dec 05 '16 at 09:19

2 Answers2

22

I have singleton context

This is your problem. DbContext isn't thread-safe, and is designed to execute one query at a time. Since you're sharing your DbContext, you're probably attempting to invoke another query concurrently, which isn't "legal" in DbContext terms.

You can even see it in the remarks of ToListAsync:

Multiple active operations on the same context instance are not supported. Use 'await' to ensure that any asynchronous operations have completed before calling another method on this context.

What you should do is not re-use your context with a global singleton, by create a new context each time you want to query your database.

Edit:

Instead of getting a single Context via your factory method, simply allocate a new one for every query:

using (var context = new ConfigurationContext(connectionString))
{
    var query = await (from feature in context.Features
                join featureFlag in context.FeatureFlags
                on feature.FeatureId equals featureFlag.FeatureId
                into allFeatures
                from featureFlagValue in allFeatures.DefaultIfEmpty()
                where featureFlagValue.TenantId == tenantId
                select new BusinessEntities.FeatureFlag
                {
                   Code = feature.Code,
                   Type = feature.Type,
                   FeatureFlagId = featureFlagValue == null ? 0 : featureFlagValue.FeatureFlagId
                }).ToListAsync();
}
Yuval Itzchakov
  • 146,575
  • 32
  • 257
  • 321
  • Thanks @yuval. I have updated my question. i am not sure how should i create new instance of context in linq join query. Or whole linq join query will be on single context? Bit confused still – Rusty Apr 21 '15 at 06:11
  • @Rusty Edited my answer with an example. – Yuval Itzchakov Apr 21 '15 at 06:14
  • I am suppose to use context factory, i was using "using" statement before. So is there a way to use context factory but it should return a new context by a property? – Rusty Apr 21 '15 at 06:37
  • @Rusty You can simply return a fresh instance each time you invoke the factory method, but i don't really see the point in that. – Yuval Itzchakov Apr 21 '15 at 06:39
  • You need a new instance for each unit of work, not each time you want to query. @Rusty, you can change your factory property to a method nammed CreateContext – Guillaume Apr 21 '15 at 07:11
  • @Guillaume can you provide me some reference, how to do that – Rusty Apr 21 '15 at 07:25
10

A DbContext should live for one business transaction (unit of work), no more no less. A business transaction is usually a request, a page or a form. DbContext is not thread safe, it keeps an internal entity cache and tracks changes so you cannot share it accros multiple request.

You really need to read Entity Framework documentation : Working with DbContext.

I wouldn't create an instance per repository as a business transaction can manipulate several repositories.

You can inject the context in your repository :

public class BaseRepository
{
    private IConfigurationContext context;
    public BaseRepository(IConfigurationContext context)
    {
        this.context = context;
    }
    //...
}

Change your factory so it creates an instance each time :

public interface IConfigurationContextFactory
{
    IConfigurationContext CreateContext();
}

// ...
public ConfigurationContext CreateContext()
{
    return new ConfigurationContext(connectionString);
}

Then configure your dependency resolver to inject a IConfigurationContext per unit of work. Let's say you are working on an ASP.NET application using unity.

container.RegisterType<IConfigurationContext>(
    //Instance per http request (unit of work)
    new PerRequestLifetimeManager(),
    //Create using factory
    new InjectionFactory(c => c.Resolve<IConfigurationContextFactory>.CreateContext()));

Don't forget to call SaveChangesAsync at the end of your business transaction when needed : operation succeeded and modifications need to be persisted.

Guillaume
  • 12,824
  • 3
  • 40
  • 48