为什么“CreateCommand()”不是C#(或至少是.NET)的一部分?

时间:2012-11-24 12:19:32

标签: c# sql-server using-statement

在成功完成与SQL Server一起学习C#的初始阶段之后,我发现我使用的各种教程通过声明全局SqlConnectionSqlDataAdapter甚至{ {1}}变量。

因此,这个在单线程应用程序中运行良好的代码在多线程环境中不能很好地工作。在我对解决方案的研究中,我发现MSDNthis educational answer都建议在using / try方法中包装SQL事务的“原子”部分:

DataSet

所以,我现在要做的是将我的整个代码转换为使用这样的包装器。但在继续讨论之前,我想了解这种方法的权衡。根据我的经验,大型设计师/工程师团队通常有充分理由决定包含某些防御功能。从我作为C / C ++程序员的角度来看,C#的整个价值主张是“防御性”(其中权衡是众所周知的CLR性能命中),这一点尤其有趣。

总结我的问题:

  1. 如上所述,在我的代码中封装每个事务的权衡是什么?
  2. 我应该寻找任何警告吗?

4 个答案:

答案 0 :(得分:4)

原因在于灵活性。开发人员是否希望在事务中包含该命令,他们是否希望在给定错误上重试,如果是这样,他们想要从线程池连接多少次,或者每次都要创建新连接(具有性能开销) ),他们想要一个SQL连接或更通用的DbConnection等。

然而,MS提供了企业库,这是一套功能,它包含了许多开源库中常见方法。看一下Data Access块: http://msdn.microsoft.com/en-us/library/ff632023.aspx

答案 1 :(得分:3)

没有内置这样的方法,因为:

  • 为每个命令连接和断开数据库是不经济的。如果在代码中的给定点执行多个命令,则需要为它们使用相同的连接,而不是重复打开和关闭连接。

  • 该方法无法知道你想对每种异常做什么,所以它唯一可以做的就是重新抛出它们,然后在第一次抓住异常就没有意义了。的地方。

因此,该方法所做的几乎所有事情都是针对每种情况而定的。

此外,该方法必须做更多才能普遍有用。它必须采用命令类型和参数的参数。否则它只能用于文本查询,并且会鼓励人们动态地创建SQL查询,而不是使用存储过程和/或参数化查询,并且它不是普通库想要做的事情。

答案 2 :(得分:2)

1 - 没有真正的权衡,这是非常标准的。

2 - 您的代码可以将命令作为字符串发送为SQL查询,但它缺少一点灵活性:

  • 您不能使用参数化查询(command.Parameters.AddWithValue(...)),一旦开始使用存储过程,这将是强制性的
  • 您无法使用此类output参数
  • 你不能对任何被查询的事情做任何事情

我更喜欢使用这样的东西:

private static void CallProc(string storedProcName, Action<SqlCommand> fillParams, Action postAction, Action onError)
{
    using (SqlConnection connection = new SqlConnection(connectionString))
    {
        using (SqlCommand command = new SqlCommand(String.Format("[dbo].[{0}]", storedProcName), connection))
        {
            try 
            {
                if(fillParams != null)
                    fillParams(command);
                command.Connection.Open();
                command.ExecuteNonQuery();
                if(postAction != null)
                    postAction();
             }        
            catch (InvalidOperationException)
            {
                //log and/or rethrow or ignore
                throw;
            }
            catch (SqlException)
            {
                //log and/or rethrow or ignore
                throw;
            }
            catch (ArgumentException)
            {
                //log and/or rethrow or ignore
                throw;
            }
            catch
            {
                if(onError != null)
                   onError();
            }
        }
    }
}

然后,您可以使用变量来处理返回值,输出参数等。

你打电话就像:

CallProc("myStoredProc",
    command => 
    {
        command.Parameters.AddWithValue("@paramNameOne", "its value here");
        // More parameters for the stored proc...
    },
    null,
    null);

答案 3 :(得分:1)

只要您将功能封装在“瓶颈”方法(如您发布的静态方法)中,以便所有数据库访问都在一个易于更改的共享代码中实现,通常需要没有权衡,因为您可以在以后更改实现,而无需重写大量代码。

通过每次创建一个新连接,风险在于每次打开/关闭连接时可能会产生昂贵的开销。但是,应该汇集连接,在这种情况下,开销可能不会很大,并且这种性能损失可能很小。

另一种方法是创建单个连接并将其保持打开状态,为所有查询共享它。毫无疑问,这会更有效,因为您可以最大限度地减少每笔交易的开销。但是,性能提升可能很小。

在这两种情况下,除非您确保所有数据库查询都在单个线程上运行,否则将要解决其他线程(多个同时查询)问题。性能影响都取决于您每秒触发的查询数量 - 当然,如果您使用的是非常低效的查询,那么连接方法的效率并不重要;您需要将“优化”时间集中在最糟糕的性能问题上。

所以我建议现在保持简单并避免过早优化,但是尝试将数据库访问代码的实现保持在单独的层中,这样您的主代码库就可以简单地向访问层发出命令,并且具有最小化特定于数据库的代码。它对数据库的“了解”越少越好。这将使更改底层实现或将代码移植到以后使用不同的数据库引擎变得更加容易。

另一种可以帮助解决此问题的方法是将查询封装在存储过程中。这意味着您的程序知道过程的名称及其参数,但访问的实际表/列隐藏在数据库中。然后,您的代码尽可能少地了解数据库的低级结构,从而提高其灵活性,可维护性和可移植性。存储过程调用也比发送通用SQL命令更有效。