一个命令正在进行中。

12
我正在尝试为我正在开发的Web应用程序运行后台工作程序。 我使用Npgsql作为我的EF Core提供程序。
为了澄清,我已经将我的DbContext注入为短暂生命周期,并在我的连接字符串中允许连接池,但是每当我尝试测试它时,我都会收到以下错误:

Npgsql.NpgsqlOperationInProgressException:命令已在执行中:[这里是我的查询]

我已经设置了我的Program类如下:
public class Program
{
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureServices((hostContext, services) =>
            {
                // Get the configuration
                IConfiguration config = hostContext.Configuration;

                // DbContext
                services.AddDbContext<MyDbContext>(options => options.UseNpgsql(config.GetConnectionString("PostgreSQLString")), ServiceLifetime.Transient);

                services.AddHostedService<Worker>();
                services.AddScoped<IDTOService, BackgroundDTOService>();
            });
}

这就引出了我的Worker

public class Worker : BackgroundService
{
    private Logger logger;

    public Worker(IServiceProvider services, IConfiguration configuration)
    {
        this.Services = services;

        var optionsBuilder = new DbContextOptionsBuilder<MyDbContext>();
        optionsBuilder.UseNpgsql(configuration.GetConnectionString("PostgreSQLString"));
        var context = new StatPeekContext(optionsBuilder.Options);

        this.logger = new Logger(new LogWriter(context));
    }

    public IServiceProvider Services { get; }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        this.logger.LogInformation("ExecuteAsync in Worker Service is running.");
        await this.DoWork(stoppingToken);
    }

    private async Task DoWork(CancellationToken stoppingToken)
    {
        using (var scope = Services.CreateScope())
        {
            var context = scope.ServiceProvider.GetRequiredService<MyDbContext>();
            var dtoService = scope.ServiceProvider.GetRequiredService<IDTOService>();
            await dtoService.ProcessJSON(stoppingToken);
        }
    }

    public override async Task StopAsync(CancellationToken cancellationToken)
    {
        this.logger.LogInformation("Worker service is stopping.");
        await Task.CompletedTask;
    }
}

这导致我的BackGroundDTOService服务。
public class BackgroundDTOService : IDTOService
{
    private int executionCount = 0;
    private Logger logger;
    private MyDbContext context;
    private DbContextOptionsBuilder<MyDbContext> optionsBuilder;

    public BackgroundDTOService(IConfiguration configuration, MyDbContext context)
    {
        this.optionsBuilder = new DbContextOptionsBuilder<MyDbContext>();
        this.optionsBuilder.UseNpgsql(configuration.GetConnectionString("PostgreSQLString"));

        this.logger = new Logger(new LogWriter(new MyDbContext(this.optionsBuilder.Options)));

        this.context = context;
    }

    public async Task ProcessJSON(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            this.executionCount++;

            this.logger.LogInformation($"DTO Service is working. Count: {this.executionCount}");

            this.ProcessTeams();

            await Task.Delay(TimeSpan.FromSeconds(1), stoppingToken);
        }
    }

    public void ProcessTeams()
    {
        // Add Any Franchises that don't exist
        var franchiseDumps = this.context.RequestDumps.Where(rd => rd.Processed == false && rd.DumpType == "leagueteams");
        foreach (RequestDump teamDump in franchiseDumps)
        {
            var league = this.context.Leagues.Include(l => l.Franchises).FirstOrDefault(l => l.Id == teamDump.LeagueID);
            var teams = Jeeves.GetJSONFromKey<List<DTOTeam>>(teamDump.JsonDump, "leagueTeamInfoList");

            foreach (DTOTeam team in teams)
            {
                this.UpdateFranchise(team, league);
            }

            this.logger.LogInformation($"DTO Service Processed League Teams on count {this.executionCount}");
        }

        this.context.SaveChanges();
    }

错误似乎发生在获取 franchiseDumps 后,尝试获取 league 时立即发生。
2个回答

38

你能尝试将查询物化吗:

 var franchiseDumps = this.context.RequestDumps.Where(rd => rd.Processed == false && rd.DumpType == "leagueteams").ToList();

25
如果你在 EF Core LINQ 查询上使用 foreach,那么你将保持连接打开并流式传输结果。这意味着如果在 foreach 中对相同上下文执行另一个查询,则实际上是在同一连接上执行多个命令,这是不支持的。 - Shay Rojansky
4
我猜测这里所说的“其他提供者”是使用了SqlClient和MARS(Multiple Active Result Sets)功能,正是这个功能导致它可以工作。Npgsql不支持MARS(而且这是有充分理由的),正确的解决方案是按照建议将列表具体化,如果太大无法实现,则使用两个上下文(尽管这会带来其它问题)。 - Shay Rojansky
正如@ShayRojansky所提到的,您不能同时在同一连接上运行多个命令。 - RubbleFord
这非常有帮助!谢谢。我会研究为每个类/方法实现单独的上下文。 - jDave1984
@ShayRojansky 你好,我刚刚偶然看到了你的解释,请问你能否再详细解释一下这个答案的原因?从你的评论中可以看出,第一个评论的第二句话是关于使用LINQ和不使用LINQ的解释吗?我想我可能有些误解,谢谢。 - Ian004
显示剩余3条评论

6

我也遇到了同样的问题,后来发现相比于MSSQLPostgreSQL不支持多活动结果集(MARS)。

另外一种适用于我的方法是使用外键获取对象:

IEnumerable<Expense> objList = _db.Expenses;
foreach (var obj in objList.ToList())
{
    obj.ExpenseCategory = _db.ExpenseCategories.FirstOrDefault(u => u.Id == obj.ExpenseCategoryId);
}

它对我有效,当使用ASP.NET时实现PostGreSQL并不常见。 - Carlos Chaccon

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接