C# 我应该将实体框架视为非托管资源吗?

C# 我应该将实体框架视为非托管资源吗?,c#,entity-framework,destructor,idisposable,C#,Entity Framework,Destructor,Idisposable,我正在使用一个类,该类在其构造函数中使用对EF的引用 我已经实现了IDisposable,但我不确定是否需要析构函数,因为我不确定是否可以将EF分类为非托管资源 如果EF是一个托管资源,那么我不需要析构函数,因此我认为这是一个合适的示例: public ExampleClass : IDisposable { public ExampleClass(string connectionStringName, ILogger log) { //...

我正在使用一个类,该类在其构造函数中使用对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()
方法中抛出该异常

正如上面所说:

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实现它自己的终结器。