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
Related
I have a problem with save more than one items to database in c# asp.net with fluent nhibernate. I have :
public static readonly ISessionFactory SessionFactory = DbContext.SessionFactory();
public static readonly ISession Session = SessionFactory.OpenSession();
public static readonly ITransaction Transaction = Session.BeginTransaction();
public IEnumerable<Candidate> Candidates => Session.Query<Candidate>();
public Candidate GetUser(int id)
{
return Session.Query<Candidate>().FirstOrDefault(x => x.Id == id);
}
public void AddCandidate(Candidate candidate)
{
try
{
Session.Save(candidate);
Transaction.Commit();
}
catch (Exception exception)
{
throw;
}
}
And the error is : IsolationLevel = '((NHibernate.Transaction.AdoTransaction)Transaction).IsolationLevel' threw an exception of type 'System.NullReferenceException'
More information : can't access the deleted object
My Class DbContext:
public static class DbContext
{
private static ISessionFactory _sessionFactory;
static DbContext()
{
GetFactory();
}
public static void GetFactory()
{
var myEntities = new[]
{
typeof (ApplicationUser)
};
var configuration = Fluently.Configure()
.Database(MsSqlConfiguration.MsSql2012.ConnectionString(c => c.FromConnectionStringWithKey("ConnectionString"))
.ShowSql())
.Mappings(m => m.FluentMappings.AddFromAssembly(Assembly.GetExecutingAssembly()))
.ExposeConfiguration(x =>
{
x.SetProperty("", "");
x.AddDeserializedMapping(MappingHelper.GetIdentityMappings(myEntities), null);
})
.ExposeConfiguration(BuildSchema);
_sessionFactory = configuration.BuildSessionFactory();
}
public static ISessionFactory SessionFactory()
{
return _sessionFactory;
}
public static ISession GetSession()
{
if (!CurrentSessionContext.HasBind(_sessionFactory))
CurrentSessionContext.Bind(_sessionFactory.OpenSession());
return _sessionFactory.GetCurrentSession();
}
public static ISession MakeSession()
{
return _sessionFactory.OpenSession();
}
//public static IUserStore<ApplicationUser, string> Users => new IdentityStore(MakeSession());
private static void BuildSchema(Configuration config)
{
new SchemaUpdate(config)
.Execute(true, true);
}
}
Please, help!
Thanks!
SessionFactory should be singleton and using it as static can be part of the solution. Objects from ISession and ITransaction should be created when necessary. Try change your code to something like this:
public static readonly ISessionFactory SessionFactory = DbContext.SessionFactory();
public Candidate GetUser(int id)
{
Candidate candidate = null;
using (var session = SessionFactory.OpenSession())
{
candidate = Session.Query<Candidate>().FirstOrDefault(x => x.Id == id);
}
return candidate;
}
public void AddCandidate(Candidate candidate)
{
using (var session = SessionFactory.OpenSession())
{
using (var transaction = session.BeginTransaction())
{
try
{
Session.Save(candidate);
transaction.Commit();
}
catch
{
transaction.RollBack();
throw;
}
}
}
}
Avoid to expose Queryable as your code is doing on the Candidates property.
There are some approaches to rightly implement the session management on an application and depending whant environment code is executed it can change.
I recommend you to see these posts:
http://benfoster.io/blog/yet-another-session-per-request-post
How to implement session-per-request pattern in asp.net mvc with Nhibernate
I get a weird behavior with NHibernate with Fluent Configuration.
Whenever a generic exception unrelated to the NHibernate occurs i.e. in the view a DivideByZeroException every request after the exception throws.
An exception of type 'NHibernate.LazyInitializationException' occurred in NHibernate.dll but was not handled in user code. Additional information: Initializing[Entity]-Could not initialize proxy - no Session.
Due to nature of the bug the bug is critical due to the fact that 1 user can make the whole website dead if he generates an exception
Following it is my HttpModule for Nhibernate with Asp.Net MVC 5 that takes care of sessions.
NHibernateSessionPerRequest.cs
public class NHibernateSessionPerRequest : IHttpModule
{
private static readonly ISessionFactory SessionFactory;
// Constructs our HTTP module
static NHibernateSessionPerRequest()
{
SessionFactory = CreateSessionFactory();
}
// Initializes the HTTP module
public void Init(HttpApplication context)
{
context.BeginRequest += BeginRequest;
context.EndRequest += EndRequest;
}
// Disposes the HTTP module
public void Dispose() { }
// Returns the current session
public static ISession GetCurrentSession()
{
return SessionFactory.GetCurrentSession();
}
// Opens the session, begins the transaction, and binds the session
private static void BeginRequest(object sender, EventArgs e)
{
ISession session = SessionFactory.OpenSession();
session.BeginTransaction();
CurrentSessionContext.Bind(session);
}
// Unbinds the session, commits the transaction, and closes the session
private static void EndRequest(object sender, EventArgs e)
{
ISession session = CurrentSessionContext.Unbind(SessionFactory);
if (session == null) return;
try
{
session.Transaction.Commit();
}
catch (Exception)
{
session.Transaction.Rollback();
throw;
}
finally
{
session.Close();
session.Dispose();
}
}
// Returns our session factory
private static ISessionFactory CreateSessionFactory()
{
if (HttpContext.Current != null) //for the web apps
_configFile = HttpContext.Current.Server.MapPath(
string.Format("~/App_Data/{0}", CacheFile)
);
_configuration = LoadConfigurationFromFile();
if (_configuration == null)
{
FluentlyConfigure();
SaveConfigurationToFile(_configuration);
}
if (_configuration != null) return _configuration.BuildSessionFactory();
return null;
}
// Returns our database configuration
private static MsSqlConfiguration CreateDbConfigDebug2()
{
return MsSqlConfiguration
.MsSql2008
.ConnectionString(c => c.FromConnectionStringWithKey("MyConnection"));
}
// Updates the database schema if there are any changes to the model,
// or drops and creates it if it doesn't exist
private static void UpdateSchema(Configuration cfg)
{
new SchemaUpdate(cfg)
.Execute(false, true);
}
private static void SaveConfigurationToFile(Configuration configuration)
{
using (var file = File.Open(_configFile, FileMode.Create))
{
var bf = new BinaryFormatter();
bf.Serialize(file, configuration);
}
}
private static Configuration LoadConfigurationFromFile()
{
if (IsConfigurationFileValid == false)
return null;
try
{
using (var file = File.Open(_configFile, FileMode.Open))
{
var bf = new BinaryFormatter();
return bf.Deserialize(file) as Configuration;
}
}
catch (Exception)
{
return null;
}
}
private static void FluentlyConfigure()
{
if (_configuration == null)
{
_configuration = Fluently.Configure()
.Database(CreateDbConfigDebug2)
.CurrentSessionContext<WebSessionContext>()
.Cache(c => c.ProviderClass<SysCacheProvider>().UseQueryCache())
.Mappings(m => m.FluentMappings.AddFromAssemblyOf<EntityMap>()
.Conventions.Add(DefaultCascade.All(), DefaultLazy.Always()))
.ExposeConfiguration(UpdateSchema)
.ExposeConfiguration(c => c.Properties.Add("cache.use_second_level_cache", "true"))
.BuildConfiguration();
}
}
private static bool IsConfigurationFileValid
{
get
{
var ass = Assembly.GetAssembly(typeof(EntityMap));
var configInfo = new FileInfo(_configFile);
var assInfo = new FileInfo(ass.Location);
return configInfo.LastWriteTime >= assInfo.LastWriteTime;
}
}
private static Configuration _configuration;
private static string _configFile;
private const string CacheFile = "hibernate.cfg.xml";
}
Edit
The Repository Implementation i use
public class Repository<T> : IIntKeyedRepository<T> where T : class
{
private readonly ISession _session;
public Repository()
{
_session = NHibernateSessionPerRequest.GetCurrentSession();
}
#region IRepository<T> Members
public bool Add(T entity)
{
_session.Save(entity);
return true;
}
public bool Add(System.Collections.Generic.IEnumerable<T> items)
{
foreach (T item in items)
{
_session.Save(item);
}
return true;
}
public bool Update(T entity)
{
_session.Update(entity);
return true;
}
public bool Delete(T entity)
{
_session.Delete(entity);
return true;
}
public bool Delete(System.Collections.Generic.IEnumerable<T> entities)
{
foreach (T entity in entities)
{
_session.Delete(entity);
}
return true;
}
#endregion
#region IIntKeyedRepository<T> Members
public T FindBy(int id)
{
return _session.Get<T>(id);
}
#endregion
#region IReadOnlyRepository<T> Members
public IQueryable<T> All()
{
return _session.Query<T>();
}
public T FindBy(System.Linq.Expressions.Expression<System.Func<T, bool>> expression)
{
return FilterBy(expression).Single();
}
public IQueryable<T> FilterBy(System.Linq.Expressions.Expression<System.Func<T, bool>> expression)
{
return All().Where(expression).AsQueryable();
}
#endregion
}
Edit 2
The base controller class I use
public class BaseController : Controller
{
private readonly IRepository<UserEntity> _userRepository;
public BaseController()
{
_userRepository = new Repository<UserEntity>();
BaseModel = new LayoutModel {Modals = new List<string>()};
}
public UserEntity LoggedUser { get; set; }
public LayoutModel BaseModel { get; set; }
protected override void OnActionExecuting(ActionExecutingContext ctx)
{
base.OnActionExecuting(ctx);
if (HttpContext.User.Identity.IsAuthenticated)
{
if (Session != null && Session["User"] != null)
{
LoggedUser = (User) Session["User"];
}
var curUsername = HttpContext.User.Identity.Name;
if (LoggedUser == null || LoggedUser.Entity2.un!= curUsername)
{
LoggedUser = _userRepository.FindBy(u => u.Entity2.un== curUsername);
Session["User"] = LoggedUser;
}
BaseModel.LoggedUser = LoggedUser;
BaseModel.Authenticated = true;
}
else
{
LoggedUser = new UserEntity
{
Entity= new Entity{un= "Guest"},
};
BaseModel.LoggedUser = LoggedUser;
}
}
}
The extended question and all the snippets - are finally helping to find out where is the issue.
There is a really big issue: Session["User"] = LoggedUser;
This would hardly work. Why?
because we place into long running object (Web Session)
an instance loaded via very shortly lasting Web Request
Not all its properties will/could be loaded, When we place LoggedUser into session. It could be just a root entity with many proxies representing references and collections. These will NEVER be loaded later, because its Mather session is closed... gone
Solution?
I would use .Clone() of the User object. In its implementation we can explicitly load all needed references and collections and clone them as well. Such object could be placed into the Web Session
[Serializable]
public class User, ICloneable, ...
{
...
public override object Clone()
{
var entity = base.Clone() as User;
entity.Role = Role.Clone() as Role;
...
return entity;
}
So, what would be placed into session?
Session["User"] = LoggedUser.Clone();
As Radim Köhler noted i was saving a lazy-loaded object in Session that caused the problem.
But i wanted to avoid the Serilization of all objects and i fixed it as follows.
I added the following method to eager-load an entity instead of lazy
public T FindByEager(int id)
{
T entity = FindBy(id);
NHibernateUtil.Initialize(entity);
return entity;
}
And changed BaseController to
if (Session != null) Session["User"] = userRepository.FindByEager(LoggedUser.Id);
I am trying to setup a Session factory with Nhibernate code by mapping, but I have issues configuring it, and its pretty hard to find guides to Code by mapping with the session factory.
Atm. I have this SessionManager, but I am uncertain where to specify its a MySQL database, proberly miss more.
public class SessionManager
{
private const string ConnString = "Server=localhost; Port=3306; Database=test; Uid=root; Pwd=123456;";
public static SessionManager CurrentInstance
{
get
{
if (_currentInstance == null)
{
object sync = new object();
lock (sync)
_currentInstance = new SessionManager();
}
return _currentInstance;
}
}
public static ISession Session
{
get
{
if (_sessionFactory == null)
{
object sync = new object();
lock (sync)
_sessionFactory = new Configuration()
.DataBaseIntegration(x => x.ConnectionString = ConnString)
.Configure()
.AddAssembly(typeof(EmployeeMap).Assembly)
.BuildSessionFactory();
}
return _sessionFactory.OpenSession();
}
}
private SessionManager() { }
static SessionManager _currentInstance;
static ISessionFactory _sessionFactory;
}
I think what you are trying to do is specify that you are using mysql. When I have done this I have used an NHibernate confiuration file with a line stating the driver_class:
<property name="connection.driver_class">NHibernate.Driver.MySqlDataDriver</property>
Try this tutorial
http://nhforge.org/wikis/howtonh/your-first-nhibernate-based-application.aspx
it describes the process of setting up your nhibernate session and shows a sample hibernate.cfg.xml file. In this set up you need to specify the MySqlDataDriver instead of the SQLServerCeDriver shown.
I don't know if you can do this without having to use a hibernate.cfg.xml
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!
For a web application, it seems like a good way to handle the session is to use the setting <property name="current_session_context_class">managed_web</property>, call CurrentSessionContext.Bind/Unbind on Begin/EndRequest. Then I can just use sessionFactory.GetCurrentSession() in the repository class.
This works fine for all page request. But I have background workers doing stuff and using the same repository classes to do stuff. These do not run within a web request, so that session handling won't work.
Any suggestions to how this can be solved?
I solved it by creating my own session context class:
public class HybridWebSessionContext : CurrentSessionContext
{
private const string _itemsKey = "HybridWebSessionContext";
[ThreadStatic] private static ISession _threadSession;
// This constructor should be kept, otherwise NHibernate will fail to create an instance of this class.
public HybridWebSessionContext(ISessionFactoryImplementor factory)
{
}
protected override ISession Session
{
get
{
var currentContext = ReflectiveHttpContext.HttpContextCurrentGetter();
if (currentContext != null)
{
var items = ReflectiveHttpContext.HttpContextItemsGetter(currentContext);
var session = items[_itemsKey] as ISession;
if (session != null)
{
return session;
}
}
return _threadSession;
}
set
{
var currentContext = ReflectiveHttpContext.HttpContextCurrentGetter();
if (currentContext != null)
{
var items = ReflectiveHttpContext.HttpContextItemsGetter(currentContext);
items[_itemsKey] = value;
return;
}
_threadSession = value;
}
}
}
I've found it simplest in this scenario to handle session creation myself using a DI library and 'hybrid' scope (in StructureMap, this is defined as InstanceScope.Hybrid). This will scope instances by HttpContext in an ASP.net app domain, and ThreadStatic in a normal app domain, allowing you to use the same approach in both.
I'm sure other DI libraries offer a similar feature.
On my project, I wrote a little wrapper class around the CurrentSessionContext.
Perhaps you can extend it to suit your needs.
I think you just need to tweak the implementation of BindSessionToRequest and GetCurrentSession:
public static class SessionManager
{
private static ISessionFactory _sessionFactory = null;
private static ISessionFactory SessionFactory
{
get
{
if (_sessionFactory == null)
{
//check whether we're in web context or win context, and create the session factory accordingly.
if (System.Web.HttpContext.Current != null)
{
if (_sessionFactory == null)
{
_sessionFactory = DAOBase.GetSessionFactory();
}
}
else
{
_sessionFactory = DAOBase.GetSessionFactoryForWin();
}
}
return _sessionFactory;
}
}
public static void BindSessionToRequest()
{
ISession session = SessionManager.SessionFactory.OpenSession();
NHibernate.Context.CurrentSessionContext.Bind(session);
}
public static bool CurrentSessionExists()
{
return NHibernate.Context.CurrentSessionContext.HasBind(SessionFactory);
}
public static void UnbindSession()
{
ISession session = NHibernate.Context.CurrentSessionContext.Unbind(SessionManager.SessionFactory);
if (session != null && session.IsOpen)
{
session.Close();
}
}
public static ISession GetCurrentSession()
{
return SessionFactory.GetCurrentSession();
}
}