PLINQO и проблема транзакций - PullRequest
1 голос
/ 06 мая 2011

Я только начал работать с 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 Ответ

2 голосов
/ 06 мая 2011

Я нашел решение своей проблемы.

Возникла исключительная ситуация, когда я пытался присоединить сущность, которая уже присоединена к текстовому тексту, и это произошло из-за того, что в моем методе 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");
        }

    }
 }
...