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

jac*_*ert 7 c# destructor entity-framework idisposable

我正在使用一个在其构造函数中使用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;
    }
}
Run Code Online (Sandbox Code Playgroud)

如果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);

    }
}
Run Code Online (Sandbox Code Playgroud)

哪一个?

sst*_*tan 8

在这种情况下,您永远不会想要使用终结器(析构函数).

是否DbContext包含非托管资源,甚至是否负责任地释放这些非托管资源,与您是否可以尝试DbContext.Dispose()从终结器调用无关.

事实是,你有一个管理对象(其中的一个实例任何时候DbContext是),这是从来没有安全试图调用该实例上的任何方法.原因是,在调用终结器时,该DbContext对象可能已经被GC收集并且不再存在.如果发生这种情况,你会NullReferenceException在试图打电话时得到一个Db.Dispose().或者,如果你很幸运,并且Db仍然"活着",DbContext.Dispose()如果它依赖于已经完成和收集的其他对象,也可以从方法中抛出异常.

正如这篇"Dispose Pattern"MSDN文章所说:

X不要在终结器代码路径中访问任何可终结的对象,因为它们已经完成的风险很大.

例如,具有对另一个可终结对象B的引用的可终结对象A不能在A的终结器中可靠地使用B,反之亦然.终结器以随机顺序调用(缺少关键终结的弱排序保证).

另外,请注意Eric Lippert的以下内容当您知道一切都是错误的时候,第二部分:

神话:终结者以可预测的顺序运行

假设我们有一个对象树,所有对象都可以终结,并且都在终结器队列中.从树根到树叶,从树叶到树根,或任何其他顺序,都没有要求树最终确定.

神话:正在最终确定的对象可以安全地访问另一个对象.

这个神话直接来自前一个.如果你有一个对象树并且你正在最终确定根,那么这些孩子仍然活着 - 因为它是活着的,因为它在终结队列中,所以孩子们有生活参考 - 但孩子们可能已经已经完成,并且没有特别好的状态来访问他们的方法或数据.


还有什么要考虑的:你想要处理什么?您是否关注确保数据库连接及时关闭?如果是这样,那么你会对EF文档对此有何看法感兴趣:

默认情况下,上下文管理与数据库的连接.上下文根据需要打开和关闭连接.例如,上下文打开一个连接以执行查询,然后在处理完所有结果集时关闭连接.

这意味着,默认情况下,不需要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;
    }
}
Run Code Online (Sandbox Code Playgroud)

让我们说你的类是这样使用的:

using (var example = new ExampleClass("connString", log))
{
    // ...
}
Run Code Online (Sandbox Code Playgroud)

即使这看起来是一个非常安全和干净的设计,因为在已经创建了一个新实例ExampleClass 之后,构造函数内部抛出异常DbContext,ExampleClass.Dispose()从不调用,并且通过扩展,DbContext.Dispose()永远不会在新创建的实例上调用.

你可以在这里阅读更多有关这种不幸情况的信息.

为了确保始终调用DbContextDispose()方法,无论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;
    }
}
Run Code Online (Sandbox Code Playgroud)

但是,如果构造函数不仅仅是创建一个实例,那么上面只是一个问题DbContext.