where is DbContextTransaction in .Net Core - c#

I need to use DbContextTransaction in a class on a .Net Standard library, I haven't been able to find the corresponding nuget package, I am porting this https://github.com/mehdime/DbContextScope/tree/master/Mehdime.Entity to .Net Core, but I only have 2 compilation errores due to missing packages/dependencies which I am not sure how to find:
Error CS0246: The type or namespace name 'MarshalByRefObject' could
not be found (are you missing a using directive or an assembly
reference?) (CS0246) (Mehdime.Entity)
Error CS0246: The type or namespace name 'DbContextTransaction' could
not be found (are you missing a using directive or an assembly
reference?) (CS0246) (Mehdime.Entity)
Code is:
/*
* Copyright (C) 2014 Mehdi El Gueddari
* http://mehdi.me
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
using System;
using System.Collections.Generic;
using System.Data;
using System.Runtime.ExceptionServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
namespace Mehdime.Entity
{
/// <summary>
/// As its name suggests, DbContextCollection maintains a collection of DbContext instances.
///
/// What it does in a nutshell:
/// - Lazily instantiates DbContext instances when its Get Of TDbContext () method is called
/// (and optionally starts an explicit database transaction).
/// - Keeps track of the DbContext instances it created so that it can return the existing
/// instance when asked for a DbContext of a specific type.
/// - Takes care of committing / rolling back changes and transactions on all the DbContext
/// instances it created when its Commit() or Rollback() method is called.
///
/// </summary>
public class DbContextCollection : IDbContextCollection
{
private Dictionary<Type, DbContext> _initializedDbContexts;
private Dictionary<DbContext, DbContextTransaction> _transactions;
private IsolationLevel? _isolationLevel;
private readonly IDbContextFactory _dbContextFactory;
private bool _disposed;
private bool _completed;
private bool _readOnly;
internal Dictionary<Type, DbContext> InitializedDbContexts { get { return _initializedDbContexts; } }
public DbContextCollection(bool readOnly = false, IsolationLevel? isolationLevel = null, IDbContextFactory dbContextFactory = null)
{
_disposed = false;
_completed = false;
_initializedDbContexts = new Dictionary<Type, DbContext>();
_transactions = new Dictionary<DbContext, DbContextTransaction>();
_readOnly = readOnly;
_isolationLevel = isolationLevel;
_dbContextFactory = dbContextFactory;
}
public TDbContext Get<TDbContext>() where TDbContext : DbContext
{
if (_disposed)
throw new ObjectDisposedException("DbContextCollection");
var requestedType = typeof(TDbContext);
if (!_initializedDbContexts.ContainsKey(requestedType))
{
// First time we've been asked for this particular DbContext type.
// Create one, cache it and start its database transaction if needed.
var dbContext = _dbContextFactory != null
? _dbContextFactory.CreateDbContext<TDbContext>()
: Activator.CreateInstance<TDbContext>();
_initializedDbContexts.Add(requestedType, dbContext);
if (_readOnly)
{
dbContext.Configuration.AutoDetectChangesEnabled = false;
}
if (_isolationLevel.HasValue)
{
var tran = dbContext.Database.BeginTransaction(_isolationLevel.Value);
_transactions.Add(dbContext, tran);
}
}
return _initializedDbContexts[requestedType] as TDbContext;
}
public int Commit()
{
if (_disposed)
throw new ObjectDisposedException("DbContextCollection");
if (_completed)
throw new InvalidOperationException("You can't call Commit() or Rollback() more than once on a DbContextCollection. All the changes in the DbContext instances managed by this collection have already been saved or rollback and all database transactions have been completed and closed. If you wish to make more data changes, create a new DbContextCollection and make your changes there.");
// Best effort. You'll note that we're not actually implementing an atomic commit
// here. It entirely possible that one DbContext instance will be committed successfully
// and another will fail. Implementing an atomic commit would require us to wrap
// all of this in a TransactionScope. The problem with TransactionScope is that
// the database transaction it creates may be automatically promoted to a
// distributed transaction if our DbContext instances happen to be using different
// databases. And that would require the DTC service (Distributed Transaction Coordinator)
// to be enabled on all of our live and dev servers as well as on all of our dev workstations.
// Otherwise the whole thing would blow up at runtime.
// In practice, if our services are implemented following a reasonably DDD approach,
// a business transaction (i.e. a service method) should only modify entities in a single
// DbContext. So we should never find ourselves in a situation where two DbContext instances
// contain uncommitted changes here. We should therefore never be in a situation where the below
// would result in a partial commit.
ExceptionDispatchInfo lastError = null;
var c = 0;
foreach (var dbContext in _initializedDbContexts.Values)
{
try
{
if (!_readOnly)
{
c += dbContext.SaveChanges();
}
// If we've started an explicit database transaction, time to commit it now.
var tran = GetValueOrDefault(_transactions, dbContext);
if (tran != null)
{
tran.Commit();
tran.Dispose();
}
}
catch (Exception e)
{
lastError = ExceptionDispatchInfo.Capture(e);
}
}
_transactions.Clear();
_completed = true;
if (lastError != null)
lastError.Throw(); // Re-throw while maintaining the exception's original stack track
return c;
}
public Task<int> CommitAsync()
{
return CommitAsync(CancellationToken.None);
}
public async Task<int> CommitAsync(CancellationToken cancelToken)
{
if (cancelToken == null)
throw new ArgumentNullException("cancelToken");
if (_disposed)
throw new ObjectDisposedException("DbContextCollection");
if (_completed)
throw new InvalidOperationException("You can't call Commit() or Rollback() more than once on a DbContextCollection. All the changes in the DbContext instances managed by this collection have already been saved or rollback and all database transactions have been completed and closed. If you wish to make more data changes, create a new DbContextCollection and make your changes there.");
// See comments in the sync version of this method for more details.
ExceptionDispatchInfo lastError = null;
var c = 0;
foreach (var dbContext in _initializedDbContexts.Values)
{
try
{
if (!_readOnly)
{
c += await dbContext.SaveChangesAsync(cancelToken).ConfigureAwait(false);
}
// If we've started an explicit database transaction, time to commit it now.
var tran = GetValueOrDefault(_transactions, dbContext);
if (tran != null)
{
tran.Commit();
tran.Dispose();
}
}
catch (Exception e)
{
lastError = ExceptionDispatchInfo.Capture(e);
}
}
_transactions.Clear();
_completed = true;
if (lastError != null)
lastError.Throw(); // Re-throw while maintaining the exception's original stack track
return c;
}
public void Rollback()
{
if (_disposed)
throw new ObjectDisposedException("DbContextCollection");
if (_completed)
throw new InvalidOperationException("You can't call Commit() or Rollback() more than once on a DbContextCollection. All the changes in the DbContext instances managed by this collection have already been saved or rollback and all database transactions have been completed and closed. If you wish to make more data changes, create a new DbContextCollection and make your changes there.");
ExceptionDispatchInfo lastError = null;
foreach (var dbContext in _initializedDbContexts.Values)
{
// There's no need to explicitly rollback changes in a DbContext as
// DbContext doesn't save any changes until its SaveChanges() method is called.
// So "rolling back" for a DbContext simply means not calling its SaveChanges()
// method.
// But if we've started an explicit database transaction, then we must roll it back.
var tran = GetValueOrDefault(_transactions, dbContext);
if (tran != null)
{
try
{
tran.Rollback();
tran.Dispose();
}
catch (Exception e)
{
lastError = ExceptionDispatchInfo.Capture(e);
}
}
}
_transactions.Clear();
_completed = true;
if (lastError != null)
lastError.Throw(); // Re-throw while maintaining the exception's original stack track
}
public void Dispose()
{
if (_disposed)
return;
// Do our best here to dispose as much as we can even if we get errors along the way.
// Now is not the time to throw. Correctly implemented applications will have called
// either Commit() or Rollback() first and would have got the error there.
if (!_completed)
{
try
{
if (_readOnly) Commit();
else Rollback();
}
catch (Exception e)
{
System.Diagnostics.Debug.WriteLine(e);
}
}
foreach (var dbContext in _initializedDbContexts.Values)
{
try
{
dbContext.Dispose();
}
catch (Exception e)
{
System.Diagnostics.Debug.WriteLine(e);
}
}
_initializedDbContexts.Clear();
_disposed = true;
}
/// <summary>
/// Returns the value associated with the specified key or the default
/// value for the TValue type.
/// </summary>
private static TValue GetValueOrDefault<TKey, TValue>(IDictionary<TKey, TValue> dictionary, TKey key)
{
TValue value;
return dictionary.TryGetValue(key, out value) ? value : default(TValue);
}
}
}

There is no unified DbContextTransaction class in Entity Framework Core. IDbContextTransaction interface is declared instead.
IDbContextTransaction transaction = context.Database.BeginTransaction();
One of its implementations is RelationalTransaction (I guess this is what you are looking for).

You can use DbContext.Database API.
var context = new DbContext();
var transaction = context.Database.BeginTransaction();
try
{
context.Table1.Add(data1);
context.SaveChanges();
context.Table2.Add(data2);
context.SaveChanges();
// Commit transaction if all commands succeed
transaction.Commit();
}
catch
{
}

Related

Understanding Entity Framework transactions and their behavior

I am using Entity Framework code-first and it's behaving very strangely when I use transactions.
In the code below you will find a simple example. As you can see - even though transactions are created - they are never committed. Despite NOT having a commit - my first transaction is being saved to the database in about 50% of unit test runs.
Sometimes data only saved in DB in 10% of runs. I have no reasonable explanation and loosing my mind trying to solve it..
Code:
[TestMethod]
public void TestConcurrentTransactions2()
{
int invoiceId = 1560;
var context1 = new ApplicationDbContext();
var transaction1 = context1.Database.BeginTransaction();
var invoice1 = new OracleDatabaseService.Models.Invoice()
{
InvoiceId = (++invoiceId).ToString(),
ClientId = "2",
ExternalSystemId = "0"
};
context1.Invoices.Add(invoice1);
context1.SaveChanges();
var context2 = new ApplicationDbContext();
var transaction2 = context2.Database.BeginTransaction();
var invoice2 = new OracleDatabaseService.Models.Invoice()
{
InvoiceId = (++invoiceId).ToString(),
ClientId = "2",
ExternalSystemId = "0"
};
context2.Invoices.Add(invoice2);
context2.SaveChanges();
//transaction2.Commit();
}
I'm using EF 6.2.0 and an Oracle-managed data access provider.
This behavior seems to be very situational and the only way to reproduce it is by re-running the test case over and over again. I seem to be able to repro the issue more often if I go and change invoiceId while the previous test hasn't finished executing. In this case - the test will still finish executing successfully but a record in DB will appear. Though in most cases, this doesn't really matter and data will be pushed to DB randomly.
So my questions are:
Is this an OK behavior that transaction is being committed automatically?
How do I force EF to only commit transactions when I need to?
Is there an alternative mechanism to handle transactions in EF?
UPDATE 23.08.2018
So, here is a code which almost certainly will repro the issue at least once per run:
[TestMethod]
public void Test1()
{
int invoiceId = 3350;
Parallel.For(0, 30, i =>
{
var context1 = new ApplicationDbContext();
var transaction1 = context1.Database.BeginTransaction();
var invoice1 = new OracleDatabaseService.Models.Invoice()
{
InvoiceId = (invoiceId + i).ToString(),
ClientId = "2",
ExternalSystemId = "0"
};
context1.Invoices.Add(invoice1);
context1.SaveChanges();
//transaction1.Commit();
});
}
Here is a fix attempt that seems to be working, though I don't fit into a service code pattern very well. I need to be able to come back to a service later and either roll-back or commit a transaction.
[TestMethod]
public void Test2()
{
int invoiceId = 3350;
Parallel.For(0, 30, i =>
{
using (var context = new ApplicationDbContext())
{
var transaction = context.Database.BeginTransaction();
var invoice = new OracleDatabaseService.Models.Invoice()
{
InvoiceId = (invoiceId + i).ToString(),
ClientId = "3",
ExternalSystemId = "0"
};
context.Invoices.Add(invoice);
context.SaveChanges();
//transaction.Commit();
}
});
}
This is my attempt to implement a service that uses DBContext. As you will see in the code - a descructor will check whether there is a context or transaction present and dispose of them. Seems to be working OK. Though, what will happen if any of the parallel processes fail? Is the descructor called then? I need somebody to review the code, possibly.
public class DbService
{
ApplicationDbContext _context;
DbContextTransaction _transaction;
public DbService()
{
_context = new ApplicationDbContext();
_transaction = _context.Database.BeginTransaction();
}
public void InsertInvoice(int invoiceId)
{
var invoice1 = new OracleDatabaseService.Models.Invoice()
{
InvoiceId = (invoiceId).ToString(),
ClientId = "3",
ExternalSystemId = "0"
};
_context.Invoices.Add(invoice1);
_context.SaveChanges();
}
~DbService()
{
if (_transaction != null)
{
_transaction.Rollback();
_transaction.Dispose();
}
if (_context != null)
{
_context.Dispose();
}
}
}
and test:
[TestMethod]
public void Test3()
{
int invoiceId = 3350;
Parallel.For(0, 30, i =>
{
var srvc = new DbService();
srvc.InsertInvoice(invoiceId + i);
});
}
As was suggested by #WynDysel in a comment section - the problem is resolvable by putting a context in a using block.
The actual reasons for the issue are still unknown to me. It looks logical, that unless something is explicitly said to be committed - to be committed. Well, I guess I have to live with this solution for now.
Perhaps I should make some clarifications about the reasons why I was not using the using block to begin with. It's because the DbContext is used from within a service. Within a service there are multiple operations being done in scope of the same transaction.
To multiple entities of database. So when the code is ready for commit - a Commit() method is executed and all of the changes done are pushed to DB at once. Otherwise if something goes wrong along the way, then all of the changes are rolled back. So for this I needed a service and normally am not allowed to use a using block by design.
To make a long story short - I will be using following service for managing context and transaction.
public class DbService : IDisposable
{
private bool _isDisposed = false;
private ApplicationDbContext _context;
private DbContextTransaction _transaction;
public DbService()
{
_context = new ApplicationDbContext();
_transaction = _context.Database.BeginTransaction();
}
public void InsertInvoice(int invoiceId)
{
try
{
var invoice1 = new OracleDatabaseService.Models.Invoice()
{
InvoiceId = (invoiceId).ToString(),
ClientId = "3",
ExternalSystemId = "0"
};
_context.Invoices.Add(invoice1);
_context.SaveChanges();
}
catch (Exception)
{
Dispose(false);
throw;
}
}
public void Commit(bool isFinal)
{
if (!_isDisposed)
{
_transaction.Commit();
if (isFinal)
{
Dispose(false);
}
else
{
_transaction.Dispose();
_transaction = _context.Database.BeginTransaction();
}
}
}
public void Rollback(bool isFinal)
{
if (!_isDisposed)
{
if (isFinal)
{
Dispose(false);
}
else
{
_transaction.Rollback();
_transaction.Dispose();
_transaction = _context.Database.BeginTransaction();
}
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!_isDisposed)
{
if (disposing)
{
// Free other state (managed objects).
}
if (_transaction != null)
{
if (_transaction.UnderlyingTransaction.Connection != null)
{
_transaction.Rollback();
}
_transaction.Dispose();
}
if (_context != null)
{
_context.Dispose();
}
_isDisposed = true;
}
}
~DbService()
{
Dispose(false);
}
}
It is still possible to use the service in a using block. If something along the way goes wrong then a destructor shall be called to roll back the transaction and dispose of a context. There are 2 helper methods for committing and rolling back chnages manually. It could either be a final commit when the service is no longer needed or a temporary commit of current transaction and initialization of a new transaction while keeping an integrity of a service.
InsertInvoice method's contexts are also wrapped in a try/catch block in case something unexpected goes wrong.
I can't afford to insert any pending transaction data on a production environment so am taking all possible precautions! Perhaps I will be asking a question on Github about this issue Entity Framework creators themselves.
Update #1
It is very unfortunate, but the code I provided above does NOT guarantee that records will not be inserted. You have to make some additional validations, when using the service.
For example, this testcase will cause the data to be inserted into database sometimes:
[TestMethod]
public void TestFail()
{
int invoiceId = 3700;
Parallel.For(0, 30, i =>
{
var srvc = new DbService();
srvc.InsertInvoice(invoiceId + i, i);
if (i > 15)
{
throw new Exception();
}
});
}
And following code will guarantee disposal of context correctly:
[TestMethod]
public void TestGood()
{
int invoiceId = 3700;
Parallel.For(0, 30, i =>
{
DbService srvc = null;
try
{
srvc = new DbService();
srvc.InsertInvoice(invoiceId + i, i);
if (i > 25)
throw new Exception();
}
catch(Exception ex)
{
if (srvc != null)
srvc.Dispose();
throw ex;
}
});
}

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.

Dbcontext has been disposed when using "using Statement"

Error message:
The operation cannot be completed because the dbcontext has been disposed.
Can somebody explain why and where my DbContext is getting disposed while I perform the update?
Context file:
using System.Data.Entity;
namespace OnlineTest
{
internal class OnlineTestContext : DbContext
{
private OnlineTestContext() : base("name=OnlineTest")
{
}
private static OnlineTestContext _instance;
public static OnlineTestContext GetInstance
{
get
{
if (_instance == null)
{
_instance = new OnlineTestContext();
}
return _instance;
}
}
public DbSet<User> Users { get; set; }
}
}
Business logic:
public int UpdateUser(User user)
{
user.ModifiedOn = DateTime.Now;
using (var context = OnlineTestContext.GetInstance)
{
context.Entry(user).State = EntityState.Modified;
return context.SaveChanges();
}
}
public User GetUserByEmailId(string emailId)
{
using (var context = OnlineTestContext.GetInstance)
{
return context.Users.First(u => u.EmailId == emailId);
}
}
Unit test:
[TestMethod]
public void UpdateUserUnitTest()
{
User user = onlineTestBusinessLogic.GetUserByEmailId("test#test");
user.PhoneNumber = "+91 1234567890";
int changes = onlineTestBusinessLogic.UpdateUser(user);
User Modifieduser = onlineTestBusinessLogic.GetUserByEmailId("test#test");
Assert.AreEqual(Modifieduser.PhoneNumber, "+91 0987654321");
}
Thank you.
It is disposed by the second time you call a method on a repository. A timeline is like that:
GetUserByEmailId is called, _instance is null, so it is initialized
GetUserByEmailId is completed, and context is disposed. But the object still exists in _instance field
UpdateUser is called, _instance is not null, so the old context is returned in using
context.SaveChanges is called, but since this object of context is already disposed, the exception is thrown
This is generally a good idea to avoid caching db context like this. Basic rule of thumb is "one context object per unit of work". You can find some more information about why is it so in this thread (starring Jon Skeet!).
The using keyword is you specifically saying "when the scope closes, call .Dispose() on the object I passed in". Which is not what you want, because you want to re-use the object over and over.
Remove the using and you will get stop getting this issue. e.g.
var context = OnlineTestContext.GetInstance;
context.Entry(user).State = EntityState.Modified;
return context.SaveChanges();

How to make objects thread safe with Nhibernate

I have multiple threads making sometimes rapid SaveOrUpdate of the same object graph using NHibernate. I use currently "session per request", at least I think I do (?). Some times I get the exception:
StaleObjectStateException
Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect):
My programs load the complete database at startup in order to maintain a complete runtime entity, and then makes only SaveOrUpdate operations, and in rare occasion a Delete operation. These are not typical user-interaction programs, but robots running on remote events like financial market data.
Are there some obvious design flaws/poor practice with this that may explain the stale states?
Repository:
public class GenericRepository<T>
{
public IList<T> GetAll()
{
using (ISession session = FnhManager.OpenSession())
{
var instances = session.CreateCriteria(typeof(T)).List<T>();
return instances;
}
}
public void SaveOrUpdate(IList<T> instances)
{
if (instances != null)
{
using (ISession session = FnhManager.OpenSession())
{
using (ITransaction transaction = session.BeginTransaction())
{
try
{
foreach (var i in instances)
{
session.SaveOrUpdate(i);
}
transaction.Commit();
}
catch (Exception ex)
{
transaction.Rollback();
Trace.TraceError("GenericRepository.SaveOrUpdate IList<" + typeof(T).ToString() + ">" , ex.ToString());
throw;
}
}
}
}
}
//...
FnhManager:
public class FnhManager
{
private static Configuration cfg;
private static ISessionFactory sessionFactory;
private static string connectionString;
private FnhManager(){}
public static ISession OpenSession()
{
return sessionFactory.OpenSession();
}
/// <summary>
/// Pass Any map class, used to locate all maps.
/// </summary>
/// <typeparam name="TAnyMap"></typeparam>
/// <param name="path"></param>
/// <param name="DbFileName"></param>
/// <remarks></remarks>
public static void ConfigureSessionFactory<TAnyMap>(string path, string DbFileName, DatabaseType type)
{
connectionString = "Data Source=" + Path.Combine(path, DbFileName);
switch (type)
{
case DatabaseType.SqlCe:
sessionFactory = CreateSessionFactorySqlCe<TAnyMap>(path,DbFileName);
break;
case DatabaseType.SQLite:
sessionFactory = CreateSessionFactorySQLite<TAnyMap>();
break;
}
}
private static ISessionFactory CreateSessionFactorySQLite<TMap>()
{
Trace.TraceInformation("Creating SessionFactory SQLite for: " + connectionString);
try
{
var fluentConfiguration = Fluently.Configure()
.Database(SQLiteConfiguration.Standard.ConnectionString(connectionString))
.Mappings(m => m.FluentMappings.AddFromAssemblyOf<TMap>()
.Conventions.Add(FluentNHibernate.Conventions.Helpers.DefaultLazy.Never()))
.ExposeConfiguration(c => cfg = c)
.Cache(c => c.UseQueryCache());
sessionFactory = fluentConfiguration.BuildSessionFactory();
return sessionFactory;
}
catch (Exception ex)
{
Trace.TraceError("Create SessionFactory Exception: " + ex.ToString());
throw;
}
}
private static ISessionFactory CreateSessionFactorySqlCe<TMap>( string dbPath, string dbName )
{
//Must add SqlCe dll x86+amd64-folders to bin folder. !!!
FileInfo f = new FileInfo(Path.Combine(dbPath, dbName));
if (!f.Exists)
{
var engine = new SqlCeEngine(connectionString);
engine.CreateDatabase();
}
var fluentConfiguration = Fluently.Configure()
.Database(MsSqlCeConfiguration.Standard.ConnectionString( s => s.Is(connectionString)))
.Mappings(m => m.FluentMappings.AddFromAssemblyOf<TMap>()
.Conventions.Add(FluentNHibernate.Conventions.Helpers.DefaultLazy.Never()))
.ExposeConfiguration(c => cfg = c)
.Cache(c => c.UseQueryCache());
sessionFactory = fluentConfiguration.BuildSessionFactory();
return sessionFactory;
}
public static void BuildDatabaseFromSchema()
{
SchemaExport e = new SchemaExport(cfg);
e.Execute(false, true, false);
}
public static void ValidateDatabase()
{
SchemaValidator validator = new SchemaValidator(cfg);
try
{
validator.Validate();
}
catch (HibernateException ex)
{
// not valid, try to update
try
{
SchemaUpdate update = new SchemaUpdate(cfg);
update.Execute(false, true);
}
catch (HibernateException e)
{
Trace.TraceError("Invalid schema. HibernateException: ", ex.ToString());
}
}
catch (Exception ex)
{
// System.Windows.Forms.MessageBox.Show("Invalid schema: Exception: (Should not occur) " + ex.ToString);
}
}
}
Sorry for my bad english and may complicated explanation.
But I try it again:
MyDomain first = MyDomainDao.Load(1);
MyDomain second = MyDomainDao.Load(1);
first.Name = "juhe";
MyDomainDao.SaveOrUpdate(first);
// Throws an exception, because the 'second' is not 'refreshed'
MyDomainDao.SaveOrUpdate(second);
If your code is like this you got this problem. This loadings can be in diffrent threads, now the object has two diffrent states within every session.
Whats about you versioning the domain object?
I do not exactly know your implementation, but try to refresh your entity:
Session.Refresh(myDomain, LockMode.None)
In the scope of object-relational mapping there are two main approaches for concurrency control, Optimistic and Pessimistic, which are usually implemented in the application data access layer.
Under Optimistic concurrency control your application doesn't expect that the same database entity will be updated simultaneously, so multiple threads are allowed to access it concurrently without any locking. However, if two threads are caught trying to update the same version of a database entity, one of them will be forced to rollback the operation, otherwise one's update would overwrite the other's.
Nhibernate's default approach is Optimistic, and in the case conflicting updates occur the StaleObjectStateException you observed is thrown.
If you find that the Optimistic concurrency control is not enough for your needs, you can use NHibernate's Pessimistic Locking mechanism to acquire an exclusive lock on a database entity for the duration of an entire transaction.
The exception occurs because you load an object twice (or more) and save the first in "State A" and the second in "State B". So, NHibernate will check if the states are the same. The Object with "State B" does no more exists (because of deletion) or in your case, it is updated!

NHibernate: Update vs Merge methods

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.

Categories

Resources