代码之家  ›  专栏  ›  技术社区  ›  Jonathan Wood

执行ADO。NET与IDisposable一起过火?

  •  1
  • Jonathan Wood  · 技术社区  · 7 年前

    什么时候NET第一次问世,我是众多抱怨者之一。NET缺乏确定性终结(在不可预测的时间表上调用类析构函数)。微软当时提出的妥协方案是 using 陈述

    虽然不完美,但我认为 使用 对于确保及时清理非托管资源非常重要。

    然而,我写了一些ADO。NET代码,并注意到几乎每个类都实现了 IDisposable . 这导致代码看起来像这样。

    using (SqlConnection connection = new SqlConnection(connectionString))
    using (SqlCommand command = new SqlCommand(query, connection))
    using (SqlDataAdapter adapter = new SqlDataAdapter(command))
    using (SqlCommandBuilder builder = new SqlCommandBuilder(adapter))
    using (DataSet dataset = new DataSet())
    {
        command.Parameters.AddWithValue("@FirstValue", 2);
        command.Parameters.AddWithValue("@SecondValue", 3);
    
        adapter.Fill(dataset);
    
        DataTable table = dataset.Tables[0];
        foreach (DataRow row in table.Rows) // search whole table
        {
            if ((int)row["Id"] == 4)
            {
                row["Value2"] = 12345;
            }
            else if ((int)row["Id"] == 5)
            {
                row.Delete();
            }
        }
        adapter.Update(table);
    }
    

    强烈地 怀疑我不需要所有这些 使用 声明。但是如果不了解每个类的详细代码,就很难确定哪些可以省略。结果有点难看,偏离了代码中的主要逻辑。

    有人知道为什么所有这些类都需要实现吗 IDisposable可识别 ?(微软有很多 code examples online 不必担心处理这些对象。)其他开发人员是否正在编写 使用 所有人的声明?如果没有,你如何决定哪些可以不用?

    3 回复  |  直到 7 年前
        1
  •  3
  •   Marc Gravell    7 年前

    这里的问题之一是ADO。NET是一个抽象的提供者模型。我们 我不知道 具体实现(特定ADO.NET提供程序)在处理方面需要什么。当然,我们可以合理地假设需要处理连接和事务,但是命令?大概读者可能,不仅仅是因为其中一个命令标志选项允许您将连接的生存期与读卡器相关联(因此当读卡器关闭时,连接会关闭,这在逻辑上应该扩展到disposal)。

    所以总的来说,我认为这可能是好的。

    大多数时候,人们并没有在胡闹。NET,任何ORM工具(或micro ORM工具,如“Dapper”)都能正确地实现这一点 为了你 你不用担心。


    我会公开承认,在我使用 DataTable (说真的,现在是2018年——这不应该是你表示数据的默认模型,除了一些特殊场景):我还没有处理它们。这一点毫无意义:)

        2
  •  2
  •   Mike Nakis    7 年前

    如果类实现 IDisposable ,那么您应该始终确保它得到处理,而不必对其实现进行任何假设。

    我认为这是您可以编写的最小代码量,可以确保您的对象被处置。我认为它绝对没有问题,而且它根本不会让我从代码的主要逻辑中分心。

    如果减少这段代码对您来说至关重要,那么您可以想出自己的替换(包装器) SqlConnection 其子类未实现 IDisposable可识别 一旦连接关闭,就会被连接自动销毁。然而,这将是一个巨大的工作量,并且您将失去一些关于何时处理某些内容的精确性。

        3
  •  1
  •   Jonathan Wood    7 年前

    对ADO中几乎所有内容。Net正在实施 IDisposable ,是否真的需要-例如, SqlConnection ( because of connection pooling )或者不是,比如说, DataTable ( Should I Dispose() DataSet and DataTable? )。

    如问题所述,问题是:

    如果不详细了解每个类的代码,就很难确定哪些可以省略。

    我认为这本身就是一个很好的理由,可以让一切都保持在 using 语句——一个词:封装。

    用很多话来说: 没有必要对您正在使用的每个类的实现都非常熟悉,甚至是非常熟悉。您只需要知道表面积,即公共方法、属性、事件、索引器(以及字段,如果该类具有公共字段)。从类的用户的角度来看,除了它的公共表面积之外的任何内容都是一个实现细节。

    关于所有 使用 代码中的语句—通过创建将接受SQL语句、 Action<DataSet> 和参数的params数组。类似这样:

    void DoStuffWithDataTable(string query, Action<DataTable> action, params SqlParameter[] parameters)
    {
        using (SqlConnection connection = new SqlConnection(connectionString))
        using (SqlCommand command = new SqlCommand(query, connection))
        using (SqlDataAdapter adapter = new SqlDataAdapter(command))
        using (SqlCommandBuilder builder = new SqlCommandBuilder(adapter))
        using (var table = new DataTable())
        {
            foreach(var param in parameters)
            {
                command.Parameters.Add(param);
            }
            // SqlDataAdapter has a fill overload that only needs a data table
            adapter.Fill(table);
            action();
            adapter.Update(table);
        }
    }
    

    对于需要对数据表执行的所有操作,您都可以这样使用它:

    DoStuffWithDataTable(
        "Select...",
        table => 
        { // of course, that doesn't have to be a lambda expression here...
            foreach (DataRow row in table.Rows) // search whole table
            {
                if ((int)row["Id"] == 4)
                {
                    row["Value2"] = 12345;
                }
                else if ((int)row["Id"] == 5)
                {
                    row.Delete();
                }
            }
        },
        new SqlParameter[]
        {
            new SqlParameter("@FirstValue", 2),
            new SqlParameter("@SecondValue", 3)
        }
        );
    

    这样,您的代码在处理任何 IDisposable可识别 ,而您只为此编写了一次管道代码。