如何修复'在上一个操作完成之前,在此上下文中开始第二个操作...'在使用依赖注入时?

时间:2017-09-24 21:40:50

标签: c# entity-framework dbcontext

从数据库中读取数据时出现此错误:

  

在上一个操作之前,在此上下文中启动了第二个操作   完成。不保证任何实例成员都是线程安全的。

我有以下ApplicationContext.cs:

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

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

以下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);
    }
}   

以下ServiceLoader.cs(我声明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));
    }
}

最后是以下Repository,抛出异常:

public class Repository : IRepository
{
    private ApplicationContext _db;

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

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

我还尝试将Repository声明为Transient而不是Singleton,但抛出了类似的错误

  

&#39;尝试在配置上下文时使用上下文。 DbContext实例不能在OnConfiguring内使用,因为此时仍在配置它。如果在上一个操作完成之前在此上下文上启动第二个操作,则会发生这种情况。任何实例成员都不能保证是线程安全的。&#39;

有关如何解决此问题的任何想法?谢谢!

3 个答案:

答案 0 :(得分:1)

您可以在Get()函数周围包装一个异步任务,然后等待结果:

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

答案 1 :(得分:0)

就我而言,我发现以下信息很有帮助:

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

并在启动时使用重载的AddDbContext方法将我的Db上下文的生存期范围更改为瞬态:

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

答案 2 :(得分:0)

我写了一个使用队列的解决方案。它仍然是单线程的,但是您可以从不同的线程调用它。

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;
        }
    }

然后,您可以使用以下命令从不同的线程运行查询:

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