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");
        }

    }
 }
Другие вопросы по тегам