【问题标题】:Should I Treat Entity Framework as an Unmanaged Resource?我应该将实体框架视为非托管资源吗?
【发布时间】:2015-10-28 07:02:43
【问题描述】:

我正在使用一个在其构造函数中使用对 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);

    }
}

是哪一个?

【问题讨论】:

  • IDisposable 不仅仅处理非托管资源。
  • 我将始终控制DbContext 的创建和销毁。需要为一小部分工作创建它。它的内部遵循工作单元模式
  • 我认为它是托管的,并希望它为它的非托管部分实现自己的终结器。
  • 问题是对于“DbContext 是否不受管理?”没有明确的“是”或“否”答案。它是一个 CLR 类,所以它绝对是一个托管对象。然而在后台,它使用来自池的数据库连接,这些连接往往是非托管资源的(托管包装器)。但是,默认情况下,DbContext 和相关的内部类会自行释放这些非托管资源。当您不自己打开连接时,只需处理 DbContext 就足够了(通常甚至没有必要)。没有其他事情可做。
  • @CodeCaster - 谢谢。 However, the DbContext and related, internal classes by default release those unmanaged resources themselves. 这似乎有道理,让我想将 EF 视为托管资源。据我了解,我不需要托管对象的析构函数,因此我可以放心地使用第一个示例,除非另有说明。

标签: c# entity-framework destructor idisposable


【解决方案1】:

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

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

事实是,只要您有一个托管对象(DbContext 的实例就是),尝试调用任何方法都是永远安全的在那个实例上。原因是,在调用终结器时,DbContext 对象可能已经被 GC 收集并且不再存在。如果发生这种情况,您将在尝试调用Db.Dispose() 时收到NullReferenceException。或者,如果你很幸运,并且 Db 仍然“活着”,那么如果它依赖于其他已完成并收集的对象,也可以从 DbContext.Dispose() 方法中抛出异常。

正如"Dispose Pattern" MSDN article 所说:

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

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

另外,请注意 Eric Lippert 的 When everything you know is wrong, part two 中的以下内容:

误区:终结器以可预测的顺序运行

假设我们有一棵对象树,它们都是可终结的,并且都在终结器队列中。不要求从根到叶、从叶到根或任何其他顺序最终确定树。

误区:一个被终结的对象可以安全地访问另一个对象。

这个神话直接继承于前一个。如果你有一棵对象树并且你正在终结根,那么孩子们仍然活着——因为根是活着的,因为它在终结队列中,所以孩子们有一个活着的引用——但孩子们可能已经已经完成,并且没有特别好的状态可以访问他们的方法或数据。


需要考虑的其他事项:您要处理什么?您是否担心及时关闭数据库连接?如果是这样,那么您会对EF documentation 对此的看法感兴趣:

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

这意味着,默认情况下,连接不需要调用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))
{
    // ...
}

尽管这似乎是一个非常安全和干净的设计,因为在 ExampleClass 的构造函数中抛出了异常 DbContext 的新实例之后已经创建,ExampleClass.Dispose() 永远不会被调用,并且通过扩展,DbContext.Dispose() 也永远不会在新创建的实例上调用。

您可以阅读更多关于这种不幸情况的信息here

为确保始终调用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;
    }
}

但是,如果构造函数所做的不仅仅是创建DbContext 的实例,那么上述问题实际上只是一个问题。

【讨论】:

  • 反响很好;谢谢。但是,也许我们可以通过评论充实几点:首先,您能解释一下为什么 EF 是托管对象吗?天真地,我认为 EF 是一个 db 连接的包装器(当然更多,但在这种情况下就这么多了)。我假设它的管理方式与我假设包装文件流的类是托管对象的方式相同(它是用 C# 编写的,因此在 CLR 的控制下)。流本身是由 Win32 dll 打开的,我不知道,但它不是由 CLR 管理的。你回答了我的问题,但你没有告诉我为什么 EF 是一个非托管对象;我说的对吗?
  • 第二:你说They are opened and closed (from a connection pool) as queries are executed. So, though it's still a very good idea to make sure you always call DbContext.Dispose() explicitly, it's useful to know that, if you don't do it or forget for some reason, by default, this is not causing some kind of connection leak.,这减轻了我的担忧。我觉得我需要在我的包装类上实现 IDisposable,起初主要是因为我担心 EF 可能不受管理。但我知道 IDisposable 被恰当地用于释放这两种资源。所以这部分也很受欢迎
  • 最后,感谢您的奖励考虑,回复:构造函数中的异常。这是我没有考虑过的一点,也是我没有欣赏到的模式的微妙之处。
  • 关于您最初的评论,听起来您理解正确。所有 .NET 对象都受到管理并接受垃圾回收,这包括 DbContext。相反,非托管资源通常类似于文件句柄,您需要使用低级 winapi 函数关闭它。您应该只定义终结器来清除非托管资源,这几乎是永远不会发生的。
  • 太棒了。而且我理解不要对托管资源使用析构函数,这就是我在示例中没有这样做的原因。但我感谢其他所有人的提醒。
【解决方案2】:

C# 提供垃圾收集,因此不需要显式析构函数。但是,如果您确实控制了非托管资源,则需要在使用完该资源后显式释放该资源。对该资源的隐式控制由 Finalize() 方法(称为终结器)提供,当您的对象被销毁时,垃圾收集器将调用该方法。

https://www.oreilly.com/library/view/programming-c/0596001177/ch04s04.html

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-01-26
    • 2012-06-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多