如何异步使用 DbContext

huangapple go评论114阅读模式
英文:

How to use DbContext asynchronously

问题

我有一个非常耗时的操作,如果我能够异步调用DbContext,那么它可以分成许多较短的操作。

首先是一个数据服务:

public class MyDataService : IMyDataService
{
    private readonly IMyRepository _repository;
    private readonly ITransactionScope _txScope;

    public MyDataService(IMyRepository repository, ITransactionScope txScope)
    {
        _repository = repository;
        _txScope = txScope;
    }

    public async Task<Result> CreateOrAppendAsync(SomeObject[] someObjects)
    {
        return await _txScope.DoInTransactionAsync(
            _principal.GetName(),
            "create-or-append",
            null,
            async () => await _repository.CreateOrAppendAsync(someObjects));
    }
}

然后是存储库:

public class MyRepository : IRepository
{
    private readonly InternalDbContext _dbContext;

    public Repository(InternalDbContext dbContext)
    {
        _dbContext = dbContext;
    }

    public async Task<Result> CreateOrAppendAsync(SomeObject[] someObjects)
    {
        var tasks = new List<Task<PartOfResult>>();
        foreach (var batch in someObjects.Split(10))
        {

            tasks.Add(Task.Run(() =>
            {
                // 使用 _dbContext 执行操作以获取结果
            }));
        }
        var results = await Task.WhenAll(tasks.ToArray());
        return results.Combine();
    }
}

当然,这不起作用,因为出现了以下问题:

在上一个操作完成之前,在此上下文实例上启动了第二个操作。这通常是由不同线程同时使用DbContext的同一实例引起的。有关如何避免使用DbContext的线程问题的更多信息,请参见https://go.microsoft.com/fwlink/?linkid=2097913。

问题是:我应该如何正确执行这个操作?以前我只是复制了DbContext,但由于所有这些都是同一个事务的一部分,所以这是不可能的。Postgres数据库应该能够处理多次调用,Dotnet具有很好的关键字使并行处理变得容易,但EF Core如何与所有这些一起工作呢?

英文:

I have a very long running operation that could be split into many shorter ones - if I was able to call DbContextasynchronously.

First of all, a data service:

public class MyDataService : IMyDataService
{
    private readonly IMyRepository _arepository;
    private readonly ITransactionScope _txScope;

    public MyDataService(IMyRepository repository, ITransactionScope txScope)
    {
        _repository = repository;
        _txScope = txScope;
    }

    public async Task&lt;Result&gt; CreateOrAppendAsync(SomeObject[] someObjects)
    {
        return await _txScope.DoInTransactionAsync(
            _principal.GetName(),
            &quot;create-or-append&quot;,
            null,
            async () =&gt; await _repository.CreateOrAppendAsync(someObjects));
    }
}

And then the repository:

public class MyRepository : IRepository
{
    private readonly InternalDbContext _dbContext;

    public Repository(InternalDbContext dbContext)
    {
        _dbContext = dbContext;
    }

    public async Task&lt;Result&gt; CreateOrAppendAsync(SomeObject[] someObjects)
    {
        var tasks = new List&lt;Task&lt;PartOfResult&gt;&gt;();
        foreach (var batch in someObjects.Split(10))
        {

            tasks.Add(Task.Run(() =&gt;
            {
                // do something with _dbContext to get result
            }));
        }
        var results = await Task.WhenAll(tasks.ToArray());
        return results.Combine();
    }
}

This of course does not work because of:

> A second operation was started on this context instance before a previous operation completed. This is usually caused by different threads concurrently using the same instance of DbContext. For more information on how to avoid threading issues with DbContext, see https://go.microsoft.com/fwlink/?linkid=2097913.

The question is: How would I do this correctly? Previously I've just copied the DbContext, but since all of this is part of the same transaction, that is not possible. The Postgres database should be able to handle multiple calls, Dotnet has such nice keywords to make parallel processing easy, but how does EF Core work together with all of this.

答案1

得分: 1

你正在尝试以并发方式而不是异步方式运行查询。这两者完全不同。如果要异步运行它,只需移除Task.Run部分。

public async Task&lt;Result&gt; CreateOrAppendAsync(SomeObject[] someObjects)
{
    var tasks = new List&lt;Task&lt;PartOfResult&gt;&gt;();
    foreach (var batch in someObjects.Split(10))
    {
        await CreateOrAppendAsync(batch);
    }
}

这里的要点是在线程在执行数据库操作时不应该被阻塞。

如果要并发运行查询,您需要创建多个dbContext对象,这是不可避免的。运行并发查询可能会或可能不会改善性能,这取决于数据库和查询本身。

英文:

You are trying to run queries not asynchronously, but concurrently. These are completely different things. If you want to run it asynchronously you just remove the Task.Run-part

public async Task&lt;Result&gt; CreateOrAppendAsync(SomeObject[] someObjects)
{
      var tasks = new List&lt;Task&lt;PartOfResult&gt;&gt;();
      foreach (var batch in someObjects.Split(10))
      {
          await CreateOrAppendAsync(batch);
      }
}

The point here is that the thread should not block while you are doing database operations.

If you want to run queries concurrently you need to create multiple dbContext objects, there is just no getting around this. Running concurrent queries might or might not improve anything, depending on the database and the queries.

答案2

得分: -3

根据我对你问题的理解,似乎你需要按顺序执行大量操作,因为你不希望阻塞主线程并使其响应。为了确保数据一致性,建议使用分布式事务协调器。

英文:

Based on my understanding of your question, it seems that you need to perform a large number of operations in sequence, and because you don't want to block main thread and make it responsive.
To ensure data consistency, it is recommended that you use a distributed transaction coordinator.

huangapple
  • 本文由 发表于 2023年6月15日 16:25:37
  • 转载请务必保留本文链接:https://go.coder-hub.com/76480536.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定