在故障回滚到上一个保存点(而不是回滚整个事务)的情况下,有没有办法在事务和中添加检查约束?
在我的情况下,当ALTER TABLE ... ADD CONSTRAINT命令失败时,事务无法回滚到保存点(尝试这样做会抛出InvalidOperationException)。
概述以证明关键点:
SqlTransaction transaction = connection.BeginTransaction();
// ... execute SQL commands on the transaction ...
// Create savepoint
transaction.Save("mySavepoint");
try
{
// This will fail...
SqlCommand boom = new SqlCommand(
"ALTER TABLE table WITH CHECK ADD CONSTRAINT ...",
connection,
transaction);
boom.ExecuteNonQuery();
}
catch
{
// ...and should be rolled back to the savepoint, but can't.
try
{
transaction.Rollback("mySavepoint");
}
catch (InvalidOperationException)
{
// Instead, an InvalidOperationException is thrown.
// The transaction is unusable and can only be rolled back entirely.
transaction.Rollback();
}
}
这里有准备运行的演示代码进行测试(你需要一个名为“test”的数据集):
public class Demo
{
private const string _connectionString = "Data Source=(local);Integrated security=true;Initial Catalog=test;";
private const string _savepoint = "save";
private static readonly string _tableName = DateTime.Now.ToString("hhmmss");
private static readonly string _constraintName = "CK" + DateTime.Now.ToString("hhmmss");
private static readonly string _createTable = "CREATE TABLE [dbo].[" + _tableName + "] ([one] [int] NULL,[two] [int] NULL) ON [PRIMARY]";
private static readonly string _insert1 = "INSERT INTO [" + _tableName + "] VALUES (1,1)";
private static readonly string _addConstraint = "ALTER TABLE [dbo].[" + _tableName + "] WITH CHECK ADD CONSTRAINT [" + _constraintName + "] CHECK (([one]>(1)))";
private static readonly string _insert2 = "INSERT INTO [" + _tableName + "] VALUES (2,2)";
public static void Main(string[] args)
{
// Example code! Please ignore missing using statements.
SqlConnection connection = new SqlConnection(_connectionString);
connection.Open();
SqlTransaction transaction = connection.BeginTransaction();
SqlCommand createTable = new SqlCommand(_createTable, connection, transaction);
createTable.ExecuteNonQuery();
// Create savepoint
transaction.Save(_savepoint);
SqlCommand insert1 = new SqlCommand(_insert1, connection, transaction);
insert1.ExecuteNonQuery();
try
{
// This will fail...
SqlCommand boom = new SqlCommand(_addConstraint, connection, transaction);
boom.ExecuteNonQuery();
}
catch
{
// ...and should be rolled back to the savepoint, but can't
transaction.Rollback(_savepoint);
}
SqlCommand insert2 = new SqlCommand(_insert2, connection, transaction);
insert2.ExecuteNonQuery();
transaction.Commit();
connection.Close();
}
}
答案 0 :(得分:1)
当我尝试使用TSQL时,我得到了同样的行为。
BEGIN TRAN
CREATE TABLE foo (col int)
INSERT INTO foo values (1)
SAVE TRANSACTION ProcedureSave;
BEGIN TRY
ALTER TABLE foo WITH CHECK ADD CONSTRAINT ck CHECK (col= 2)
END TRY
BEGIN CATCH
SELECT XACT_STATE() AS XACT_STATE
/*Returns -1, transaction is uncommittable. Next line will fail*/
ROLLBACK TRANSACTION ProcedureSave
/*Msg 3931, Level 16, State 1: The current transaction cannot be committed and
cannot be rolled back to a savepoint. Roll back the entire transaction.*/
END CATCH
GO
SELECT @@TRANCOUNT AS [@@TRANCOUNT] /*Zero the transaction was rolled back*/
我没有在文档中找到任何信息,说明哪些错误会导致交易以这种方式注定失败。我认为this connect item comment没有这样的文件。
答案是,错误处理是 逐案。这不仅取决于 服务器,还有错误类型 和背景。不幸的是,有 没有公布的错误处理列表 不同错误的行为。在 一般来说,只有严重错误才应该 杀死连接和极端的连接 关机服务器。但是当涉及到 语句中止与事务中止, 很难总结规则 - 即具体情况。
答案 1 :(得分:0)
我认为你不能在脚本和C#中混合使用保存点。我执行以下SQL:
BEGIN TRANSACTION
INSERT INTO Foos (Fooname)
VALUES ('Bar1')
SAVE TRANSACTION MySavePoint;
INSERT INTO Foos (FooName)
VALUES ('Bar2')
ROLLBACK TRANSACTION MySavePoint
COMMIT TRANSACTION
这将在SQL中使用,并将使用以下代码:
using (SqlConnection conn = new SqlConnection("connectionString"))
{
conn.Open();
using (SqlTransaction trans = conn.BeginTransaction())
using (SqlCommand comm = new SqlCommand("The Above SQL", conn, trans))
{
comm.ExecuteNonQuery();
trans.Commit();
}
}
如果您尝试trans.Rollback("MySavePoint");
它将失败,因为trans
对象无法控制保存点 - 它不知道它。
如果将SQL拆分为两个独立插入并使用以下代码:
using (SqlConnection conn = new SqlConnection("connectionString"))
{
conn.Open();
using (SqlTransaction trans = conn.BeginTransaction())
using (SqlCommand comm1 = new SqlCommand("INSERT INTO Foos(fooName) VALUES('Bar1')", conn, trans))
using (SqlCommand comm2 = new SqlCommand("INSERT INTO Foos(fooName) VALUES('Bar2')", conn, trans))
{
comm1.ExecuteNonQuery();
trans.Save("MySavePoint");
comm2.ExecuteNonQuery();
trans.Rollback("MySavePoint");
trans.Commit();
}
}
它会按预期工作。
只需注意,始终处理实现IDisposable
的对象 - 最好是在using
语句中。
进一步阅读:
http://www.davidhayden.com/blog/dave/archive/2005/10/15/2517.aspx
更新:在使用您的示例代码执行了一段时间后,看起来由于来自SQL的错误,事务正在回滚并变得无法使用。正如在另一个答案中所说的那样,似乎与SQL一起,由于某些错误,无论保存点如何,事务都被强制回滚。唯一可靠的方法是重新排序针对数据库运行的命令,而不是依赖保存点,或者至少不依赖于保存点中的操作。