Reputation: 418
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.
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";
}
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
}
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;
}
}
}
Upvotes: 2
Views: 2694
Reputation: 418
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);
Upvotes: 0
Reputation: 123901
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?
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();
Upvotes: 1