How to fix 'A second operation started on this context before a previous operation completed...' when working with dependency injection?

Viewed 20878

when reading data from the database I get this error:

A second operation started on this context before a previous operation completed. Any instance members are not guaranteed to be thread safe.

I have the following ApplicationContext.cs:

public class ApplicationContext : Microsoft.EntityFrameworkCore.DbContext
{
    public ApplicationContext(DbContextOptions<ApplicationContext> options)
        : base(options)
    { }

    public DbSet<MyClass> MyClasses{ get; set; }
}   

The following ApplicationContextFactory.cs

public class ApplicationContextFactory : IDesignTimeDbContextFactory<ApplicationContext>
{
    public ApplicationContext CreateDbContext(string[] args)
    {
        var builder = new DbContextOptionsBuilder<ApplicationContext>();
        var connection = "myConnectionString";

        builder.UseSqlServer(connection);

        return new ApplicationContext(builder.Options);
    }
}   

The following ServiceLoader.cs (where I declare the DI):

public static class ServiceLoader
{
    public static void ConfigureServices(IServiceCollection services)
    {
        services.AddSingleton<IRepository, Repository>();

        var connection = "myConnectionString";
        services.AddDbContext<ApplicationContext>(options => options.UseSqlServer(connection));
    }
}

and finally, the following Repository, where the exception is thrown:

public class Repository : IRepository
{
    private ApplicationContext _db;

    public Repository (ApplicationContext db)
    {
        _db = db;
    }

    public List<MyClass> Get()
    {
        _db.MyClasses.ToList();
    }
}

I have also tried to declare the Repository as Transient instead of Singleton, but a similar error is thrown

'An attempt was made to use the context while it is being configured. A DbContext instance cannot be used inside OnConfiguring since it is still being configured at this point. This can happen if a second operation is started on this context before a previous operation completed. Any instance members are not guaranteed to be thread safe.'

Any idea on how to fix this? Thanks!

3 Answers

In my case I found the following information helpful:

https://docs.microsoft.com/en-us/ef/core/miscellaneous/configuring-dbcontext

And changed the lifetime scope of my Db Context to transient using the overloaded AddDbContext method in startup:

services.AddDbContext<MyAppDbContext>(options => {
            options.UseSqlServer(
                Configuration.GetConnectionString("DefaultConnection"));
        }, ServiceLifetime.Transient);

You can wrap an async Task around your Get() function and await your results:

public async Task<List<MyClass>> Get()
{
   return await _db.MyClasses.ToListAsync();
}

I wrote a solution, which uses a queue. It is still single threaded, but you can call it from different threads.

public class ThreadSafeDataContext
    {
        private Thread databaseThread;
        private Queue<PendingQuery> pendingQueries = new Queue<PendingQuery>();
        private DatabaseContext db = new DatabaseContext();
        private bool running = true;
        public ThreadSafeDataContext()
        {
            databaseThread = new Thread(new ThreadStart(DoWork));
            databaseThread.Start();
        }
        public void StopService()
        {
            running = false;
        }
        private void DoWork()
        {
            while(running)
            {
                if (pendingQueries.Count > 0)
                {
                    // Get and run query
                    PendingQuery query = pendingQueries.Dequeue();
                    query.result = query.action(db);
                    query.isFinished = true;
                }
                else
                {
                    Thread.Sleep(1); // Waiting for queries
                }
            }
        }

        public T1 Query<T1>(Func<DatabaseContext, T1> action)
        {
            Func<DatabaseContext, object> a = (DatabaseContext db) => action(db);
            PendingQuery query = new PendingQuery(a);
            pendingQueries.Enqueue(query);

            while (!query.isFinished) {
                Thread.Sleep(1); // Wait until query is finished
            }

            return (T1)query.result;
        }
    }
    class PendingQuery
    {
        public Func<DatabaseContext, object> action;
        public bool isFinished;
        public object result;

        public PendingQuery(Func<DatabaseContext, object> action)
        {
            this.action = action;
        }
    }

Then you can just run a query from different threads by using:

TeamMembers teamMembers = threadSafeDb.Query((DatabaseContext c) => c.team.ToArray())
Related