为什么不是';";CreateCommand();C#的一部分(或者至少是.NET)?
在与SQL Server一起成功地学习了C#的初始阶段之后,我发现我使用的各种教程都犯了错误,它们声明了一个全局为什么不是';";CreateCommand();C#的一部分(或者至少是.NET)?,c#,sql-server,using-statement,C#,Sql Server,Using Statement,在与SQL Server一起成功地学习了C#的初始阶段之后,我发现我使用的各种教程都犯了错误,它们声明了一个全局SqlConnection,SqlDataAdapter甚至DataSet变量 因此,这段代码在单线程应用程序中效果很好,但在多线程环境中效果不太好。在我对解决方案的研究中,我发现并建议使用using/try方法包装SQL事务的“原子”部分: private static void CreateCommand(string queryString, string connectionS
SqlConnection
,SqlDataAdapter
甚至DataSet
变量
因此,这段代码在单线程应用程序中效果很好,但在多线程环境中效果不太好。在我对解决方案的研究中,我发现并建议使用using/try方法包装SQL事务的“原子”部分:
private static void CreateCommand(string queryString, string connectionString)
{
using (SqlConnection connection = new SqlConnection(connectionString))
{
try
{
SqlCommand command = new SqlCommand(queryString, connection);
command.Connection.Open();
command.ExecuteNonQuery();
}
catch (InvalidOperationException)
{
//log and/or rethrow or ignore
}
catch (SqlException)
{
//log and/or rethrow or ignore
}
catch (ArgumentException)
{
//log and/or rethrow or ignore
}
}
}
所以,我现在要做的是将我的整个代码转换为使用这样的包装器。但在继续之前,我想了解这种方法的权衡。根据我的经验,一个庞大的设计师/工程师团队通常有很好的理由决定不包含某些防御功能。从我作为一名C/C++程序员的角度来看,当C#的整个价值主张是“防御性”(权衡是众所周知的CLR性能影响)时,这一点尤其有趣
总结我的问题:
没有内置此类方法,因为:
- 为每个命令连接和断开数据库并不经济。如果在代码中的给定点执行多个命令,则希望对它们使用相同的连接,而不是重复打开和关闭连接
- 该方法无法知道您要对每种异常执行什么操作,因此它唯一能做的就是重新显示它们,然后在第一时间捕获异常是没有意义的
此外,该方法还需要做更多的工作才能普遍有用。它必须为命令类型和参数获取参数。否则,它只能用于文本查询,并且会鼓励人们动态创建SQL查询,而不是使用存储过程和/或参数化查询,而且这不是一般库想要做的事情。原因在于灵活性。开发人员是否希望在事务中包含该命令,是否希望在给定错误上重试,如果是,重试次数,是否希望每次都从线程池建立连接或创建新连接(具有性能开销),是否希望SQL连接或更通用的DbConnection,等等 然而,MS提供了企业库,这是一套功能,将许多常见的方法封装在一个开源库中。看看数据访问块:
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();
}
}
}
}
只要您将功能封装在一个“瓶颈”方法中,就像您发布的静态方法一样,以便所有数据库访问都在一段易于更改的共享代码中实现,通常不需要进行权衡,因为您可以在以后更改实现,而无需重写大量代码 通过每次创建一个新连接,风险在于每次打开/关闭连接都可能导致昂贵的开销。但是,连接应该合并在一起,在这种情况下,开销可能不会很大,并且这种性能影响可能最小 另一种方法是创建一个连接并保持打开状态,为所有查询共享该连接。这无疑更有效,因为您将每笔交易的管理费用降至最低。但是,性能增益可能很小 在这两种情况下,都会有额外的线程(多个同时查询)问题需要解决,除非您确保所有数据库查询都在单个线程上运行。性能影响都取决于每秒发出的查询数量——当然,如果您使用的是效率极低的查询,那么连接方法的效率也无关紧要;您需要将“优化”时间集中在最糟糕的性能问题上 因此,我建议暂时保持简单,避免过早优化,但尽量将数据库访问代码的实现保持在一个单独的层中,这样您的主代码库就可以简单地向访问层发出命令,并且其中包含最少的数据库特定代码。它对数据库的“了解”越少越好。这将使更改底层实现或移植代码以在将来使用不同的数据库引擎变得更加容易
另一种有助于实现这一点的方法是将查询封装在存储过程中。这意味着您的程序知道过程的名称和参数,但实际访问的表/列隐藏在数据库中。然后,您的代码尽可能少地了解数据库的底层结构,这提高了数据库的灵活性、可维护性和可移植性。存储过程调用也可能比发送一般SQL命令更有效。您是否在询问使用指令时如何权衡
?或者try…catch
包装您的SqlCommand
?它们最终都是一样的。使用
指令为
CallProc("myStoredProc",
command =>
{
command.Parameters.AddWithValue("@paramNameOne", "its value here");
// More parameters for the stored proc...
},
null,
null);