视图异常后的Nhibernate Lazy Load异常

本文关键字:异常 Lazy Load Nhibernate 视图 | 更新日期: 2023-09-27 18:27:48

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

每当出现与NHibernate无关的一般异常时,即在视图中,异常抛出后的每个请求都是DivideByZeroException

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.

由于漏洞的性质,漏洞是关键的,因为如果一个用户生成异常,他可以使整个网站死亡

接下来是我的HttpModule for Nhibernate with 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<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";
}

编辑

存储库实现我使用

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
}

编辑2

基础控制器类I使用

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;
        }
    }      
}

视图异常后的Nhibernate Lazy Load异常

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

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

这很难奏效。为什么?

  • 因为我们将其放入长期运行的对象(Web会话)中
  • 通过持续时间很短的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();

正如Radim Köhler所指出的,我在Session中保存了一个懒惰加载的对象,这导致了问题。

但我想避免所有对象的串行化,我按如下方式修复了它。

我添加了以下方法来急切地加载实体,而不是懒惰的

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

并将BaseController更改为

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