【问题标题】:.Net Using Transactions with Prepared Statements for SqlClient.Net 为 SqlClient 使用带有准备好的语句的事务
【发布时间】:2021-04-22 23:40:09
【问题描述】:

我正在尝试为脚本实现事务,但遇到了一个奇怪的问题。

当我尝试在事务中运行准备好的 SQL 语句时,它失败了,因为它说在分配连接时它需要一个事务。

这如何与 Prepared Statements 一起使用,因为我打算让多个 Transactions 都使用相同的 Prepared Statements。

我的代码如下

class dbTest {

    public static SqlConnection db;
    public static SqlCommand query;

    static void Main(string[] args) {
        db = connect();
        prepare();
        transaction01();
        transaction02();
        transaction03();
    }

    public static void prepare() {
        query = new SqlCommand("select id from table where id = 1 for update", db);
        query.Prepare();
    }

    public static void transaction01() {
        SqlTransaction trans = db.BeginTransaction("Trn01");
        SqlDataReader result = query.ExecuteReader();
        while(result.Read()) { Console.WriteLine(result["id"]); }
        result.Close();
        trans.Commit();
    }

    public static void transaction02() {
        SqlTransaction trans = db.BeginTransaction("Trn02");
        SqlDataReader result = query.ExecuteReader();
        while(result.Read()) { Console.WriteLine(result["id"]); }
        result.Close();
        trans.Commit();
    }

    public static void transaction03() {
        SqlTransaction trans = db.BeginTransaction("Trn03");
        SqlDataReader result = query.ExecuteReader();
        while(result.Read()) { Console.WriteLine(result["id"]); }
        result.Close();
        trans.Commit();
    }

}

如何将 Transaction 分配给现有的 Prepared Statement?

更新

更改了上面的代码以更好地显示问题。 SQL 只准备一次,但我会将它用于多个事务(或至少我想要)

再次更新

我已将下面的答案标记为正确答案,因为它看起来是实现此目标的最佳方法,但在这个非常小的示例中,我需要使用 query.Transaction 让它工作

    public static void transaction01() {
        SqlTransaction trans = db.BeginTransaction("Trn01");
        query.Transaction = trans; // this line fixed it
        SqlDataReader result = query.ExecuteReader();
        while(result.Read()) { Console.WriteLine(result["id"]); }
        result.Close();
        trans.Commit();
    }

【问题讨论】:

  • 这能回答你的问题吗? Pass parameters correctly to SqlCommand
  • using 块用于命令、连接、事务和读取器对象。 NOT 缓存db 连接对象。您无需致电Prepare
  • 将事务添加到命令的构造函数中。你需要先打开连接
  • 参数不是问题,只是没有事务就无法回滚
  • 让我稍微修改一下代码以更好地展示我正在尝试做的事情

标签: sql-server .net-core transactions prepared-statement


【解决方案1】:
  1. 使用 SqlTransaction 时,您必须显式设置 SqlCommand.Transaction,即使在 SQL Server 中登记当前事务不是可选的。

  2. select ... for update 不是有效的 SQL Server 语法,而是使用 UPDLOCK 读取表并在事务期间保留限制性锁。 EG

    select id from table with (updlock) where id = 1

当我尝试运行准备好的 SQL 语句时

  1. 在 SQL Server 中使用准备好的语句很少有用。即使没有它,查询计划缓存也会自动发生,当您使用不同的参数多次执行 SqlCommand 时,它实际上只是减少了网络上请求的大小。

但是准备好的 SqlCommand 仍然绑定到单个 SqlConnection,它的生命周期通常很短,从而最大限度地减少了准备 SqlCommand 的潜在好处。

【讨论】:

    【解决方案2】:

    您需要将SqlCommand.Transaction 设置为您的交易对象。

    SQL Server 不需要准备语句。继续执行。

    请注意,正如您在 this post 中看到的那样,您必须正确处置所有 DB 对象。

    这是你的代码清理:

    class dbTest {
    
        // DO NOT cache connection object
        static void Main(string[] args) {
            using(var db = connect())
            using(var comm = GetCommand(db))
            {
                transaction01(comm);
                transaction02(comm);
                transaction03(comm);
            }
        }
    
        public static SqlCommand GetCommand(SqlConnection conn) {
            return new SqlCommand("select id from table with (updlock) where id = 1", conn);
        }
    
        public static void transaction01(SqlCommand comm) {
            using(SqlTransaction trans = comm.Connection.BeginTransaction("Trn01"))
            {
                comm.Transaction = trans;
                using(SqlDataReader result = query.ExecuteReader())
                    while(result.Read()) { Console.WriteLine(result["id"]); }
                trans.Commit();
            } // no need to close, using will sort that out
        }
    
        public static void transaction02(SqlCommand comm) {
            using(SqlTransaction trans = comm.Connection.BeginTransaction("Trn02"))
            {
                comm.Transaction = trans;
                using(SqlDataReader result = query.ExecuteReader())
                    while(result.Read()) { Console.WriteLine(result["id"]); }
                trans.Commit();
            } // no need to close, using will sort that out
        }
    
        public static void transaction03(SqlCommand comm) {
            using(SqlTransaction trans = comm.Connection.BeginTransaction("Trn03"))
            {
                comm.Transaction = trans;
                using(SqlDataReader result = query.ExecuteReader())
                    while(result.Read()) { Console.WriteLine(result["id"]); }
                trans.Commit();
            } // no need to close, using will sort that out
        }
    
    }
    

    【讨论】:

    • 我认为comm.Transaction 为我解决了这个问题
    猜你喜欢
    • 2017-03-08
    • 2015-07-27
    • 2011-07-19
    • 1970-01-01
    • 2019-07-26
    • 1970-01-01
    • 2016-04-25
    • 1970-01-01
    • 2017-02-25
    相关资源
    最近更新 更多