PLINQO и проблема транзакций
Я только начал работать с PLINQO для реализации уровня репозитория и уровня данных в моей n-уровневой распределенной системе.
Уровень данных состоит из следующих уровней: хранилище, поставщик данных, служба данных
Уровень хранилища, отвечающий за получение данных из базы данных и настройку данных в базе данных.
Уровень поставщика данных является шлюзом между хранилищем и уровнем обслуживания
Уровень обслуживания данных содержит всю бизнес-логику и правила.
У меня возникла проблема в этой архитектуре при использовании транзакций: я получаю InvalidOperationException с сообщением об ошибке: "Не удается присоединить сущность, которая уже существует". очень часто.
Причина в том, что дизайн слоя хранилища. Общие шаги во всех методах хранилища:
MyDataContext context;
if(InTransaction == true)
context = GetExistingContext();
else
context = GetNewContext();
//Do some database operation for example:
var user = context.User.GetByKey("username");
//Detach from context
user.Detach();
if(InTransaction == false)
context.Dispose();
InvalidOperationException возникает, когда InTransaction имеет значение true, и я вызываю два метода, которые работают с одним и тем же объектом. Поскольку InTransaction имеет значение true, оба метода используют один и тот же текст данных. первый метод присоединяет объект, а в конце отсоединяет, а второй метод также пытается присоединить, и тогда возникает исключение.
Что я делаю не так и как я могу предотвратить это?
Спасибо,
Коби
1 ответ
Я нашел решение своей проблемы.
Исключение произошло, когда я попытался присоединить сущность, которая уже присоединена к текстовому тексту данных, и это произошло потому, что в моем методе 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");
}
}
}