NHibernate: Update vs Merge methods - c#

I have an NHibernate Repository. I want to call Update method on both loaded and unloaded objects in the NHibernate ISession.
But I get this exception which means I should call Merge method instead of Update.
A different object with the same identifier value was already associated with the session: 0adc76b1-7c61-4179-bb39-a05c0152f1a1, of entity: Eshop.Entities.Currency
How can I generalize my repository to avoid this exception?
Here is my generic repository:
public class NHibernateProvider : IDataProvider
{
#region Variables
private readonly object locker = new object();
private ISessionFactory sessionFactory;
private Configuration configuration;
private ITransaction transaction;
#endregion
#region Properties
private ISessionFactory SessionFactory
{
get
{
lock (locker)
{
if (Null.IsObjectNull(HttpContext.Current.Items["DataProvider"]))
{
configuration = new Configuration();
configuration.Configure();
HttpContext.Current.Items["DataProvider"] = sessionFactory = configuration.BuildSessionFactory();
HttpContext.Current.Items["DataProviderSession"] = sessionFactory.OpenSession();
}
return (HttpContext.Current.Items["DataProvider"] as ISessionFactory);
}
}
}
private ISession session;
private ISession Session
{
get
{
if (Null.IsObjectNull(HttpContext.Current.Items["DataProviderSession"]))
{
session = SessionFactory.OpenSession();
session.FlushMode = FlushMode.Auto;
HttpContext.Current.Items["DataProviderSession"] = session;
}
else
{
session = HttpContext.Current.Items["DataProviderSession"] as ISession;
}
return session;
}
}
#endregion
#region Methods
public T Get<T>(Guid ID)
{
return Session.Get<T>(ID);
}
public T Get<T>(Expression<Func<T, bool>> predicate)
{
return Session.Query<T>().Where(predicate).FirstOrDefault();
}
public IQueryable<T> GetAll<T>()
{
return Session.Query<T>();
}
public IQueryable<T> GetAll<T>(Expression<Func<T, bool>> predicate)
{
return Session.Query<T>().Where(predicate);
}
public IQueryable<T> GetAll<T>(Expression<Func<T, bool>> predicate, int currentPage, int pageSize
)
{
if (Session.Query<T>().Any(predicate))
{
return Session.Query<T>().Where(predicate).Skip<T>(currentPage*pageSize).Take(pageSize);
}
return new List<T>().AsQueryable();
}
public IQueryable<T> GetAll<T, TKey>(Expression<Func<T, bool>> predicate, int currentPage, int pageSize,
Expression<Func<T, TKey>> sortExpression)
{
if (Session.Query<T>().Any(predicate))
{
return
Session.Query<T>().Where(predicate).Skip<T>(currentPage*pageSize).Take(pageSize).OrderBy<T, TKey>(
sortExpression);
}
return new List<T>().AsQueryable();
}
public bool Exists<T>(Guid ID)
{
if (Null.IsNotObjectNull(Session.Get<T>(ID)))
{
return true;
}
return false;
}
public bool Exists<T>(Expression<Func<T, bool>> predicate)
{
return Session.Query<T>().Where(predicate).Any();
}
public void Update<T>(T targetObject, bool commit = true) where T:class
{
try
{
BeginTransaction();
Session.Update(targetObject);
CommitTransaction(commit);
}
catch (Exception)
{
RollBackTransaction();
throw;
}
}
public void Update<T>(IEnumerable<T> targetObjects, bool commit = true) where T : class
{
try
{
BeginTransaction();
foreach (var target in targetObjects)
{
Session.Update(target);
}
CommitTransaction(commit);
}
catch (Exception)
{
RollBackTransaction();
throw;
}
}
public void Insert<T>(T targetObject, bool commit = true)
{
try
{
BeginTransaction();
Session.Save(targetObject);
CommitTransaction(commit);
}
catch (Exception)
{
RollBackTransaction();
throw;
}
}
public void Insert<T>(IEnumerable<T> targetObject, bool commit = true)
{
foreach (T target in targetObject)
{
Insert<T>(target, false);
}
CommitTransaction(commit);
}
public void Delete<T>(T targetObject, bool commit = true)
{
try
{
BeginTransaction();
Session.Delete(targetObject);
CommitTransaction(commit);
}
catch (Exception)
{
RollBackTransaction();
throw;
}
}
public void Delete<T>(Guid targetID, bool commit = true)
{
try
{
BeginTransaction();
Session.Delete(Get<T>(targetID));
CommitTransaction(commit);
}
catch (Exception)
{
RollBackTransaction();
throw;
}
}
public void Delete<T>(Expression<Func<T, bool>> predicate, bool commit = true)
{
try
{
BeginTransaction();
if (Session.Query<T>().Any(predicate))
{
foreach (T element in Session.Query<T>().Where(predicate))
{
Session.Delete(element);
}
}
CommitTransaction(commit);
}
catch (Exception)
{
RollBackTransaction();
throw;
}
}
private void RollBackTransaction()
{
transaction.Rollback();
}
private void CommitTransaction(bool commit)
{
if (commit && transaction.IsActive )
{
transaction.Commit();
}
}
private void BeginTransaction()
{
if (Session.Transaction.IsActive == false)
{
transaction =Session.BeginTransaction();
}
}
#endregion
}

The Merge, Update and SaveOrUpdate are different.
Usually update() or saveOrUpdate() are used in the following scenario:
the application loads an object in the first session
the object is passed up to the UI tier
some modifications are made to the object
the object is passed back down to the business logic tier
the application persists these modifications by calling update() in a second session
saveOrUpdate() does the following:
if the object is already persistent in this session, do nothing
if another object associated with the session has the same identifier, throw an exception
if the object has no identifier property, save() it
if the object's identifier has the value assigned to a newly instantiated object, save() it
if the object is versioned by a or , and the version property value is the same value assigned to a newly instantiated object, save() it
otherwise update() the object
and merge() is very different:
if there is a persistent instance with the same identifier currently associated with the session, copy the state of the given object onto the persistent instance
if there is no persistent instance currently associated with the session, try to load it from the database, or create a new persistent instance
the persistent instance is returned
the given instance does not become associated with the session, it remains detached
Source
You should call Merge if you want to attach a detached instance of entity to current session and persistent instance of the same (with same identifier) may already exists in current session. If you directly call Update or SaveOrUpdate on this entity, you may get NonUniqueObjectException exception.
Looking at the exception you are getting, it is obvious that the persistent instance with same identifier already exists in session; you have to call Merge for this specific case if you are willing to loose the entity already in session.
In the quote above, note that returned instance (by Merge method) is the persistent instance; not the one which was passed as parameter.
How can I generalize my repository to avoid this exception?
Too broad to answer and also opinion based. I will avoid to generalize repository this way. In fact, I will avoid generic repository with NHibernate if possible. I will instead expose both Merge and Update methods and will leave it to the user to use the correct one; but as you can see, this minimizes the use of generic repository then. That is why, I will prefer to avoid it.
Another alternative is to handle the exception like below (Caution: Not safe; I do not recommend this):
try
{
nhSession.Update(instance);
}
catch(NonUniqueObjectException)
{
instance = nhSession.Merge(instance);
}
I do not recommend this because this actually may hide the actual issue in your code. In some cases, this may produce unexpected behavior. Your changes may be lost unexpectedly as your original entity in session will be overwritten.
As you said in your answer:
Found that, I should use Merge
Again, as I said above, I will not recommend using Merge instead of Update everywhere.
because merge will decide to merge or update the entity considering its state ( detached, persistent) in NHibernate session.
This may be benefit in some cases; but in other cases, this may create issues. I have explained this above.

Found that, I should use Merge , because merge will decide to merge or update the entity considering its state ( detached, persistent) in NHibernate session.

Related

EntityFrameworkCore. Update data in database. Method does not work correctly while data is being tracked

I discovered one problem while creating my project. If someone refer to the issue I will be grateful.
In my project I use a layered model. The repository layer (data access layer) that communicates with the database (DB) and the service layer (business logic layer) in which services and objects are implemented (data transfer object).
As a result, there is a problem with the dbSet.Update method. When object (obj) comes into Update method as parameter, during the method call of the _db.Entry(dbSet.Local.FirstOrDefault(i => i.Id == obj.Id) ?? obj).State = EntityState.Modified or _db.Update(dbSet.Local.FirstOrDefault(i => i.Id == obj.Id) ?? obj) in the case of the first user's update (like from "view.xaml") obj is update and changes saving in the database (because dbSet.Local.FirstOrDefault(i => i.Id == obj.Id) returns null and obviously my obj gets into the _db.Update method). In case of a repeated user's update (in the "view.xaml" view) object obj -- when it gets into the _db.Update(dbSet.Local.FirstOrDefault(i => i.Id == obj.Id) ?? obj) method, it isn't take account, as the data context already tracks it and in the _db.Update method gets an object from dbSet.Local.FirstOrDefault(i => i.Id == obj.Id).
Everything would be fine if this object in dbSet.Local was updated according to the type that comes from the user. However, this is not the case, it is tracked but not changed when the user edits its properties. It is not tracked properly rather due to the fact that I use services and, accordingly, data transfer object entities.
In view of the foregoing, I have a question.
How to make to update entity (by a new modified object) that are tracked, or how to manually assign the necessary object in dbSet.Local to replace the one stored there? or how to make Microsoft.EntityFrameworkCore.ChangeTracking not track changes to my objects in any way?
In order to make changes not tracked I used the QueryTrackingBehavior.NoTracking parameter for the DbContextOptionsBuilder entity, but this only helps on the first load, and tracking is still used when the data is updated further.
I also used the dbSet.Local.Clear() methods, but this is a bad idea, as data updating due to the deletion of the previous data from the database (like delete 20 rows from table and add one updated).
public abstract class GenericRepository<T, TKey> : IGenericRepository<T, TKey> where T : class, IEntity, new()
{
protected DbContext context;
protected DbSet<T> dbSet;
private readonly object _lock = new object();
public GenericRepository(DbContext context)
{
this.context = context;
this.dbSet = context.Set<T>();
}
public virtual IQueryable<T> GetAll()
{
lock (_lock)
return dbSet;
}
public virtual async Task<T> GetAsync(TKey id)
{
try
{
return await dbSet.FindAsync(id);
}
catch (Exception exc)
{
throw exc;
}
}
public async Task AddAsync(T obj)
{
try
{
//dbSet.Local.Clear();
await dbSet.AddAsync(obj);
//context.Entry(obj).State = EntityState.Added;
}
catch (Exception exc)
{
throw exc;
}
}
public async void Delete(TKey id)
{
//context.Entry(obj).State = EntityState.Deleted;
//context.Remove(obj);
try
{
T obj = await GetAsync(id);
dbSet.Remove(obj);
//dbSet.Remove(dbSet.Find(1504));
}
catch (Exception exc)
{
throw exc;
}
}
public void Update(T obj)
{
try
{
context.Entry(dbSet.Local.FirstOrDefault(i => i.Id == obj.Id) ?? obj).State = EntityState.Modified;
//dbSet.Update(dbSet.Local.FirstOrDefault(i => i.Id == obj.Id) ?? obj);
//dbSet.Update(obj).State = EntityState.Modified;
//dbSet.Update(obj);
//dbSet.Local.FirstOrDefault(i => i.Id == obj.Id)
}
catch (Exception exc) { throw exc; }
}
public async Task SaveAsync()
{
try
{
await context.SaveChangesAsync();
}
catch (Exception exc)
{
throw exc;
}
}
public virtual IQueryable<T> Where(Expression<Func<T, bool>> predicate)
{
lock (_lock)
return dbSet.Where(predicate);
}
//public virtual IQueryable<T> FindBy(Expression<Func<T, bool>> predicate)
//{
// return dbSet.Where(predicate);
//}
}
}
Data saving method occurs at a higher level (service level). In the View user updates data (for example, adjusts the property one of the rows of the DataGrid), then in a context binding to this View, specifically in the ViewModel, calls the binding property (by INotifyPropertyChanged), which initiates call of the method that is responsible for the CRUD operation, then this method (On_bt_CategoryUpdate_Command) calling method from private field of genericService which is responsible for updating data in the database.
public class MainViewModel : ViewModel
{
#region Fields
private IGenericService<ContractDTO, int> _contractService;
#endregion
#region Commands
#endregion
#region Properties
private object GetList;
public object _GetList { get => GetList; private set => Set(ref GetList, value); } //Property to initialize DataGrid
private ContractDTO _contractDTO;
public ContractDTO SelectedItem { get => _contractDTO; set => Set(ref _contractDTO, value); } //Property with the data of the selected row in the DataGrid
#endregion
public SpesTechViewModel(IGenericService<ContractDTO, int> contractService)
{
_contractService = contractService;
}
#region Commands function
private bool Can_bt_CategoryUpdate_Command() => true;
private void On_bt_CategoryUpdate_Command()
{
try
{
_contractService.UpdateAsync(SelectedItem); //Method that updates and saves the data in the database
}
catch (Exception exc) { MessageBox.Show(exc.Message); }
}
#endregion
#region Function
void GetByFilter<T>(IFilterModel<T> filter, IGenericService<T, int> service) where T : class, new()
{
lock (_lock)
_GetList = service.Where(filter.Predicate()).Result.ToObservableCollection();
}
#endregion
}
_contractService.UpdateAsync(SelectedItem):
public async Task<DbObjectDTO> UpdateAsync(DbObjectDTO obj)
{
try
{
DbObject dbObject = mapper.Map<DbObject>(obj);
repository.Update(dbObject);
await repository.SaveAsync();
return mapper.Map<DbObjectDTO>(dbObject);
}
catch (Exception exc) { throw exc; }
}
the first thing in this method: using AutoMapper, the DTO object is converted into a DAL object;
second: data is update using the repository layer (code above, public void Update(T obj) method); third: saving to the database; fourth: reverse mapping.
If I'm trying to do smth like this in the Update(T obj) method of GenericRepository<T, TKey>:
public void Update(T obj)
{
try
{
dbSet.Update(obj);
//dbSet.Local.FirstOrDefault(i => i.Id == obj.Id)
}
catch (Exception exc) { throw exc; }
}
On the second update of the same Entity in dbSet with a new changes of T obj I get an exception:
{"The instance of entity type 'Contract' cannot be tracked because another instance with the same key value for {'Id'} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting key values."}
It's unusual to be so heavily coupled to the local cache. The normal pattern is to call DbSet.Find(id), and update the properties of the entity that it returns.
var entity = await dbSet.FindAsync(new object[] { id }, cancellationToken);
var entry = context.Entry(entity);
entry.CurrentValues.SetValues(obj);
await context.SaveChangesAsync(cancellationToken);
This pattern has a few benefits compared to what you are doing:
If no entity exists with that id, you have a chance to inform the caller
Entity Framework handles the caching
Entity Framework only updates the properties that have actually changed
If the entity is not in the cache, then Entity Framework does have to go to the database. In certain scenarios, e.g. if your entity has a large binary property and you plan to overwrite it with a new value, you might want to avoid loading the existing values from the database and just skip to the update part.
var local = dbSet.Local.FirstOrDefault(i => i.Id == obj.Id);
if (local != null)
{
var entry = context.Entry(local);
entry.CurrentValues.SetValues(obj);
}
else
{
var entry = dbSet.Attach(obj);
entry.State = EntityState.Modified;
}
await context.SaveChangesAsync(cancellationToken);
This is an exception to the general use case, I recommend you follow the normal pattern in most cases and only use this if performance dictates it.

EF returns ExecuteReader requires an open and available Connection. The connection's current state is open. error

I have a lot of classes with this structure as you can see:
public class OrganizationUserRepository : IOrganizationUserRepository
{
private DataContext _ctx;
public OrganizationUserRepository(DataContext ctx)
{
_ctx = ctx;
}
public bool Add(OrganizationUser entity)
{
try
{
_ctx.OrganizationUsers.Add(entity);
_ctx.SaveChanges();
return true;
}
catch (Exception ex)
{
// TODO log this error
return false;
}
}
public bool Edit(OrganizationUser entity)
{
try
{
OrganizationUser Edited = _ctx.OrganizationUsers.Where(i => i.Id == entity.Id).First();
_ctx.Entry(Edited).CurrentValues.SetValues(entity);
_ctx.SaveChanges();
return true;
}
catch (Exception ex)
{
// TODO log this error
return false;
}
}
public bool Remove(string id)
{
try
{
Int64 Id = Int64.Parse(id);
OrganizationUser obj = _ctx.OrganizationUsers.Where(i => i.Id == Id).First();
_ctx.OrganizationUsers.Remove(obj);
_ctx.SaveChanges();
return true;
}
catch (Exception ex)
{
// TODO log this error
return false;
}
}
}
The db context in constructor is injected by ninject .as you can see it just one of my classes .and i have multi classes like this in another services that use a single DB .(WCF Service).But i get this error in my wcf tracelog :
ExecuteReader requires an open and available Connection. The connection's current state is open.
I am using EF code first .
I found this Wrap DbContext db = new DbContext() inusing statement. And i want to know should i use this ,if Yes how can i change my class structure to use using in my code ?
I used public Readonly DbContext .i just remove readonly and everything work fine.

Implementing retry logic for deadlock exceptions

I've implemented a generic repository and was wondering if there is a smart way to implement a retry logic in case of a deadlock exception?
The approach should be the same for all repository methods. So is there anyway I can avoid writing 'try/catch - call method again with retry-count', in every single method?
Any suggetsion are welcome.
A bit of my Repository code:
public class GenericRepository : IRepository
{
private ObjectContext _context;
public List<TEntity> ExecuteStoreQuery<TEntity>(string commandText, params object[] parameters) where TEntity : class
{
List<TEntity> myList = new List<TEntity>();
var groupData = _context.ExecuteStoreQuery<TEntity>(commandText, parameters);
return myList;
}
public IQueryable<TEntity> GetQuery<TEntity>() where TEntity : class
{
var entityName = GetEntityName<TEntity>();
return _context.CreateQuery<TEntity>(entityName);
}
public IEnumerable<TEntity> GetAll<TEntity>() where TEntity : class
{
return GetQuery<TEntity>().AsEnumerable();
}
EDIT:
1.Solution:
Modified slightly from chris.house.00's solution
public static T DeadlockRetryHelper<T>(Func<T> repositoryMethod, int maxRetries)
{
var retryCount = 0;
while (retryCount < maxRetries)
{
try
{
return repositoryMethod();
}
catch (System.Data.SqlClient.SqlException ex)
{
if (ex.Number == 1205)// Deadlock
retryCount++;
else
throw;
}
}
return default(T);
}
And you call it like this:
public TEntity FirstOrDefault<TEntity>(Expression<Func<TEntity, bool>> predicate) where TEntity : class
{
return RetryUtility.DeadlockRetryHelper<TEntity>( () =>p_FirstOrDefault<TEntity>(predicate), 3);
}
protected TEntity p_FirstOrDefault<TEntity>(Expression<Func<TEntity, bool>> predicate) where TEntity : class
{
return GetQuery<TEntity>().FirstOrDefault<TEntity>(predicate);
}
How about something like this:
public T DeadlockRetryHelper<T>(Func<T> repositoryMethod, int maxRetries)
{
int retryCount = 0;
while (retryCount < maxRetries)
{
try
{
return repositoryMethod();
}
catch (SqlException e) // This example is for SQL Server, change the exception type/logic if you're using another DBMS
{
if (e.Number == 1205) // SQL Server error code for deadlock
{
retryCount++;
}
else
{
throw; // Not a deadlock so throw the exception
}
// Add some code to do whatever you want with the exception once you've exceeded the max. retries
}
}
}
With the above code, your retry logic is all in this method and you can just pass your repository method in as a delegate.
I know this is an old post but wanted to share an updated answer.
EF 6 now has a built-in solution, you can set the execution strategy which would be a one time implementation. You create a class that inherits from DbExectutionStrategy and overrides the ShouldRetryOn() virtual method. You can create a static class of the exceptions containing constant field valuess that are retry eligible codes and loop through each one to determine if the current sql exception being thrown matches the list of eligible retry codes...
public static class SqlRetryErrorCodes
{
public const int TimeoutExpired = -2;
public const int Deadlock = 1205;
public const int CouldNotOpenConnection = 53;
public const int TransportFail = 121;
}
public class MyCustomExecutionStrategy : DbExecutionStrategy
{
public MyCustomExecutionStrategy(int maxRetryCount, TimeSpan maxDelay) : base(maxRetryCount, maxDelay) { }
private readonly List<int> _errorCodesToRetry = new List<int>
{
SqlRetryErrorCodes.Deadlock,
SqlRetryErrorCodes.TimeoutExpired,
SqlRetryErrorCodes.CouldNotOpenConnection,
SqlRetryErrorCodes.TransportFail
};
protected override bool ShouldRetryOn(Exception exception)
{
var sqlException = exception as SqlException;
if (sqlException != null)
{
foreach (SqlError err in sqlException.Errors)
{
// Enumerate through all errors found in the exception.
if (_errorCodesToRetry.Contains(err.Number))
{
return true;
}
}
}
return false;
}
}
Finally once, you've set up your custom execution strategy, you simply create another class that inherits from DbConfiguration with a public constructor that Sets the execution strategy:
public class MyEfConfigurations : DbConfiguration
{
public MyEfConfigurations()
{
SetExecutionStrategy("System.Data.SqlClient",() => new MyCustomExecutionStrategy(5,TimeSpan.FromSeconds(10)));
}
}
EntityFramework 6 add ExecutionStrategy feature. All that is need is to setup up the strategy properly.
My retry policy:
public class EFRetryPolicy : DbExecutionStrategy
{
public EFRetryPolicy() : base()
{
}
//Keep this constructor public too in case it is needed to change defaults of exponential back off algorithm.
public EFRetryPolicy(int maxRetryCount, TimeSpan maxDelay): base(maxRetryCount, maxDelay)
{
}
protected override bool ShouldRetryOn(Exception ex)
{
bool retry = false;
SqlException sqlException = ex as SqlException;
if (sqlException != null)
{
int[] errorsToRetry =
{
1205, //Deadlock
-2, //Timeout
};
if (sqlException.Errors.Cast<SqlError>().Any(x => errorsToRetry.Contains(x.Number)))
{
retry = true;
}
}
return retry;
}
}
Tell EF to apply my strategy:
public class EFPolicy: DbConfiguration
{
public EFPolicy()
{
SetExecutionStrategy(
"System.Data.SqlClient",
() => new EFRetryPolicy());
}
}
Sources:
Implementing Connection Resiliency with Entity Framework 6
Microsoft documentation
The retry strategy will not work with user initiated transactions (transaction created with TransactionScope) as explained here. If used you will get the Error The configured execution strategy does not support user initiated transactions
The solution works though I prefer not to have to worry about the number of arguments to the Action or Func that will be retired. If you create a single retry method with a generic Action, you can handle all of the variability of the method to be called in a lambda:
public static class RetryHelper
{
public static void DeadlockRetryHelper(Action method, int maxRetries = 3)
{
var retryCount = 0;
while (retryCount < maxRetries)
{
try
{
method();
return;
}
catch (System.Data.SqlClient.SqlException ex)
{
if (ex.Number == 1205)// Deadlock
{
retryCount++;
if (retryCount >= maxRetries)
throw;
// Wait between 1 and 5 seconds
Thread.Sleep(new Random().Next(1000, 5000));
}
else
throw;
}
}
}
}
Then use it like so:
RetryHelper.DeadlockRetryHelper(() => CopyAndInsertFile(fileModel));
Have you considered some form of policy injection? You could use Unity interception, just as an example, to capture all your repository calls. Then you just write the retry logic once, in the interceptor, rather than repeating it many times in each method.
I have used the below solution provided by MiguelSlv in above post and it worked for me as expected. Its simple and easy.
EntityFramework 6 add ExecutionStrategy feature. All that is need is to setup up the strategy properly.
My retry policy:
public class EFRetryPolicy : DbExecutionStrategy
{
public EFRetryPolicy() : base()
{
}
//Keep this constructor public too in case it is needed to change defaults of exponential back off algorithm.
public EFRetryPolicy(int maxRetryCount, TimeSpan maxDelay): base(maxRetryCount, maxDelay)
{
}
protected override bool ShouldRetryOn(Exception ex)
{
bool retry = false;
SqlException sqlException = ex as SqlException;
if (sqlException != null)
{
int[] errorsToRetry =
{
1205, //Deadlock
-2, //Timeout
};
if (sqlException.Errors.Cast<SqlError>().Any(x => errorsToRetry.Contains(x.Number)))
{
retry = true;
}
}
return retry;
}
}
Tell EF to apply this policy
public class EFPolicy: DbConfiguration
{
public EFPolicy()
{
SetExecutionStrategy(
"System.Data.SqlClient",
() => new EFRetryPolicy());
}
}
Sources:
Implementing Connection Resiliency with Entity Framework 6
Microsoft documentation
The retry strategy will not work with user initiated transactions (transaction created with TransactionScope) as explained here. If used you will get the Error The configured execution strategy does not support user initiated transactions

Object Context, Repositories and Transactions

I was wondering what the best way to use transations with the entity framework.
Say I have three repositories:
Repo1(ObjectContext context)
Repo2(ObjectContext context)
Repo3(ObjectContext context)
and a service object that takes the three repositories:
Service(Repo1 repo1,Repo2 repo2, Repo3 repo3)
Serive.CreateNewObject <- calls repo1, repo2, repo3 to do stuff.
So when I create the service I create three repositories first and pass them down, each repositry takes a object context so my code looks something like this:
MyObjectContext context = new MyObjectContext();
Repo1 repo = new Repo1(context);
// etc
Now I have a controller class that is responsible for calling different services and compants of my application, showing the right forms etc. Now what I want to be able to do is wrap everything that happens in one of the controller methods in a transaction so that if some thing goes wrong I can rollback back.
The controller takes a few different Service objects, but doesn't know anything about the object context.
My questions are:
Should the context be passed in to the service layer also.
How do I implement a transaction in the controller so that anything that happens in the service
layers arn't commited untill everything has passed.
Sorry if it's a bit hard to understand..
Why doesn't your controller know about the ObjectContext?
This is where I would put it. Check out - http://msdn.microsoft.com/en-us/magazine/dd882510.aspx - here the Command is what will commit/rollback the UnitOfWork(ObjectContext).
If you don't want to have your Controller know exactly about the EF (good design) then you want to abstract your ObjectContext into an interface similar to the approach in the above link.
How about using a custom TransactionScope, one that commits when all of your services have committed?
public class TransactionScope : Scope<IDbTransaction>
{
public TransactionScope()
{
InitialiseScope(ConnectionScope.CurrentKey);
}
protected override IDbTransaction CreateItem()
{
return ConnectionScope.Current.BeginTransaction();
}
public void Commit()
{
if (CurrentScopeItem.UserCount == 1)
{
TransactionScope.Current.Commit();
}
}
}
So the transaction is only committed when the UserCount is 1, meaning the last service has committed.
The scope classes are (shame we can't do attachements...):
public abstract class Scope<T> : IDisposable
where T : IDisposable
{
private bool disposed = false;
[ThreadStatic]
private static Stack<ScopeItem<T>> stack = null;
public static T Current
{
get { return stack.Peek().Item; }
}
internal static string CurrentKey
{
get { return stack.Peek().Key; }
}
protected internal ScopeItem<T> CurrentScopeItem
{
get { return stack.Peek(); }
}
protected void InitialiseScope(string key)
{
if (stack == null)
{
stack = new Stack<ScopeItem<T>>();
}
// Only create a new item on the stack if this
// is different to the current ambient item
if (stack.Count == 0 || stack.Peek().Key != key)
{
stack.Push(new ScopeItem<T>(1, CreateItem(), key));
}
else
{
stack.Peek().UserCount++;
}
}
protected abstract T CreateItem();
public void Dispose()
{
Dispose(true);
}
protected virtual void Dispose(bool disposing)
{
if (!disposed)
{
if (disposing)
{
// If there are no users for the current item
// in the stack, pop it
if (stack.Peek().UserCount == 1)
{
stack.Pop().Item.Dispose();
}
else
{
stack.Peek().UserCount--;
}
}
// There are no unmanaged resources to release, but
// if we add them, they need to be released here.
}
disposed = true;
}
}
public class ScopeItem<T> where T : IDisposable
{
private int userCount;
private T item;
private string key;
public ScopeItem(int userCount, T item, string key)
{
this.userCount = userCount;
this.item = item;
this.key = key;
}
public int UserCount
{
get { return this.userCount; }
set { this.userCount = value; }
}
public T Item
{
get { return this.item; }
set { this.item = value; }
}
public string Key
{
get { return this.key; }
set { this.key = value; }
}
}
public class ConnectionScope : Scope<IDbConnection>
{
private readonly string connectionString = "";
private readonly string providerName = "";
public ConnectionScope(string connectionString, string providerName)
{
this.connectionString = connectionString;
this.providerName = providerName;
InitialiseScope(string.Format("{0}:{1}", connectionString, providerName));
}
public ConnectionScope(IConnectionDetailsProvider connectionDetails)
: this(connectionDetails.ConnectionString, connectionDetails.ConnectionProvider)
{
}
protected override IDbConnection CreateItem()
{
IDbConnection connection = DbProviderFactories.GetFactory(providerName).CreateConnection();
connection.ConnectionString = connectionString;
connection.Open();
return connection;
}
}
Wrap the operation in a TransactionScope.
You might want to implement the transaction model used by the Workflow Foundation. It basically has an interface that all "components" implement. After each does the main work successfully, then the host calls the "commit" method on each. If one failed, it calls the "rollback" method.

Trouble with db4o...objects aren't returned after an IIS reset/container is out of scope

So I'm probably doing something tragically wrong with db4o to cause this issue to happen...but every time I reset my context I lose all of my objects. What I mean by this is that as soon as my container object goes out of scope (due to an IIS reset or whatever) I can no longer retrieve any of the objects that I previously persisted. Within the scope of a "session" I can retrieve everything but as soon as that "session" dies then nothing is returned. What's odd is that the database file continues to grow in size so I know everything is in there it just never gets returned after the fact. Any idea what's going on?
Here is my generic wrapper for db4o:
public class DataStore : IDisposable {
private static readonly object serverLock = new object();
private static readonly object containerLock = new object();
private static IObjectServer server;
private static IObjectContainer container;
private static IObjectServer Server {
get {
lock (serverLock) {
if (server == null)
server = Db4oFactory.OpenServer(ConfigurationManager.AppSettings["DatabaseFilePath"], 0);
return server;
}
}
}
private static IObjectContainer Container {
get {
lock (containerLock) {
if (container == null)
container = Server.OpenClient();
return container;
}
}
}
public IQueryable<T> Find<T>(Func<T, bool> predicate) {
return (from T t in Container where predicate(t) select t).AsQueryable();
}
public IQueryable<T> Find<T>() {
return (from T t in Container select t).AsQueryable();
}
public ValidationResult Save(IValidatable item) {
var validationResult = item.Validate();
if (!validationResult.IsValid) return validationResult;
Container.Store(item);
return validationResult;
}
public void Delete(object item) {
Container.Delete(item);
}
public void Dispose() {
Server.Close();
Container.Close();
}
}
I think you are missing the commit statement in your Save method.
Goran

Categories

Resources