在c#中处理相互引用的多个对象

时间:2014-05-04 16:03:48

标签: c# sql .net

好的,我搜索过“类似”的主题,但仍然没有找到我在这里要问的任何答案。

我有一个在 System.Data.SqlClient 命名空间下创建多个sql对象的函数。我已经读过 使用 语句在 使用 块之后处理了一个对象,但声明的变量是readonly 。我的函数重用了其中一些变量,因此我无法在 中使用 语句声明它们。

为清晰起见,这是我的功能正文。我应该在其他对象(命令,事务,阅读器等)上调用Dispose,还是 使用 通过连接对象递归处理它们?我该如何处置这些物品?

我还是C#的新手(我来自C / C ++背景)所以如果这个问题听起来很无知,请原谅我。

public string SignIn(string userId, string password)
{
     SqlCommand sqlCommand = null;
     SqlTransaction sqlTransaction = null;
     string sessionId = "";

     using(SqlConnection sqlConnection = new SqlConnection Properties.Settings.Default.SessionManagerDBConnectionString))
     {
          try
          {
               sqlConnection.Open();

               sqlCommand = sqlConnection.CreateCommand();
               sqlCommand.CommandText = "GetUserByUserIdPassword";
               sqlCommand.CommandTimeout = 30;
               sqlCommand.CommandType = CommandType.StoredProcedure;
               SqlParameter parameterUserId = sqlCommand.Parameters.Add("@UserId", SqlDbType.NVarChar, 32);
               parameterUserId.Value = userId;
               SqlParameter parameterPassword = sqlCommand.Parameters.Add("@Password", SqlDbType.NChar, 64);
               parameterPassword.Value = this.GetSHA256Hash(password);
               sqlTransaction = sqlConnection.BeginTransaction("SampleTransaction");

               // more database activity, execute command, store results in datareader
               sqlTransaction.Commit();
               sqlConnection.Close();
          }
          catch (SqlException ex)
          {
               if(sqlTransaction != null)
                    sqlTransaction.Rollback();
               MessageBox.Show(ex.Number + ":" + ex.Message, ex.Server + ":" + ex.Source, MessageBoxButtons.OK, MessageBoxIcon.Error);
          }
     }
     return sessionId;
}

我试图再次搜索类似的问题并找到一些更接近的答案。

Is SqlCommand.Dispose() required if associated SqlConnection will be disposed?

Does SqlCommand.Dispose close the connection?

我想我应该在try-catch中添加一个finally子句,并为我创建的所有sql对象调用几个Dispose方法。我希望这样做还是有推荐的风格吗?

finally
{
     if(sqlCommand != null)
          sqlCommand.Dispose();
     if(sqlTransaction != null)
          sqlTransaction.Dispose();
     ...
}

我尝试在try-catch块中为其中一个sqlCommand对象放置一个using语句,但如果在抛出异常时该部分代码中止,则执行会跳转到catch部分。使用不会处置sqlCommand对象。

try
{
     ...
     using(sqlCommand = sqlConnection.CreateCommand())
     {
          sqlCommand.CommandText = "GetUserByUserIdPassword2";
          sqlCommand.CommandTimeout = 30;
          sqlCommand.CommandType = CommandType.StoredProcedure;
          SqlParameter parameterUserId = sqlCommand.Parameters.Add("@UserId", SqlDbType.NVarChar, 32);
          parameterUserId.Value = userId;
          SqlParameter parameterPassword = sqlCommand.Parameters.Add("@Password", SqlDbType.NChar, 64);
          parameterPassword.Value = this.GetSHA256Hash(password);

          SqlDataReader reader = sqlCommand.ExecuteReader(CommandBehavior.SingleRow);
          // throws exception, no stored procedure "GetUserByUserIdPassword2"
     }
     ...
}
catch() {}

// sqlCommand still accessible at this point because using above was "aborted".

4 个答案:

答案 0 :(得分:2)

如果对象实现IDisposable,则使用using。如果由于某种原因需要创建新的SqlCommand,请完成一个using块并启动一个新块。如果您仍然需要访问第一个SqlCommand,请嵌套它们 只要您没有从命令中打开SqlCommand,就可以重用datareader个对象。因此,您可以创建SqlCommand,设置其所有属性并执行它,然后重置其所有属性并再次执行,依此类推。这确实略微节省了内存分配的成本,但我认为这也降低了代码的清晰度,所以只有在分析证明有必要时才会这样做。

答案 1 :(得分:1)

  

将通过连接对象递归处理它们

当然,usingSqlConnections或其他ADO.NET对象一无所知。它只知道打电话给Dispose。无论处置的对象是什么,都会发生。

恰好是处理SqlConnection也会处理与连接相关的所有读者和命令的资源。您只需要处理连接。

我不知道这是否记录在MSDN上,但我知道它是通过反编译程序集来实现的。出于兼容性原因,他们永远不能改变这种行为,因此可以安全地依赖它。

通常,您必须在实现IDisposable的任何对象上调用dispose。此规则的例外情况是,您确定不会调用Dispose是安全的。就像在这种情况下一样。

答案 2 :(得分:1)

我想我找到了答案!

即使抛出的异常跳过了嵌套的using语句,sqlCommand对象仍可在代码的底部访问,但sqlCommand仍会在稍后处理。我通过实际为所述sqlCommand对象的dispos事件分配一个函数来测试它。

由于事务对象的要求,此处的代码与上面的代码略有不同。但逻辑基本相同。

try
{
       using(sqlCommand = new SqlCommand("GetUserByUserIdPassword2", sqlConnection, sqlTransaction))
       {
              sqlCommand.CommandTimeout = 15;
              sqlCommand.CommandType = CommandType.StoredProcedure;
              SqlParameter parameterUserId = sqlCommand.Parameters.Add("@UserId", SqlDbType.NVarChar, 32);
              parameterUserId.Value = userId;
              SqlParameter parameterPassword = sqlCommand.Parameters.Add("@Password", SqlDbType.NChar, 64);
              parameterPassword.Value = this.GetSHA256Hash(password);

              sqlCommand.Disposed += new System.EventHandler(this.sqlCommand_Disposed);

              SqlDataReader sqlDataReader = sqlCommand.ExecuteReader(CommandBehavior.SingleRow);
              // exception thrown, no sored proc "GetUserByUserIdPassword2"
              sqlDataReader.Close();
       }
}
catch(...) {}

...

private void sqlCommand_Disposed(object sender, EventArgs e)
{
     MessageBox.Show("sqlCommand has been disposed");
}

所以,基本上,即使嵌套的using语句是" aborted"通过try块中抛出的异常,并且执行被跳过到catch块,在函数退出之后(或当变量超出范围时)仍然为该对象调用Dispose方法。

我认为这种行为对于任何数量的嵌套使用语句都是相同的。

答案 3 :(得分:-1)

在CSharp中,using是一个特殊关键字,在代码中有不同的行为。

如果您将using放在代码页的顶部,然后使用act作为c ++' s Include或VB(和其他一些脚本langs')Import

如果你写了一个" using"然后它就像一个paranthesis,它的作用就像是CSharp的Dispose()方法的别名..或c ++的Destructor tilda或普通c的free()方法。

关于SQL Connection ..最好的方法是首先Close()连接(所以这个,连接仍然活着 - 实际上只是准备使用 - 但是null ..返回等待连接池中的新订单..)然后处理,如果你需要..

如果您仍有问题,并且怀疑调用Dispose()方法不足以释放资源

然后您可以使用Garbage Collector' Collect()静态方法来强制进行垃圾回收 但是,由于面临意外的垃圾收集问题而不是微软推荐(即您收集表单但需要从该表单中获取一些变量)

在使用示例中:

public void Dispose (bool ForceToCollect )
{
   if (ForceToCollect) 
               GC.Collect (0, GCCollectionMode.Forced );
}

Gc.Collect()方法中,您可以设置使用第一个选项释放哪个数据内存组,其中int为int ..如果您计划释放最后一个组的资源 - 表示上次创建的项目组,作为最后创建的示例表单实例及其所有子控件 - 然后int应为0;在最后一个创建之前为1的那个应该是1,依此类推..

希望这会有所帮助..