c# - 并行性和 Entity Framework

标签 c# entity-framework parallel-processing async-await

在我们的 Web 应用程序中,需要来自数据库中各种表的数据是很常见的。今天,您可能会发现针对单个请求串行执行 5 或 6 个数据库查询。这些查询都不依赖于其他查询的数据,因此它们非常适合并行执行。问题是众所周知的 DbConcurrencyException,当针对同一上下文执行多个查询时会抛出该异常。

我们通常为每个请求使用一个上下文,然后有一个存储库类,这样我们就可以在不同的项目中重复使用查询。然后,我们在处理 Controller 时在请求结束时处理上下文。

下面是一个使用并行的例子,但是还是有问题!

var fileTask = new Repository().GetFile(id);
var filesTask = new Repository().GetAllFiles();
var productsTask = AllProducts();
var versionsTask = new Repository().GetVersions();
var termsTask = new Repository().GetTerms();

await Task.WhenAll(fileTask, filesTask, productsTask, versionsTask, termsTask);

每个存储库都在内部创建自己的上下文,但就像现在一样,它们并没有被释放。那是个问题。我知道我可以在我创建的每个存储库上调用 Dispose,但这很快就会使代码变得困惑。我可以为每个使用自己的上下文的查询创建一个包装函数,但这感觉很困惑,而且不是解决问题的长期解决方案。

解决这个问题的最佳方法是什么?我希望客户端/消费者不必担心在并行执行多个查询的情况下处理每个存储库/上下文。

我现在唯一的想法是遵循类似于工厂模式的方法,除了我的工厂会跟踪它创建的所有对象。一旦我知道我的查询已完成并且工厂可以在内部处理每个存储库/上下文,我就可以处理工厂。

我很惊讶看到围绕并行性和 Entity Framework 的讨论如此之少,因此希望社区能提出更多想法。

编辑

这是我们的存储库的一个简单示例:

public class Repository : IDisposable {
    public Repository() {
        this.context = new Context();
        this.context.Configuration.LazyLoadingEnabled = false;
    }

    public async Task<File> GetFile(int id) {
        return await this.context.Files.FirstOrDefaultAsync(f => f.Id == id);
    }

    private bool disposed = false;

    protected virtual void Dispose(bool disposing) {
        if (!this.disposed) {
            if (disposing) {
                context.Dispose();
            }
        }
        this.disposed = true;
    }

    public void Dispose() {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
}

如您所见,每个存储库都有自己的上下文。这意味着需要处理每个存储库。在我上面给出的示例中,这意味着我需要调用 4 次 Dispose()

我对工厂方法解决问题的想法如下:

public class RepositoryFactory : IDisposable {
    private List<IRepository> repositories;

    public RepositoryFactory() {
        this.repositories = new List<IRepository>();
    }

    public IRepository CreateRepository() {
        var repo = new Repository();
        this.repositories.Add(repo);
        return repo;            
    }

    #region Dispose
    private bool disposed = false;

    protected virtual void Dispose(bool disposing) {
        if (!this.disposed) {
            if (disposing) {
                foreach (var repo in repositories) {
                    repo.Dispose();
                }
            }
        }
        this.disposed = true;
    }

    public void Dispose() {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
    #endregion
}

这个工厂将负责创建我的存储库的实例,但它也会跟踪它创建的所有实例。一旦处理完这个单一的工厂类,它将在内部负责处理它创建的每个存储库。

最佳答案

您可以允许客户端通过将某种可选(默认为 false)autodispose 位传递给构造函数来配置 Repository 的处置行为。一个实现看起来像这样:

public class Repository : IDisposable
{
    private readonly bool _autodispose = false;
    private readonly Lazy<Context> _context = new Lazy<Context>(CreateContext);

    public Repository(bool autodispose = false) {
        _autodispose = autodispose;
    }

    public Task<File> GetFile(int id) {
        // public query methods are still one-liners
        return WithContext(c => c.Files.FirstOrDefaultAsync(f => f.Id == id));
    }

    private async Task<T> WithContext<T>(Func<Context, Task<T>> func) {
        if (_autodispose) {
            using (var c = CreateContext()) {
                return await func(c);
            }
        }
        else {
            return await func(_context.Value);
        }
    }

    private static Context CreateContext() {
        var c = new Context();
        c.Configuration.LazyLoadingEnabled = false;
        return c;
    }

    public void Dispose() {
        if (_context.IsValueCreated)
            _context.Value.Dispose();
    }
}

注意:为了便于说明,我使处理逻辑保持简单;您可能需要重新处理您的disposed位。

您的查询方法仍然是简单的一行,客户端可以很容易地根据需要配置处置行为,甚至可以在自动处置情况下重新使用 Repository 实例:

var repo = new Repository(autodispose: true);
var fileTask = repo.GetFile(id);
var filesTask = repo.GetAllFiles();
var productsTask = AllProducts();
var versionsTask = repo.GetVersions();
var termsTask = repo.GetTerms();

await Task.WhenAll(fileTask, filesTask, productsTask, versionsTask, termsTask);

关于c# - 并行性和 Entity Framework ,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29654025/

相关文章:

c# - 如何反射(reflect) T 以构建查询的表达式树?

python - 如何在 Python 中并行化 I/O 绑定(bind)操作?

c# - 适用于 C# ASP.NET 开发人员的 SharePoint

c# - 计时器、UI 框架和不良耦合——有什么想法吗?

asp.net-mvc - 找不到类型或命名空间名称 'DbContext'

java - 如何实现用于流式传输斐波那契数的 Spliterator?

c - 发送数组以按顺序排名

c# - 以编程方式更改 Windows 服务用户

c# - 如何使用 ADO.NET 实现嵌套 SQL 事务?

c# - 通用存储库,CreateObjectSet<T>() 方法