【发布时间】:2015-04-15 15:13:01
【问题描述】:
在我们的 Web 应用程序中,需要来自数据库中各种表的数据是很常见的。今天,您可能会发现单个请求会连续执行 5 或 6 个数据库查询。这些查询都不依赖于其他查询的数据,因此它们是并行执行的完美候选者。问题是众所周知的DbConcurrencyException,当针对同一上下文执行多个查询时会抛出该问题。
我们通常为每个请求使用一个上下文,然后有一个存储库类,以便我们可以在各种项目中重用查询。然后,当控制器被释放时,我们会在请求结束时释放上下文。
下面是一个使用并行的例子,但还是有问题!
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,但这开始很快使代码混乱。我可以为每个使用自己的上下文的查询创建一个包装函数,但这感觉很混乱,并且不是解决问题的一个很好的长期解决方案。
解决此问题的最佳方法是什么?我希望客户端/消费者不必担心在并行执行多个查询的情况下处理每个存储库/上下文。
我现在唯一的想法是遵循类似于工厂模式的方法,除了我的工厂会跟踪它创建的所有对象。一旦我知道我的查询已完成,我就可以处置工厂,并且工厂可以在内部处置每个存储库/上下文。
我很惊讶看到围绕并行性和实体框架的讨论如此之少,因此希望社区能提供更多想法。
编辑
以下是我们的存储库的简单示例:
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
}
这个工厂将负责创建我的存储库的实例,但它也会跟踪它创建的所有实例。一旦这个工厂类被处理掉,它将在内部负责处理它创建的每个存储库。
【问题讨论】:
-
我相信如果您不手动管理连接,则不需要释放 EF 上下文。它应该为每个请求打开和关闭。不过,不处理上下文让我觉得这是一种肮脏的方法。
-
@usr 我们有其他人在生产环境中编写的代码,但并未处理所有上下文。 :) 它有效,但我不确定后果是什么或将会是什么。由于上下文实现了
IDisposable,我想开发一种方法来消除可能发生的事情的奥秘。 -
“解决这个问题的最佳方法是什么?” -- 我认为您需要更具体地说明您对已经确定的可能方法的反对意见.比“凌乱”和“杂乱”更好的东西。事实上,封装是隐藏“混乱”和“混乱”的一种常见且有效的技术,某种包装器是一种封装形式。如果没有更多细节,您将得到的只是模糊的、固执己见的答案。
-
@PeterDuniho - 公平点。我反对为每个存储库方法编写包装器方法是因为它感觉非常重复。我很想知道人们对处理这个问题的工厂式方法有何看法。我认为这个问题清楚地表明了问题所在以及我要解决的问题。软件工程对如何解决问题有意见,所以如果有人对如何最好地解决问题有意见,我很乐意听到。
-
在您的示例中不清楚创建上下文的位置和时间。你说每个请求都有一个上下文,但每个存储库都会创建自己的上下文?
标签: c# entity-framework parallel-processing async-await