我应该将entity framework视为非托管资源吗?
我正在使用一个在其构造函数中使用EF引用的类。
我已经实现了IDisposable
,但我不确定我是否需要析构函数,因为我不确定我是否可以将EF归类为非托管资源。
如果EF是托管资源,那么我不需要析构函数,所以我认为这是一个恰当的例子:
public ExampleClass : IDisposable { public ExampleClass(string connectionStringName, ILogger log) { //... Db = new Entities(connectionStringName); } private bool _isDisposed; public void Dispose() { if (_isDisposed) return; Db.Dispose(); _isDisposed= true; } }
如果EF不受管理,那么我将继续这样做:
public ExampleClass : IDisposable { public ExampleClass(string connectionStringName, ILogger log) { //... Db = new Entities(connectionStringName); } public void Dispose() { Dispose(true); } ~ExampleClass() { Dispose(false); } private bool _isDisposed; protected virtual void Dispose(bool disposing) { if (_isDisposed) return; // Dispose of managed resources if (disposing) { // Dispose of managed resources; assumption here that EF is unmanaged. } // Dispose of unmanaged resources Db.Dispose(); _isDisposed = true; //freed, so no destructor necessary. GC.SuppressFinalize(this); } }
哪一个?
在这种情况下,您永远不会想要使用终结器(析构函数)。
DbContext
是否包含非托管资源,甚至是否负责任地释放这些非托管资源,与您是否可以尝试从终结器调用DbContext.Dispose()
。
事实是,每当你有一个托管对象( DbContext
一个实例)时,尝试调用该实例上的任何方法都是不安全的。 原因是,在调用终结器时, DbContext
对象可能已经被GC收集并且不再存在。 如果发生这种情况,则在尝试调用Db.Dispose()
时会出现NullReferenceException
。 或者,如果你很幸运,并且Db
仍处于“活着状态”,如果DbContext.Dispose()
方法依赖于已经完成和收集的其他对象,也可以从DbContext.Dispose()
方法中抛出exception。
正如这篇“Dispose Pattern”MSDN文章所说:
X不要访问终结器代码路径中的任何可终结对象,因为它们已经完成的风险很大。
例如,具有对另一个可终结对象B的引用的可终结对象A不能在A的终结器中可靠地使用B,反之亦然。 终结器以随机顺序调用(缺少关键终结的弱排序保证)。
另外,请注意Eric Lippert的以下内容当您知道的一切都是错误的时,第二部分 :
神话:终结者以可预测的顺序运行
假设我们有一个对象树,所有对象都可以最终化,并且都在终结器队列中。 从树根到叶子,从叶子到根,或任何其他顺序,都没有要求树最终确定。
神话:最终确定的对象可以安全地访问另一个对象。
这个神话直接来自前一个。 如果你有一个对象树并且你正在最终确定根,那么这些孩子仍然活着 – 因为它是活着的,因为它在终结队列中,所以孩子们有一个生动的参考 – 但孩子们可能已经已经完成,并且没有特别好的状态来访问他们的方法或数据。
还有其他需要考虑的事情:你想要处理什么? 您是否关注确保数据库连接及时关闭? 如果是这样,那么你会对EF文档对此有何看法感兴趣:
默认情况下,上下文管理与数据库的连接。 上下文根据需要打开和关闭连接。 例如,上下文打开一个连接以执行查询,然后在处理完所有结果集时关闭连接。
这意味着,默认情况下,连接不需要DbContext.Dispose()
以及时关闭。 在执行查询时,它们(从连接池)打开和关闭。 所以,尽管确保你总是明确地调用DbContext.Dispose()
仍然是一个非常好的主意,但是知道如果你不这样做或者由于某种原因忘记了它是有用的,默认情况下,这不会导致某种连接泄漏
最后,您可能想要记住的最后一件事是,您发布的代码没有终结器,因为您在另一个类的构造函数中实例化DbContext
,实际上可能是DbContext.Dispose()
方法不会总是被调用。 很高兴知道这个特殊情况,所以你不会被你的裤子抓住。
例如,假设我稍微调整代码以允许在实例化DbContext
的构造函数中的行之后抛出exception:
public ExampleClass : IDisposable { public ExampleClass(string connectionStringName, ILogger log) { //... Db = new Entities(connectionStringName); // let's pretend I have some code that can throw an exception here. throw new Exception("something went wrong AFTER constructing Db"); } private bool _isDisposed; public void Dispose() { if (_isDisposed) return; Db.Dispose(); _isDisposed= true; } }
让我们说你的类是这样使用的:
using (var example = new ExampleClass("connString", log)) { // ... }
尽管这似乎是一个非常安全和干净的设计,因为在创建了一个新的DbContext
实例之后 ,在ExampleClass
的构造函数中抛出exception,所以从不调用ExampleClass.Dispose()
,并且通过扩展, DbContext.Dispose()
永远不会在新创建的实例上调用DbContext.Dispose()
。
你可以在这里阅读更多有关这种不幸情况的信息 。
为了确保始终调用DbContext
的Dispose()
方法,无论在ExampleClass
构造函数中发生什么,您都必须将ExampleClass
类修改为如下所示:
public ExampleClass : IDisposable { public ExampleClass(string connectionStringName, ILogger log) { bool ok = false; try { //... Db = new Entities(connectionStringName); // let's pretend I have some code that can throw an exception here. throw new Exception("something went wrong AFTER constructing Db"); ok = true; } finally { if (!ok) { if (Db != null) { Db.Dispose(); } } } } private bool _isDisposed; public void Dispose() { if (_isDisposed) return; Db.Dispose(); _isDisposed= true; } }
但是,如果构造函数不仅仅是创建DbContext
的实例,那么上面只是一个问题。