视图异常后的Nhibernate Lazy Load异常

时间:2014-10-26 10:53:40

标签: c# asp.net session nhibernate fluent-nhibernate

我通过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.

由于错误的性质,错误是至关重要的,因为1个用户可以在整个网站生成异常时使整个网站死亡

以下是用于Nhibernate的HttpModule和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

我使用的基本控制器类

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

2 个答案:

答案 0 :(得分:1)

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

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

这很难奏效。为什么?

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

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

解?

我会使用User对象的.Clone()。在其实现中,我们可以显式加载所有需要的引用和集合,并克隆它们。这样的对象可以放入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;
    }

那么,什么会进入会话?

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

答案 1 :(得分:0)

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

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

我添加了以下方法来加载实体而不是lazy

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

并将BaseController更改为

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