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");
}
}
}
精彩评论