Работа над проектом WebApi, поддерживаемым mssql с EntityFramework, а также Oracle (12c) с использованием oracle ManagedDataAccess.Client.OracleConnection. Мы используем autofac для внедрения экземпляра нашего контекста на каждый запрос, но весь доступ к оракулу осуществляется только по специальному запросу.
У нас есть определенные операции, которые зависят от обеих баз данных одновременно, поэтому мы решили использовать объект TransactionScope для управления транзакцией.
По большей части это работает хорошо, легковесные транзакции, продвигаемые как распределенные, отлично работают. Но есть одна проблема, с которой я столкнулся после завершения распределенной транзакции.
Данный:
public void Test()
{
var preItem = new HelpItem
{
Field1 = "pre batch";
};
_context.Items.Add(preItem);
_context.SaveChanges(); // This save always works.
var batchResult = FooService.BatchOperation(true);
var postItem = new HelpItem
{
Field1 = "post batch";
};
_context.Items.Add(postItem);
_context.SaveChanges(); // This will succeed/fail depending on whether FooService caused a distributed transaction.
}
С методом BatchOperation как:
public Result BatchOperation(bool triggerDtc)
{
using (var transaction = new new TransactionScope(TransactionScopeOption.Required, new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted }))
{
if (triggerDtc){
// Make requests to both databases.
} else {
// Make request to one database.
}
// Always complete for the sake of the demonstration.
transaction.Complete();
}
}
Если обнаружена распределенная транзакция, а затем завершена и полностью удалена, EF, похоже, не сможет восстановиться и вернуться к работе, как это было до того, как транзакция вступила в игру.
Ошибка:
Распределенная транзакция завершена. Либо зачислите этот сеанс в новую транзакцию, либо в транзакцию NULL.
Каков был бы правильный способ справиться с этим?
В этом конкретном случае вы можете просто создать другую транзакцию вокруг второй части:
var batchResult = FooService.BatchOperation(true);
using (var transaction = new new TransactionScope(TransactionScopeOption.Required, new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted }))
{
var postItem = new HelpItem
{
Field1 = "post batch";
};
_context.Items.Add(postItem);
_context.SaveChanges(); // This save depends on whether FooService caused a distributed transaction.
transaction.Complete();
}
Но эта проблема возникла из-за того, что метод FooService.BatchOperation был изменен с помощью простого поиска в другой базе данных, неосознанно нарушая все методы, которые продолжают использовать контекст после его вызова. С обычной транзакцией один контекст EF может свободно использоваться в них и из них без проблем, есть ли способ добиться того же с распределенной транзакцией?
РЕДАКТИРОВАТЬ: Это действительно меня сейчас смутило. Простого выполнения запроса в другой (нераспределенной) области транзакций достаточно, чтобы восстановить функциональность EF.
public IHttpActionResult Test()
{
var preItem = new HelpItem
{
Field1 = "pre batch";
};
_context.Items.Add(preItem);
_context.SaveChanges(); // This save works.
var batchResult = FooService.BatchOperation(true);
using (var transaction = new new TransactionScope(TransactionScopeOption.Required, new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted }))
{
var lookupAnything = _context.Items.ToList();
transaction.Complete(); // This is optional, because we really don't care and it's disposed either way.
}
var postItem = new HelpItem
{
Field1 = "post batch";
};
_context.Items.Add(postItem);
_context.SaveChanges(); // Now this always works.
}
Очевидно, я не могу просто везде размещать это, поэтому до сих пор не уверен, каково фактическое решение.