Я нашел решение своей проблемы.
Возникла исключительная ситуация, когда я пытался присоединить сущность, которая уже присоединена к текстовому тексту, и это произошло из-за того, что в моем методе Update () не было указания, если сущность уже присоединена к контексту данных, что метод использует.
Когда я удалил вложение, я не получил исключения, но сущность иногда не обновлялась (в случаях, когда текст данных является новым).
Поэтому я подумал, как соединить эти две конфликтующие ситуации с решением TransactionScope.
Я создал класс TransactionScope, который позволяет методам присоединяться и выходить из области видимости.
Метод Join () создает новый текстовый текст и инициализирует счетчик использования, поэтому любой последовательный вызов этого метода увеличивает этот счетчик на единицу.
Метод Leave () уменьшает счетчик использования, и когда он достигает нуля, текст данных удаляется.
Существует также свойство, которое возвращает контекст данных (вместо каждого метода, чтобы попытаться получить свой собственный контекст).
Я изменил методы, которым нужен контекст данных, с того, что описано в моем вопросе, на:
_myTranscationScope.Join();
try
{
var context = _myTransactionScope.Context;
//Do some database operation for example:
context.User.GetByKey("username");
}
finally
{
_myTranscationScope.Leave();
}
Кроме того, я переопределил метод Dispose для datacontext и перенес туда отсоединение сущностей вместо того, чтобы делать это в каждом методе.
Все, что мне нужно, чтобы убедиться, что у меня есть правильная область транзакции и убедиться, что каждый вызов присоединения также имеет вызов для выхода (даже в исключительных случаях)
Теперь мой код отлично работает со всеми сценариями (включая операции с одной базой данных, сложные транзакции, работу с сериализованными объектами).
Вот код класса TransactionScope (я удалил линейные коды, которые зависят от проекта, над которым я работаю, но все же это полностью рабочий код):
using System;
using CSG.Games.Data.SqlRepository.Model;
namespace CSG.Games.Data.SqlRepository
{
/// <summary>
/// Defines a transaction scope
/// </summary>
public class TransactionScope :IDisposable
{
private int _contextUsageCounter; // the number of usages in the context
private readonly object _contextLocker; // to make access to _context thread safe
private bool _disposed; // Indicates if the object is disposed
internal TransactionScope()
{
Context = null;
_contextLocker = new object();
_contextUsageCounter = 0;
_disposed = false;
}
internal MainDataContext Context { get; private set; }
internal void Join()
{
// block the access to the context
lock (_contextLocker)
{
CheckDisposed();
// Increment the context usage counter
_contextUsageCounter++;
// If there is no context, create new
if (Context == null)
Context = new MainDataContext();
}
}
internal void Leave()
{
// block the access to the context
lock (_contextLocker)
{
CheckDisposed();
// If no one using the context, leave...
if(_contextUsageCounter == 0)
return;
// Decrement the context usage counter
_contextUsageCounter--;
// If the context is in use, leave...
if (_contextUsageCounter > 0)
return;
// If the context can be disposed, dispose it
if (Context.Transaction != null)
Context.Dispose();
// Reset the context of this scope becuase the transaction scope ended
Context = null;
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
lock (_contextLocker)
{
if (_disposed) return;
if (disposing)
{
if (Context != null && Context.Transaction != null)
Context.Dispose();
_disposed = true;
}
}
}
private void CheckDisposed()
{
if (_disposed)
throw new ObjectDisposedException("The TransactionScope is disposed");
}
}
}