Область транзакций
Класс TransactionScope (область транзакций) предоставляет простой способ отметить участок кода как участвующий в транзакции. Область транзакций может выбрать и управлять внешней транзакцией автоматически. При создании экземпляра TransactionScope с помощью инструкции new диспетчер транзакций определяет, в какой транзакции следует участвовать. Если в области транзакции не возникает исключений (т. е. в промежутке между созданием экземпляра объекта TransactionScope и вызовом метода Dispose), то транзакция, в которой участвует область, может продолжить работу. Если в области транзакции возникает исключение, то будет произведен откат транзакции, в которой она участвует. После того как приложение завершит всю работу, которую необходимо выполнить в транзакции, один раз вызывается метод Complete. Это оповещает диспетчер транзакций о том, что будет допустимо зафиксировать транзакцию. Если метод Complete не был вызван, транзакция завершается. Дополнительные сведения об области транзакций см. в документации MSDN.
Реализация области транзакций в платформе .NET Framework
Пространство имен System.Transactions является частью Microsoft .NET Framework версии 2.0 версий 3.0, 3.5 и 4. Оно предоставляет платформу транзакций, полностью интегрированную с ADO.NET и со средой SQL Server CLR. Класс System.Transactions.transactionscope делает блок кода транзакционным, неявно прикрепляя связи в распределенной транзакции. Метод Complete может быть вызван в конце блока кода, отмеченного TransactionScope. Транзакция прекращается, если метод Complete не вызывается до вызова метода Dispose. При возникновении исключения транзакция считается прекращенной.
Дополнительные сведения см. на веб-сайте https://msdn2.microsoft.com/en-us/library/ms172070(VS.80).aspx.
Ограничения пространства имен System.Transactions
Пространство имен System.Transactions не поддерживается в .NET Compact Framework 2.0. Поэтому реализация будет предназначена только для настольных операционных систем Windows и будет соответствовать версиям .NET Framework 2.0, .NET Framework 3.0, .NET Framework 3.5 или .NET Framework 4.
Следует заметить, что по истечении времени ожидания инфраструктура System.Transactions вызовет откат из отдельного потока. Основной поток не будет уведомлен, что в отдельном потоке происходит откат. При работе с транзакциями с длительным временем выполнения может возникнуть недетерминированное поведение и случаи частичной фиксации. Чтобы разрешить эту проблему, при создании объекта области транзакции следует увеличить его время существования.
В области транзакций можно прикрепить только один объект SqlCeConnection, если к ней еще не прикреплен другой диспетчер транзакций.
Если открыто соединение вне области транзакций и его необходимо прикрепить в существующей области транзакций, то это можно сделать с помощью метода EnlistTransaction.
Реализация TransactionScope
SQL Server Compact прикрепляется как ресурс к инфраструктуре System.Transactions.
По умолчанию, если несколько команд открываются в прикрепленном соединении в области транзакций, они прикрепляются к контексту текущей транзакции. Также возможно открыть соединение, которое не прикреплено к области транзакций. При этом создаются неприкрепленные команды. Транзакции SQL Server Compact в области транзакций получают по умолчанию сериализуемый тип.
Поскольку в SQL Server Compact не поддерживаются распределенные транзакции, два или более соединения нельзя прикрепить в одну область транзакций или во вложенную область транзакций, имеющую ту же внешнюю область транзакций.
Не разрешаются явные транзакции для прикрепленного соединения в области транзакций.
Не поддерживается неявное прикрепление соединений. Для прикрепления в области транзакций можно выполнить следующие действия.
Открыть соединение в области транзакций.
Если соединение уже открыто, вызвать метод EnlistTransaction для объекта соединения.
Ограничения SQL Server Compact
Ниже приведены различия между SQL Server Compact и SQL Server, относящиеся к области транзакций.
Распределенные транзакции не поддерживаются в SQL Server Compact. Вследствие этого локальная транзакция не будет автоматически повышена до свободно распределяемой транзакции.
Параллельные транзакции поддерживаются в SQL Server Compact даже для нескольких активных результирующих наборов; однако в SQL Server параллельные транзакции не поддерживаются.
TransactionScope: пример 1
Следующий пример показывает, как с помощью класса TransactionScope прикрепить, а затем зафиксировать транзакцию.
using (TransactionScope transScope = new TransactionScope())
{
using (SqlCeConnection connection1 = new
SqlCeConnection(connectString1))
{
/* Opening connection1 automatically enlists it in the
TransactionScope as a lightweight transaction. */
connection1.Open();
// Do work in the connection.
}
// The Complete method commits the transaction.
transScope.Complete();
}
TransactionScope: пример 2
Следующий пример показывает, как с помощью класса TransactionScope создать две таблицы в базе данных.
static void Setup(String strDbPath)
{
/* Delete the database file if it already exists. We will create a new one. */
if (File.Exists(strDbPath))
{
File.Delete(strDbPath);
}
// Create a new database.
SqlCeEngine engine = new SqlCeEngine();
engine.LocalConnectionString = @"Data source = " + strDbPath;
engine.CreateDatabase();
engine.Dispose();
}
/* This function creates two tables in the specified database. Before creating the tables, it re-creates the database.
These tables are created in a TransactionScope, which means that either both of them will be created or not created at all. */
static void CreateTablesInTransaction(String strDbPath)
{
/* Create the connection string. In order to have the connection enlisted into the TransactionScope, the Enlist property in the connection string must be explicitly set to true. */
String strConn = @"Data source = " + strDbPath + ";Enlist=true";
SqlCeConnection conn = new SqlCeConnection(strConn);
try
{
Setup(strDbPath); // Create a new database for our tables.
using (TransactionScope scope = new TransactionScope())
{
/* To enlist a connection into a TransactinScope, specify 'Enlist=true' in its connection string and open the connection in the scope of that TransactionScope object. */
conn.Open();
// Create the tables.
SqlCeCommand command = conn.CreateCommand();
command.CommandText = @"create table t1(col1 int)";
command.ExecuteNonQuery();
command.CommandText = @"create table t2(col1 int)";
command.ExecuteNonQuery();
/* If this statement is executed and the TransactionScope has not timed out, t1 and t2 will be created in the specified database. */
scope.Complete();
}
}
catch (SqlCeException e)
{
Console.WriteLine(e.Message);
}
finally
{
if (conn.State != System.Data.ConnectionState.Closed)
{
conn.Close();
}
conn.Dispose();
}
}
TransactionScope: пример 3
Следующий пример показывает вставку значений в две таблицы с помощью класса TransactionScope.
/* This function assumes that tables t1(col1 int) and t2(col1 int) are already created in the specified database. The condition for the following function is this:
If INSERTs into the first table succeed, then INSERT into the second table. However, if the INSERTs into the second table fail, roll back the inserts in the second table but do not roll back the inserts in the first table. Although this can also be done by way of regular transactions, this function demonstrates how to do it using TransactionScope objects. */
static void CreateTableAndInsertValues(String strDbPath)
{
/* Create the connection string. To have the connection enlisted into the TransactionScope, the Enlist property in the connection string must be explicitly set to true. */
String strConn = @"Data source = " + strDbPath + ";Enlist=true";
SqlCeConnection conn1 = new SqlCeConnection(strConn);
SqlCeConnection conn2 = new SqlCeConnection(strConn);
try
{
using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Required))
{
conn1.Open();
SqlCeCommand command1 = conn1.CreateCommand();
command1.CommandText = @"insert into t1(col1) values(1)";
command1.ExecuteNonQuery();
command1.CommandText = @"insert into t1(col1) values(2)";
command1.ExecuteNonQuery();
/* If this statement is executed and the TransactionScope has not timed out, two records will be inserted into table 1. */
scope.Complete();
try
{
using (TransactionScope scopeInner = new TransactionScope(TransactionScopeOption.RequiresNew))
{
conn2.Open();
SqlCeCommand command2 = conn2.CreateCommand();
command2.CommandText = @"insert into t2(col1) values(1)";
command2.ExecuteNonQuery();
command2.CommandText = @"insert into t2(col1) values(2)";
command2.ExecuteNonQuery();
/* If this statement is run and the TransactionScope has not timed out, two records will be inserted into table 2. */
scopeInner.Complete();
}
}
catch (SqlCeException e)
{
Console.WriteLine(@"Exception in Inner block: " + e.Message);
}
}
}
catch (SqlCeException e)
{
Console.WriteLine(@"Exception in Outer block: " + e.Message);
}
finally
{
// Close both the connections.
if (conn1.State != System.Data.ConnectionState.Closed)
{
conn1.Close();
}
if (conn2.State != System.Data.ConnectionState.Closed)
{
conn2.Close();
}
conn1.Dispose();
conn2.Dispose();
}
}