C# 我应该将实体框架视为非托管资源吗?
我正在使用一个类,该类在其构造函数中使用对EF的引用 我已经实现了C# 我应该将实体框架视为非托管资源吗?,c#,entity-framework,destructor,idisposable,C#,Entity Framework,Destructor,Idisposable,我正在使用一个类,该类在其构造函数中使用对EF的引用 我已经实现了IDisposable,但我不确定是否需要析构函数,因为我不确定是否可以将EF分类为非托管资源 如果EF是一个托管资源,那么我不需要析构函数,因此我认为这是一个合适的示例: public ExampleClass : IDisposable { public ExampleClass(string connectionStringName, ILogger log) { //...
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()
方法中抛出该异常
正如上面所说:
X不访问终结器代码路径中的任何可终结对象,因为它们可能已经被终结
例如,引用了另一个可终结对象B的可终结对象a不能在a的终结器中可靠地使用B,反之亦然。终结器以随机顺序调用(缺少对关键终结的弱顺序保证)
另外,请注意Eric Lippert的以下内容:
神话:终结器以可预测的顺序运行
假设我们有一个对象树,所有对象都是可终结的,并且都在终结器队列中。没有要求从根到叶、从叶到根或任何其他顺序最终确定树
神话:正在定稿的对象可以安全地访问另一个对象。
这个神话直接源于上一个神话。如果您有一个对象树,并且正在终结根,那么子对象仍然是活动的-因为根是活动的,因为它在终结队列上,所以子对象有一个活动引用-但是子对象可能已经被终结,并且没有特别好的状态来访问它们的方法或数据
还有一点需要考虑:你想处理什么?您是否关心确保及时关闭数据库连接?如果是这样的话,那么您将对以下内容感兴趣: 默认情况下,上下文管理到数据库的连接。上下文根据需要打开和关闭连接。例如,上下文打开连接以执行查询,然后在处理完所有结果集后关闭连接 这意味着,默认情况下,连接不需要调用
DbContext.Dispose()
,就可以及时关闭。它们在执行查询时(从连接池)打开和关闭。因此,尽管确保始终显式调用DbContext.Dispose()
,这仍然是一个非常好的主意,但如果您不这样做或出于某种原因忘记,那么在默认情况下,这不会导致某种连接泄漏,这一点很有用
最后,您可能需要记住的最后一件事是,您发布的代码没有终结器,因为您在另一个类的构造函数中实例化了
DbContext
,所以实际上可能不会总是调用DbContext.Dispose()
方法。知道这个特殊情况很好,这样你就不会被抓住
例如,假设我稍微调整您的代码,允许在构造函数中实例化DbContext
的行之后抛出异常:
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))
{
// ...
}
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
实例之后,ExampleClass.Dispose()
在ExampleClass.Dispose()的构造函数内部抛出了异常,因此不会调用DbContext.Dispose()
也不会在新创建的实例上调用
你可以阅读更多关于这种不幸情况的信息
为了确保始终调用DbContext
的Dispose()
方法,无论ExampleClass
构造函数中发生了什么,您都必须将ExampleClass
类修改为以下内容:
using (var example = new ExampleClass("connString", log))
{
// ...
}
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
C#的实例,而是提供垃圾收集,因此不需要显式析构函数,那么上述问题实际上只是一个问题。但是,如果您确实控制非托管资源,则需要在使用完该资源后显式释放该资源。Finalize()方法(称为finalizer)提供了对该资源的隐式控制,当对象被销毁时,垃圾收集器将调用该方法
IDisposable不只是处理非托管资源。我将始终控制DbContext
的创建和销毁。它需要为一个小的工作单元创建。它的内部构件遵循工作模式的单位,认为它是被管理的,并期望它为它的非托管P实现它自己的终结器。