[ Team LiB ]Recipe 6.4 Using ADO.NET and SQL Server DBMS Transactions Together Problem You need to use a DBMS transaction within a SQL Server stored procedure from an ADO.NET transacti
Trang 1[ Team LiB ]
Recipe 6.4 Using ADO.NET and SQL Server DBMS Transactions Together
Problem
You need to use a DBMS transaction within a SQL Server stored procedure from an ADO.NET transaction with the SQL Server NET data provider
Solution
Use error-checking within a catch block as shown in Example 6-5
The sample uses a single stored procedure:
InsertCategories_Transacted
Used to insert a single record into the Categories table in the Northwind database within a DBMS transaction If the record insert fails, the transaction is rolled back; otherwise, the transaction is committed
The sample code contains two event handlers:
Form.Load
Sets up the sample by filling a DataTable with the Categories table from the
Northwind sample database The default view of the table is bound to a data grid
on the form
Insert Button.Click
Inserts user-entered data for the Categories records into the Northwind database within a manual transaction using a DBMS transacted stored procedure The transaction is rolled back in the stored procedure if either the Force DBMS
Rollback checkbox is checked or if no value is entered for the Category Name field Otherwise, the ADO.NET manual transaction is committed
Example 6-5 Stored procedure: InsertCategories_Transacted
CREATE PROCEDURE InsertCategories_Transacted
@CategoryId int output,
@CategoryName nvarchar(15),
@Description ntext,
@Rollback bit = 0
Trang 2AS
SET NOCOUNT ON
begin tran
insert Categories(
CategoryName,
Description)
values (
@CategoryName,
@Description)
if @@error<>0 or @@rowcount=0 or @Rollback=1 begin
rollback tran
set @CategoryID = -1
return 1
end
commit tran
set @CategoryID = Scope_Identity( )
select @CategoryID CategoryId
return 0
The C# code is shown in Example 6-6
Example 6-6 File: DbmsTransactionForm.cs
// Namespaces, variables, and constants
using System;
using System.Configuration;
using System.Windows.Forms;
using System.Data;
using System.Data.SqlClient;
private SqlDataAdapter da;
private DataTable dt;
//
Trang 3private void DbmsTransactionForm_Load(object sender, System.EventArgs e) {
// Fill the table
String sqlText = "SELECT CategoryID, CategoryName, Description " + "FROM Categories";
da = new SqlDataAdapter(sqlText,
ConfigurationSettings.AppSettings["Sql_ConnectString"]);
dt = new DataTable("Categories");
da.FillSchema(dt, SchemaType.Source);
da.Fill(dt);
// Bind the default view of the table to the grid
dataGrid.DataSource = dt.DefaultView;
}
private void insertButton_Click(object sender, System.EventArgs e)
{
// Create the connection
SqlConnection conn = new SqlConnection(
ConfigurationSettings.AppSettings["Sql_ConnectString"]);
// Create the transaction
conn.Open( );
SqlTransaction tran = conn.BeginTransaction( );
// Create command in the transaction with parameters
SqlCommand cmd =
new SqlCommand("InsertCategories_Transacted", conn, tran);
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Add("@CategoryID", SqlDbType.Int).Direction =
ParameterDirection.Output;
cmd.Parameters.Add("@CategoryName", SqlDbType.NVarChar, 15); cmd.Parameters.Add("@Description", SqlDbType.NText);
cmd.Parameters.Add("@Rollback", SqlDbType.Bit);
try
{
// Set the parameters to the user-entered values
// Set the CategoryName to DBNull if not entered
if(categoryNameTextBox.Text.Trim( ).Length == 0)
cmd.Parameters["@CategoryName"].Value = DBNull.Value;
else
cmd.Parameters["@CategoryName"].Value =
categoryNameTextBox.Text;
Trang 4cmd.Parameters["@Description"].Value = descriptionTextBox.Text; cmd.Parameters["@Rollback"].Value =
forceDbmsRollbackCheckBox.Checked ? 1 : 0;
// Attempt to insert the record
cmd.ExecuteNonQuery( );
// Success Commit the transaction
tran.Commit( );
MessageBox.Show("Transaction committed.");
}
catch (SqlException ex)
{
bool spRollback = false;
foreach (SqlError err in ex.Errors)
{
// Check if transaction rolled back in the
// stored procedure
if(err.Number == 266)
{
MessageBox.Show(ex.Message,
"DBMS transaction rolled back in " +
"stored procedure",
MessageBoxButtons.OK,
MessageBoxIcon.Error);
spRollback = true;
break;
}
}
if (!spRollback)
{
// transaction was not rolled back by the DBMS
// SqlException error Roll back the transaction
tran.Rollback( );
MessageBox.Show(ex.Message);
}
}
catch (Exception ex)
{
// Other Exception Roll back the transaction
tran.Rollback( );
MessageBox.Show(ex.Message);
}
Trang 5finally
{
conn.Close( );
}
// Refresh the data
da.Fill(dt);
}
Discussion
SQL Server returns error 266 if a stored procedure exits with a transaction count that is not the same as when the stored procedure was entered The count is returned by the function @@TRANCOUNT The error simply sends a message to the client and does not affect execution of the stored procedure It doesn't mean that the DBMS transaction in the stored procedure could not be started, completed, or terminated properly
When calling a stored procedure from a NET manual transaction, the transaction count entering the stored procedure is 1 Using the SQL BEGIN TRAN command in the stored procedure creates a nested transaction, increasing the transaction count to 2 If the stored procedure transaction is subsequently committed with the COMMIT TRAN command, the transaction count is decremented back to 1 Keep in mind commits of inner
transactions don't free resources or make modifications permanent, and don't affect outer transactions If ROLLBACK is called, all inner transactions to the outermost transaction are rolled back and the transaction count is decremented to 0 Error 266 is returned by the rolled back stored procedure since the transaction count entering the stored procedure is 1 while the count when exiting is 0 Attempting to commit or roll back the transaction from .NET after it has been rolled back in the stored procedure will cause an
InvalidOperationException because the transaction has already been rolled back
The solution catches exceptions raised while executing a stored procedure and checks if they correspond to SQL Server error 266, which is the mismatch between the starting and exiting stored procedure transaction count values as a result of the stored procedure rolling back the transaction If the stored procedure has rolled back the transaction, it is not rolled back by the NET code All other errors raised while executing the stored procedure are rolled back by the NET code
[ Team LiB ]