2

在成功地完成了与 SQL Server 一起学习 C# 的初始阶段之后,我发现我使用的各种教程只是通过声明一个 global甚至是SqlConnection变量而弄错了。SqlDataAdapterDataSet

因此,这段代码在单线程应用程序中运行良好,但在多线程环境中运行不佳。在我对解决方案的研究中,我发现MSDN这个教育答案都建议将 SQL 事务的“原子”部分包装在 using/try 方法中:

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 性能损失)时,这尤其有趣。

总结我的问题:

  1. 如上所述在我的代码中封装每笔交易的权衡是什么?
  2. 我应该寻找任何警告吗?
4

4 回答 4

4

原因归结为灵活性。开发人员是否想在事务中包含命令,他们是否想重试给定的错误,如果是多少次,他们是否需要来自线程池的连接或每次都创建一个新连接(具有性能开销),他们想要 SQL 连接还是更通用的 DbConnection 等?

但是,MS 提供了 Enterprise Library,这是一套功能,它包含了开源库中的许多常用方法。看看数据访问块:http: //msdn.microsoft.com/en-us/library/ff632023.aspx

于 2012-11-24T12:38:38.687 回答
3

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

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

  • 该方法无法知道你想对每种异常做什么,所以它唯一能做的就是重新抛出它们,然后一开始就捕获异常是没有意义的。

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

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

于 2012-11-24T12:33:32.993 回答
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);
于 2012-11-24T12:43:51.830 回答
1

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

通过每次创建一个新连接,风险是您可能会为每次打开/关闭连接产生昂贵的开销。但是,应该将连接池化,在这种情况下,开销可能不会很大,并且这种性能影响可能很小。

另一种方法是创建一个连接并保持打开状态,为所有查询共享它。这无疑更有效,因为您将每笔交易的开销降至最低。但是,性能增益可能很小。

在这两种情况下,都会有额外的线程(多个同时查询)问题需要解决,除非您确保所有数据库查询都在单个线程上运行。性能影响都取决于您每秒发出多少查询 - 当然,如果您使用效率极低的查询,您的连接方法的效率就无关紧要了;您需要将“优化”时间集中在最差的性能问题上。

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

Another approach that can help with this is to encapsulate queries in stored procedures. This means your program knows the name of the procedure and the parameters for it, but the actual tables/columns that are accessed are hidden inside the database. Your code then knows as little as possible of the low-level structure of the database, which improves its flexibility, maintainability, and portability. Stored procedure calls can also be more efficient than sending generic SQL commands.

于 2012-11-24T12:44:22.780 回答