并行性和 Entity Framework
Parallelism and the Entity Framework
在我们的 Web 应用程序中,需要来自数据库中各种表的数据是很常见的。今天,您可能会发现针对单个请求串行执行 5 或 6 个数据库查询。 None 这些查询依赖于来自其他查询的数据,因此它们非常适合并行执行。问题是众所周知的 DbConcurrencyException
,当针对同一上下文执行多个查询时会抛出该问题。
我们通常为每个请求使用一个上下文,然后有一个存储库 class 这样我们就可以在不同的项目中重复使用查询。然后我们在处理控制器时在请求结束时处理上下文。
下面是一个使用并行的例子,但是还是有问题!
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
,但这很快就会使代码变得混乱。我可以为每个使用自己的上下文的查询创建一个包装函数,但这感觉很乱,不是解决问题的长期解决方案。
解决此问题的最佳方法是什么?我希望 client/consumer 在并行执行多个查询的情况下不必担心处理每个 repository/context。
我现在唯一的想法是遵循类似于工厂模式的方法,除了我的工厂会跟踪它创建的所有对象。一旦我知道我的查询完成并且工厂可以在内部处理每个 repository/context.
,我就可以处理工厂
我很惊讶看到围绕并行性和 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
}
这个工厂将负责创建我的存储库的实例,但它也会跟踪它创建的所有实例。一旦这个工厂 class 被处置,它将在内部负责处置它创建的每个存储库。
您可以允许客户端通过将某种可选(默认为 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);
在我们的 Web 应用程序中,需要来自数据库中各种表的数据是很常见的。今天,您可能会发现针对单个请求串行执行 5 或 6 个数据库查询。 None 这些查询依赖于来自其他查询的数据,因此它们非常适合并行执行。问题是众所周知的 DbConcurrencyException
,当针对同一上下文执行多个查询时会抛出该问题。
我们通常为每个请求使用一个上下文,然后有一个存储库 class 这样我们就可以在不同的项目中重复使用查询。然后我们在处理控制器时在请求结束时处理上下文。
下面是一个使用并行的例子,但是还是有问题!
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
,但这很快就会使代码变得混乱。我可以为每个使用自己的上下文的查询创建一个包装函数,但这感觉很乱,不是解决问题的长期解决方案。
解决此问题的最佳方法是什么?我希望 client/consumer 在并行执行多个查询的情况下不必担心处理每个 repository/context。
我现在唯一的想法是遵循类似于工厂模式的方法,除了我的工厂会跟踪它创建的所有对象。一旦我知道我的查询完成并且工厂可以在内部处理每个 repository/context.
,我就可以处理工厂我很惊讶看到围绕并行性和 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
}
这个工厂将负责创建我的存储库的实例,但它也会跟踪它创建的所有实例。一旦这个工厂 class 被处置,它将在内部负责处置它创建的每个存储库。
您可以允许客户端通过将某种可选(默认为 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);