2

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;
        }
    }      
}
Radim Köhler
  • 122,561
  • 47
  • 239
  • 335
Alexander Talavari
  • 418
  • 2
  • 9
  • 24
  • Just a note. I tried to find what is going on with your code... but it seems to be correct. I'd suggest - show the code which throws exception. Lazy load exception is simply "common due to misunderstood settings". But in this case... setting is as should... – Radim Köhler Oct 26 '14 at 18:14
  • The exception is on purpose.Just a generic division by-zero.But whenever an exception occurs the session for a reason i don't know yet gets gets in an undefined state.That's why any other calls after that throw a lazy load exception. – Alexander Talavari Oct 26 '14 at 18:18
  • Simply, lazy load exception always means: there is a proxy, but the session is closed. For example, it seems like there is some `OnException` handling, which does close session, while redirect with loaded object... later without session. Without more of your code... it (to me) simply is not clear what is wrong. – Radim Köhler Oct 26 '14 at 18:21
  • To make it clear: For example i have /Home and /Error pages. When application starts it load /Home correctly. I navigate to /Home from another IP everthing is ok. But if i visit /Error who generates div by zeroi get the yellow error page.Then i go back to /Home and i get the no Session error on both clients. If i restart the app everything is ok until an exception happens and leaves the session in undefined state. PS Cause you mentioned error handling i have Elmah for the error logging but the behavior is same with or without it – Alexander Talavari Oct 26 '14 at 18:22
  • Look. To me it seems, like you loaded some object, and kept it referenced outside of the session boundary. Like `CurrentUser`. This is the only way, how "session/request" error could effect more then one request. If you are using session like described above... to only load objects, render them, and let them die when request ends... such error will not appear. Do you understand what I am trying to tell you? – Radim Köhler Oct 26 '14 at 18:27
  • I think i understand you. I updated with my repository implementation just in case the error it's there. The problem becomes application wide when an Exception occurs.And for all users that use the app. – Alexander Talavari Oct 26 '14 at 18:36
  • Sorry, that I do not have THE answer. Just, I see your repository is tightly coupled with session per request. In case, that such repository is used on a global level (as I said, to load some resource e.g. User or Employee) it could be problem. It could be called when session per request is called... to load more information... for handling exception. I am still repeating the same... sorry, cannot help more ;( – Radim Köhler Oct 26 '14 at 18:40
  • I added my BaseController which maybe be the problem. I really apreciate your help nonetheless.Also you have any idea to make the repository better? – Alexander Talavari Oct 26 '14 at 18:42
  • Well, I would say, that with your latest extension I could draft some solution... I believe I explained what is the issue clearly... – Radim Köhler Oct 26 '14 at 18:55
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/63674/discussion-between-alexander-talavari-and-radim-kohler). – Alexander Talavari Oct 26 '14 at 18:57

2 Answers2

1

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();
Radim Köhler
  • 122,561
  • 47
  • 239
  • 335
0

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);
Community
  • 1
  • 1
Alexander Talavari
  • 418
  • 2
  • 9
  • 24