在一个“using”块中,如果出现返回或异常,SqlConnection会被关闭吗?

150

第一个问题:
假设我有

using (SqlConnection connection = new SqlConnection(connectionString))
{
    connection.Open();

    string storedProc = "GetData";
    SqlCommand command = new SqlCommand(storedProc, connection);
    command.CommandType = CommandType.StoredProcedure;
    command.Parameters.Add(new SqlParameter("@EmployeeID", employeeID));

    return (byte[])command.ExecuteScalar();
}

连接会被关闭吗?因为从技术上讲,我们在最后一个}之前就已经使用return返回了。

第二个问题:
这一次我有:

try
{
    using (SqlConnection connection = new SqlConnection(connectionString))
    {
        int employeeID = findEmployeeID();

        connection.Open();
        SqlCommand command = new SqlCommand("UpdateEmployeeTable", connection);
        command.CommandType = CommandType.StoredProcedure;
        command.Parameters.Add(new SqlParameter("@EmployeeID", employeeID));
        command.CommandTimeout = 5;

        command.ExecuteNonQuery();
    }
}
catch (Exception) { /*Handle error*/ }

现在,假设我们在try块中遇到错误并被捕获。连接是否仍然会关闭?因为我们跳过了try中剩下的代码,直接执行catch语句。

我是否对using的工作方式有过于线性的思考?也就是说,当我们离开using作用域时,Dispose()是否仅仅被调用一次?

8个回答

190
  1. 是的
  2. 是的。

无论哪种情况,当使用块被退出时(无论是成功完成还是出错),它都会被关闭。

虽然我认为像这样组织代码会更好,因为即使对于后来支持它的新维护程序员来说,也更容易看出会发生什么:

using (SqlConnection connection = new SqlConnection(connectionString)) 
{    
    int employeeID = findEmployeeID();    
    try    
    {
        connection.Open();
        SqlCommand command = new SqlCommand("UpdateEmployeeTable", connection);
        command.CommandType = CommandType.StoredProcedure;
        command.Parameters.Add(new SqlParameter("@EmployeeID", employeeID));
        command.CommandTimeout = 5;

        command.ExecuteNonQuery();    
    } 
    catch (Exception) 
    { 
        /*Handle error*/ 
    }
}

12
当使用Using语句时,我是否需要打开连接? - Fandango68
3
如果您在使用事务,可以在 using 语句中使用 try catch,这样您就可以在 catch 中明确地进行 .Commit.Rollback 操作。 这种做法更易读和明确,且允许您根据异常类型来决定是否提交操作(如果有必要的话)。 (如果没有提交,事务会在 conn.Close 时自动回滚。) - Chris
10
@Fernando68 是的,你仍需要“打开”连接。使用using只保证调用对象的“Dispose”方法。 - juharr
我在using块内部使用了ExecuteScalar返回值。当我第二次运行该方法时,它非常快,就像连接已经打开一样。为什么第二次会这么快呢? - positive perspective
1
@positiveperspective - 请查阅连接池 - David
显示剩余2条评论

50

两个问题的答案都是肯定的。using语句会被编译成try/finally块。

using (SqlConnection connection = new SqlConnection(connectionString))
{
}

是相同的。
SqlConnection connection = null;
try
{
    connection = new SqlConnection(connectionString);
}
finally
{
   if(connection != null)
        ((IDisposable)connection).Dispose();
}

编辑:修复了对Disposable的强制转换

http://msdn.microsoft.com/en-us/library/yh598w02.aspx

它并不完全是那样,但足够接近了。确切的差异并不重要。 - Bryan
@Bryan没明白,你能具体说明一下区别吗?这样可以帮助我们更好地学习 :-) - mohits00691
哇,那是很久以前的评论啊 :) 看起来好像在我发表评论后的那一天进行了编辑。我想这就是我所考虑的区别。 - Bryan
@Bryan 是的,我在你的评论后进行了调整。 - Ryan Pedersen
我们每次进行其他操作,比如CRUD操作时,是否总是要使用SqlConnection connection = new SqlConnection(connectionString)?每次插入数据时,我们是否都需要使用SqlConnection connection = new SqlConnection(connectionString)? - paraJdox1

19

这是我的模板。你所需要的一切,以便从SQL服务器选择数据。连接已关闭和处理,并捕获了连接和执行中的错误。

string connString = System.Configuration.ConfigurationManager.ConnectionStrings["CompanyServer"].ConnectionString;
string selectStatement = @"
    SELECT TOP 1 Person
    FROM CorporateOffice
    WHERE HeadUpAss = 1 AND Title LIKE 'C-Level%'
    ORDER BY IntelligenceQuotient DESC
";
using (SqlConnection conn = new SqlConnection(connString))
{
    using (SqlCommand comm = new SqlCommand(selectStatement, conn))
    {
        try
        {
            conn.Open();
            using (SqlDataReader dr = comm.ExecuteReader())
            {
                if (dr.HasRows)
                {
                    while (dr.Read())
                    {
                        Console.WriteLine(dr["Person"].ToString());
                    }
                }
                else Console.WriteLine("No C-Level with Head Up Ass Found!? (Very Odd)");
            }
        }
        catch (Exception e) { Console.WriteLine("Error: " + e.Message); }
        if (conn.State == System.Data.ConnectionState.Open) conn.Close();
    }
}

* 修订日期:2015年11月09日 *
如NickG所建议的;如果太多大括号让你感到烦恼,可以采用以下格式...

using (SqlConnection conn = new SqlConnection(connString))
   using (SqlCommand comm = new SqlCommand(selectStatement, conn))
   {
      try
      {
         conn.Open();
         using (SqlDataReader dr = comm.ExecuteReader())
            if (dr.HasRows)
               while (dr.Read()) Console.WriteLine(dr["Person"].ToString());
            else Console.WriteLine("No C-Level with Head Up Ass Found!? (Very Odd)");
      }
      catch (Exception e) { Console.WriteLine("Error: " + e.Message); }
      if (conn.State == System.Data.ConnectionState.Open) conn.Close();
   }

再说了,如果你在EA或DayBreak Games工作,你甚至可以放弃任何换行,因为那只是为那些以后需要回来查看你的代码的人准备的,谁会真正关心呢?我是对的吧?我的意思是,一行而不是23行,这意味着我是一个更好的程序员,对吧?

using (SqlConnection conn = new SqlConnection(connString)) using (SqlCommand comm = new SqlCommand(selectStatement, conn)) { try { conn.Open(); using (SqlDataReader dr = comm.ExecuteReader()) if (dr.HasRows) while (dr.Read()) Console.WriteLine(dr["Person"].ToString()); else Console.WriteLine("No C-Level with Head Up Ass Found!? (Very Odd)"); } catch (Exception e) { Console.WriteLine("Error: " + e.Message); } if (conn.State == System.Data.ConnectionState.Open) conn.Close(); }

哎呀,好了。我把这个从我的系统里解决了,一时兴起就结束了。继续吧。


6
你知道吗,你可以在不需要额外添加大括号的情况下堆叠 using 语句。删除最后一个大括号,然后将 using 语句相邻地排列即可 :) - NickG
是的先生。谢谢您。我知道,但我希望我的代码能够准确地显示正在发生的事情,而不使用太多其他的捷径。这对于最终读者来说是一个很好的补充说明。 - ShaneLS
为什么在结尾使用 conn.Close();?难道不是通过 using 语句进行处理并自动释放吗? - Fredrick Gauss
我相信现在已经可以了(自从 .net 3.5)。在 .net 2.0 早期对我来说不太清楚,所以我养成了检查和关闭的习惯。 - ShaneLS
C#现在支持多个参数,因此您可以删除嵌套和双重using语句。 - Neil Walker

5

当您离开使用范围时,Dispose方法会被调用。 "Using"的目的是为开发人员提供一种确保资源得到释放的可靠方式。

来自MSDN

在达到using语句的结尾或在语句块之前抛出异常并且控制权离开语句块时,using语句可以被退出。


5

Using 会在分配对象的代码周围产生一个 try/finally 块,并为您调用 Dispose()

它可以避免手动创建 try/finally 块并调用 Dispose() 的麻烦。


3
在你的第一个例子中,C#编译器实际上会将using语句翻译成以下内容:
SqlConnection connection = new SqlConnection(connectionString));

try
{
    connection.Open();

    string storedProc = "GetData";
    SqlCommand command = new SqlCommand(storedProc, connection);
    command.CommandType = CommandType.StoredProcedure;
    command.Parameters.Add(new SqlParameter("@EmployeeID", employeeID));

    return (byte[])command.ExecuteScalar();
}
finally
{
    connection.Dispose();
}

最终语句总是在函数返回之前被调用,因此连接将始终关闭/处理。

因此,在您的第二个示例中,代码将编译为以下内容:

try
{
    try
    {
        connection.Open();

        string storedProc = "GetData";
        SqlCommand command = new SqlCommand(storedProc, connection);
        command.CommandType = CommandType.StoredProcedure;
        command.Parameters.Add(new SqlParameter("@EmployeeID", employeeID));

        return (byte[])command.ExecuteScalar();
    }
    finally
    {
        connection.Dispose();
    }
}
catch (Exception)
{
}

异常将在finally语句中被捕获并关闭连接。外部catch子句不会看到异常。

1
非常好的例子,但我不同意你最后的评论。如果在using块内发生异常,它将在任何外部catch中被捕获,事实上,我通过在try/catch块内编写2个using块进行了测试,并惊讶地发现,我得到了来自第二个内部using块的异常错误消息。 - WhySoSerious

1
我在一个try/catch块内写了两个using语句,如果它放在内部using语句中,我可以看到异常被捕获的方式是相同的,就像ShaneLS example所示。
     try
     {
       using (var con = new SqlConnection(@"Data Source=..."))
       {
         var cad = "INSERT INTO table VALUES (@r1,@r2,@r3)";

         using (var insertCommand = new SqlCommand(cad, con))
         {
           insertCommand.Parameters.AddWithValue("@r1", atxt);
           insertCommand.Parameters.AddWithValue("@r2", btxt);
           insertCommand.Parameters.AddWithValue("@r3", ctxt);
           con.Open();
           insertCommand.ExecuteNonQuery();
         }
       }
     }
     catch (Exception ex)
     {
       MessageBox.Show("Error: " + ex.Message, "UsingTest", MessageBoxButtons.OK, MessageBoxIcon.Error);
     }

无论try/catch放置在哪里,异常都将被捕获而不会出现问题。

-1

虽然这是一个旧帖子,但仍然相关。我来到这里是为了寻找一种方法,可以在一个using语句内部避免使用另一个using语句。尽管未来可能会有深入的评论改变我的想法,但我对此感到满意。;) 这里的讨论很有帮助。谢谢。为了易读性而简化 -

public DataTable GetExchangeRates()
    {
        DataTable dt = new DataTable();

        try
        {
            logger.LogInformation($"Log a message.");

            string conStr = _config.GetConnectionString("conStr");

            using (SqlCommand cmd = new SqlCommand("someProc", new SqlConnection(conStr)))
            {
                cmd.CommandType = CommandType.StoredProcedure;
                cmd.Connection.Open();
                dt.Load(cmd.ExecuteReader());
            }

            return dt;

        }
        catch (Exception ex)
        {
            logger.LogError(ex, ex.Message);
        }
    }

目前你的回答不够清晰,请编辑并添加更多细节,以帮助其他人理解它如何回答问题。你可以在帮助中心找到有关如何编写好答案的更多信息。 - Community

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接