开发者

PLINQO and Transactions problem

I just started working with PLINQO to implement the repository layer and the data tier in my n-tier distributed system.

The data tier consists of the following layers: repository, data provider, data service

The repository layer responsible for getting data from database and setting data in the database.

The data provider layer is the gate between the repository and the service layer

The data service layer holds all the business logic and rules.

I am having a problem in this architecture when using transactions: I am getting InvalidOperationException with error message: "Cannot a开发者_如何学Pythonttach an entity that already exists." very often.

The reason is because the desing of the repository layer. The common steps in all the repository methods are:

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();

The InvalidOperationException happens when InTransaction is true and I am calling to two methods that operates on the same entity. Becuase the InTransaction is true the two methods uses the same datacontext. the first method attach the entity and in the end detach and the second method also tries to attach and then the exception occurs.

What am I doing wrong and how can I prevent this from happen ?

Thanks,

Koby


I found a solution to my problem.

The exception occurred when I tried to attach an entity that is already attached to a datacontext and this happened because I didn't had an indication in my Update() method if the entity is already attached to the data context that the method uses.

When I removed the attach I didn't got the exception but the entity sometimes was not udpated (in cases the datacontext is new).

So I thought how to bridge between those 2 conflicting situation and the solution is TransactionScope.

I created TransactionScope class that allows methods to Join and Leave the scope.

The Join() method creates new datacontext and initialize usage counter so any consecutive call to this method increase this counter by one.

The Leave() method decrements the usage counter and when it reaches to zero the datacontext is disposed.

There is also property that returns the data context (instead of each method to try and get its own context).

I changed the methods that need data context from what described in my question to:

_myTranscationScope.Join();

try
{
  var context = _myTransactionScope.Context;

  //Do some database operation for example:
  context.User.GetByKey("username");

}
finally
{
    _myTranscationScope.Leave();
}

In addition I overrided the Dispose method of the datacontext and moved the detach of the entities to there instead of doing this in each method.

All I need to make sure is that I have the correct transaction scope and make sure each call to join has also call to leave (even in exception)

This now make my code works perfect with all the scenarios (including single database operations, complex transactions, working with serialized objects).

Here is the code of the TransactionScope class (I removed line codes that depends on the project I am working on, but still it is fully working code):

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

    }
 }
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜