视图exception后的Nhibernate Lazy Loadexception

我用NHibernate和Fluent配置得到一个奇怪的行为。

每当发生与NHibernate无关的genericsexception时,即在视图中, DivideByZeroException抛出exception后的每个请求。

 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. 

由于bug的性质,bug是至关重要的,因为如果1个用户生成exception,则可以使整个网站死亡

接下来是我的HttpModule for Nhibernate和Asp.Net MVC 5来处理会话。

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() .Cache(c => c.ProviderClass().UseQueryCache()) .Mappings(m => m.FluentMappings.AddFromAssemblyOf() .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"; } 

编辑

我使用的存储库实现

 public class Repository : IIntKeyedRepository where T : class { private readonly ISession _session; public Repository() { _session = NHibernateSessionPerRequest.GetCurrentSession(); } #region IRepository Members public bool Add(T entity) { _session.Save(entity); return true; } public bool Add(System.Collections.Generic.IEnumerable 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 entities) { foreach (T entity in entities) { _session.Delete(entity); } return true; } #endregion #region IIntKeyedRepository Members public T FindBy(int id) { return _session.Get(id); } #endregion #region IReadOnlyRepository Members public IQueryable All() { return _session.Query(); } public T FindBy(System.Linq.Expressions.Expression<System.Func> expression) { return FilterBy(expression).Single(); } public IQueryable FilterBy(System.Linq.Expressions.Expression<System.Func> expression) { return All().Where(expression).AsQueryable(); } #endregion } 

编辑2

我使用的基本控制器类

 public class BaseController : Controller { private readonly IRepository _userRepository; public BaseController() { _userRepository = new Repository(); BaseModel = new LayoutModel {Modals = new List()}; } 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; } } } 

扩展的问题和所有片段 – 最终有助于找出问题所在。

有一个非常大的问题: Session["User"] = LoggedUser;

这很难奏效。 为什么?

  • 因为我们放入长时间运行的对象(Web Session)
  • 通过非常持久的Web请求加载的实例

当我们将LoggedUser放入会话时,并非所有属性都将被加载。 它可能只是一个根实体,有许多代理表示引用和集合。 这些将永远不会被加载,因为它的Mather会话已关闭…已经消失

解?

我会使用User对象的.Clone() 。 在其实现中,我们可以显式加载所有需要的引用和集合,并克隆它们。 这样的对象可以放入Web会话中

 [Serializable] public class User, ICloneable, ... { ... public override object Clone() { var entity = base.Clone() as User; entity.Role = Role.Clone() as Role; ... return entity; } 

那么,什么会进入会话?

 Session["User"] = LoggedUser.Clone(); 

正如RadimKöhler所说,我在Session中保存了一个延迟加载的对象导致了这个问题。

但我想避免所有对象的Serilization,我修复如下。

我添加了以下方法来急切加载实体而不是延迟

 public T FindByEager(int id) { T entity = FindBy(id); NHibernateUtil.Initialize(entity); return entity; } 

并将BaseController更改为

 if (Session != null) Session["User"] = userRepository.FindByEager(LoggedUser.Id);