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!
Related
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.
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
{
}
I'm using Fluent nHibernate and Oracle database. I's working fine, but there is one problem, that i can't figure out.
I'm trying to create new session, after previos has been killed. But there is no new session in session list in pl/sql developer and query in second session throw exception like in first one. So my question is what i'm doing wrong or what i'm missing.
public static class FluentNHibernateHelper
{
private static ISessionFactory _sessionFactory;
public static ISessionFactory SessionFactory
{
get
{
if (_sessionFactory == null)
{
var dbConfig = OracleDataClientConfiguration.Oracle10
.ConnectionString(c => c.Is(RmsConnection.ConnectionString))
.ShowSql()
.FormatSql()
.Driver<OracleDataClientDriver>();
_sessionFactory = Fluently.Configure()
.Database(dbConfig)
.Mappings(m => m.FluentMappings.AddFromAssemblyOf<RmsDu.Data.Model.MessageHead>())
.BuildSessionFactory();
}
return _sessionFactory;
}
}
/// <summary>
/// Open new db session
/// </summary>
/// <returns></returns>
public static ISession OpenSession()
{
return SessionFactory.OpenSession();
}
}
[Test]
public void SessionKillTest()
{
try
{
using (var session = FluentNHibernateHelper.OpenSession())
{
var q = session.Query<Data.Model.MessageType>();
//Here we kill first session
q.ToList();
}
}
catch (Exception ex) {}
using (var session = FluentNHibernateHelper.OpenSession())
{
var q = session .Query<Data.Model.MessageType>();
q.ToList();
}
}
The answer is that you have to dispose session.Connection manually before session dispose
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.
I have a problem with some simple code, I'm refactoring some existing code from LINQ to SQL to the Entity Framework. I'm testing my saves and deletes, and the delete is really bugging me:
[TestMethod]
public void TestSaveDelete()
{
ObjectFactory.Initialize(x =>
{
x.For<IArticleCommentRepository>().Use<ArticleCommentRepository>();
});
PLArticleComment plac = new PLArticleComment();
plac.Created = DateTime.Now;
plac.Email = "myemail";
plac.Name = "myName";
plac.Text = "myText";
plac.Title = "myTitle";
IArticleCommentRepository acrep = ObjectFactory.GetInstance<IArticleCommentRepository>();
try
{
PortalLandEntities ple = new PortalLandEntities();
int count = ple.PLArticleComment.Count();
acrep.Save(plac);
Assert.AreEqual(ple.PLArticleComment.Count(), count + 1);
//PLArticleComment newPlac = ple.PLArticleComment.First(m => m.Id == plac.Id);
//ple.Attach(newPlac);
acrep.Delete(plac);
Assert.AreEqual(ple.PLArticleComment.Count(), count + 1);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
Every time i try to run this code, I get an exception in the delete statement, telling me that its not contained within the current ObjectStateManager.Please note that both my Save and delete looks like this:
public void Delete(PLCore.Model.PLArticleComment comment)
{
using (PortalLandEntities ple = Connection.GetEntityConnection())
{
ple.DeleteObject(comment);
ple.SaveChanges();
}
}
public void Save(PLCore.Model.PLArticleComment comment)
{
using (PortalLandEntities ple = Connection.GetEntityConnection())
{
ple.AddToPLArticleComment(comment);
ple.SaveChanges();
}
}
and the connection thingy:
public class Connection
{
public static PortalLandEntities GetEntityConnection()
{
return new PortalLandEntities();
}
}
Any ideas on what i could do to make it work?
You cannot load an entity from one ObjectContext (in your case, an ObjectContext is an instance of PortalLandEntities) and then delete it from another ObjectContext, unless you detach it from the first and attach it to the second. Your life will be much, much simpler if you use only one ObjectContext at a time. If you cannot do that, you must manually Detach and then Attach first, all the while keeping track of which entities are connected to which ObjectContext.
How to use DI with your Connection : make it non-static.
public class Connection
{
private PortalLandEntities _entities;
public PortalLandEntities GetEntityConnection()
{
return _entities;
}
public Connection(PortalLandEntities entities)
{
this._entities = entities;
}
}
Then use a DI container per request. Most people do this via a controller factory.