Как описано в «Программировании служб WCF» Джувала Лоуи, WCF использует протокол двухфазной фиксации (2PC) для управления распределенными транзакциями:
Когда транзакция заканчивается, менеджер транзакций проверяет объединенное голосование.участвующих услуг.Если какой-либо сервис или клиент проголосовал за прерывание, транзакция обречена: всем участвующим ресурсам дано указание отменить изменения, внесенные во время транзакции.
Я тестировал разные менеджеры ресурсов (VRM, sql server,...) против транзакций, но ни одна из них не работала правильно.Основная проблема заключается в том, что прерывание распределенной транзакции не отменяет изменения, примененные к ресурсу.
Мой источник состоит из двух консольных программ (клиент и служба).Каждый вызов операции сервиса AddValue добавляет запись (целочисленное поле) в таблицу базы данных sql-сервера.Второй вызов AddValue вызывает исключение, которое прерывает распределенную транзакцию.
Запустите службу, а затем программу клиента.Теперь просмотрите массив values3 (в клиентской программе), это будет {1, 2}, что означает, что изменения не отменены.
Сервисный контракт:
[ServiceContract(SessionMode=SessionMode.Required)]
public interface IExercise
{
[OperationContract]
[TransactionFlow(TransactionFlowOption.Mandatory)]
void AddValue(int value);
[OperationContract]
int[] GetValues();
}
Реализация сервиса:
[ServiceBehavior(
InstanceContextMode = InstanceContextMode.PerSession,
ReleaseServiceInstanceOnTransactionComplete = false,
ConcurrencyMode = ConcurrencyMode.Single,
TransactionIsolationLevel = System.Transactions.IsolationLevel.Serializable,
TransactionTimeout = "00:5:0")]
[BindingRequirement(TransactionFlowEnabled = true)]
[ErrorHandlerBehavior]
public class ExerciseService : IExercise
{
#region IExercise
[OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = false)]
public void AddValue(int value)
{
Debug.Assert(Transaction.Current != null &&
Transaction.Current.TransactionInformation.DistributedIdentifier != Guid.Empty);
AddDbValue(value);
//AddVRMValue(value);
if (value == 2)
throw new Exception("abort tx");
//OperationContext.Current.SetTransactionComplete();
//Transaction.Current.Rollback();
}
public int[] GetValues()
{
var result = GetDbValues();
//var result = GetVRMValues();
return result;
}
#endregion
#region db
private const string Table1 = "dbo.Table1";
private const string Column1 = "Column1";
private static SqlConnection connection;
static ExerciseService()
{
var text = Properties.Settings.Default.ExerciseConnectionString;
connection = new SqlConnection(text);
connection.Open();
}
private void AddDbValue(int value)
{
var text = "insert into " + Table1 + " values (" + value + ")";
using (var command = new SqlCommand(text, connection))
{
command.ExecuteNonQuery();
}
}
private int[] GetDbValues()
{
using (var command = new SqlCommand("select * from " + Table1, connection))
using (var reader = command.ExecuteReader())
{
var result = reader.ReadColumn<int>(Column1).ToArray();
return result;
}
}
#endregion
#region VRM
private static TransactionalList<int> Storage = new TransactionalList<int>();
private void AddVRMValue(int value)
{
Storage.Add(value);
}
private int[] GetVRMValues()
{
var result = Storage.ToArray();
return result;
}
#endregion
}
Сервисный прокси:
public class ExerciseClient : ClientBase<IExercise>, IExercise
{
#region IExercise
public void AddValue(int value)
{
Channel.AddValue(value);
}
public int[] GetValues()
{
var result = Channel.GetValues();
return result;
}
#endregion
}
Клиентская программа:
class Program
{
static void Main(string[] args)
{
try
{
using (var proxy2 = new ExerciseClient())
using (var proxy = new ExerciseClient())
{
using (var s = new TransactionScope(TransactionScopeOption.Required, TimeSpan.FromMinutes(5)))
{
proxy.AddValue(1);
var values = proxy.GetValues();
proxy2.AddValue(2);
var values2 = proxy2.GetValues();
s.Complete();
}
}
}
catch
{
}
using (var proxy3 = new ExerciseClient())
{
var values3 = proxy3.GetValues();
}
}
}
А это файл конфигурации службы:
<services>
<service name="Tofigh.Exercise.ExerciseService">
<endpoint address="net.tcp://localhost/Exercise"
binding="netTcpBinding"
bindingConfiguration="bindingConfiguration"
contract="Tofigh.Exercise.IExercise"
/>
</service>
</services>
<bindings>
<netTcpBinding>
<binding name="bindingConfiguration"
transactionFlow="true">
<reliableSession enabled="true" />
</binding>
</netTcpBinding>
</bindings>
Мой второй вопрос: как DTC вызывает RM для отката изменений в случае прерывания транзакции?Рассмотрим случаи, когда жизненный цикл RM зависит от жизненного цикла обслуживания.Сохраняет ли LTM или что-то еще ссылку на ресурс, пока не закончится распределенная трансация?Что делать, если ресурс в это время недоступен?