Using NHibernate interceptor together with Ninject

2019-07-26 22:45发布

问题:

I was reading this article and found it quite interesting (thanks @Aaronaught). Was what came closest to solve my problem.

The only detail is that in my case I would use the NHibernate interceptor, but an exception is thrown An unhandled exception of type 'System.StackOverflowException' occurred in System.Core.dll

Code

Session factory:

public class SessionFactoryBuilder : IProvider
{
    private ISessionFactory _sessionFactory;
    private readonly Configuration _configuration;

    public SessionFactoryBuilder(AuditInterceptor auditInterceptor)
    {
        _configuration = Fluently.Configure(new Configuration().Configure())
                .Mappings(m => m.AutoMappings.Add(AutoMap.AssemblyOf<IEntidade>(new AutomappingConfiguration())))
                .ExposeConfiguration(SetupDatabase)
                .BuildConfiguration();

        _configuration.SetInterceptor(auditInterceptor);

        _sessionFactory = _configuration.BuildSessionFactory();
    }

    private static void SetupDatabase(Configuration config)
    {
        var schema = new SchemaExport(config);
        //schema.Execute(true, true, false);
    }

    public object Create(IContext context)
    {
        return _sessionFactory;
    }

    public Type Type
    {
        get { return typeof(ISessionFactory); }
    }
}

I have a module that sets up my repositories and ORM (NHibernate)

public class RepositoriosModule : NinjectModule
{
    public override void Load()
    {
        Bind<AuditInterceptor>().ToSelf().InRequestScope();

        // NHibernate 
        Bind<ISessionFactory>().ToProvider<SessionFactoryBuilder>().InSingletonScope();
        Bind<ISession>().ToMethod(CreateSession).InRequestScope();
        Bind<NHUnitOfWork>().ToSelf().InRequestScope();

        //Model Repositories
        Bind<IRepositorio<Usuario>, IUsuariosRepositorio>().To<UsuariosRepositorio>().InRequestScope();
    }

    private ISession CreateSession(IContext context)
    {
        return context.Kernel.Get<ISessionFactory>().OpenSession();
    }
}

Interceptor to update auditable properties (CriadoEm (create at), CriadoPor (create by), AtualizadoEm and AtualizadoPor)

public class AuditInterceptor : EmptyInterceptor
{
    private readonly IUsuario _usuarioLogado;
    public AuditInterceptor(IUsuario usuarioLogado)
    {
        _usuarioLogado = usuarioLogado;
    }

    public override bool OnFlushDirty(object entity, object id, object[] currentState, object[] previousState, string[] propertyNames, NHibernate.Type.IType[] types)
    {
        var auditableObject = entity as IAuditavel;
        if (auditableObject != null)
        {
            currentState[Array.IndexOf(propertyNames, "AtualizadoEm")] = DateTime.Now;
            return true;
        }
        return false;
    }

    public override bool OnSave(object entity, object id, object[] state, string[] propertyNames, NHibernate.Type.IType[] types)
    {
        var auditableObject = entity as IAuditavel;
        if (auditableObject != null)
        {
            var currentDate = DateTime.Now;
            state[Array.IndexOf(propertyNames, "CriadoEm")] = currentDate;
            return true;
        }
        return false;
    }
}

A provider to retrieve the logged in user:

public class UsuarioProvider : Provider { private Usuario _usuario;

protected override Usuario CreateInstance(IContext context)
{
    var usuariosRepositorio = context.Kernel.Get<IUsuariosRepositorio>(); // Stackoverflow on this line!!

    if (_usuario == null && WebSecurity.IsAuthenticated)
        _usuario = usuariosRepositorio.Get(WebSecurity.CurrentUserId);
    return _usuario;
}

}

And the class NinjectWebCommon (web application) define:

private static void RegisterServices(IKernel kernel)
{
    kernel.Bind<IUsuario>().ToProvider<UsuarioProvider>().InRequestScope(); //.When((req) => WebSecurity.IsAuthenticated)
    kernel.Load(new RepositoriosModule(), new MvcSiteMapProviderModule());
}

[Add] Repository class

public class UsuariosRepositorio : Repositorio<Usuario>, IUsuariosRepositorio
{
    public UsuariosRepositorio(NHUnitOfWork unitOfWork)
        : base(unitOfWork)
    { }
}


public class Repositorio<T> :  IRepositorio<T>
    where T : class, IEntidade
{

    private readonly NHUnitOfWork _unitOfWork;
    public IUnitOfWork UnitOfWork { get { return _unitOfWork; } }
    private readonly ISession _session;

    public Repositorio(IUnitOfWork unitOfWork)
    {
        _unitOfWork = (NHUnitOfWork)unitOfWork;
        _session = _unitOfWork.Context.SessionFactory.GetCurrentSession();
    }

    public void Remover(T obj)
    {
        _session.Delete(obj);
    }

    public void Armazenar(T obj)
    {
        _session.SaveOrUpdate(obj);
    }

    public IQueryable<T> All()
    {
        return _session.Query<T>();
    }

    public object Get(Type entity, int id)
    {
        return _session.Get(entity, id);
    }

    public T Get(Expression<Func<T, bool>> expression)
    {
        return Query(expression).SingleOrDefault();
    }

    public T Get(int id)
    {
        return _session.Get<T>(id);
    }

    public IQueryable<T> Query(Expression<Func<T, bool>> expression)
    {
        return All().Where(expression);
    }
}

Problem

The problem occurs in the class UsuarioProvider while trying to retrieve the user repository.

Stackoverflow error:

An unhandled exception of type 'System.StackOverflowException' occurred in System.Core.dll

回答1:

I see two problems :

The main problem I see is that SessionFactoryBuilder needs an AuditInterceptor which needs an IUsuario, which needs a UsuarioProvider, which needs a SessionFactoryBuilder, thus introducing a cycle, and a stack-overflow.

The second problem I see is that your AuditInterceptor is linked to a request when your SessionFactoryBuilder is singleton like. I must confess I can't see how it work with several logged users.

You should instantiate and attach the AuditInterceptor as part of the CreateSession, instead of trying to create it once and for all as part of the Session builder. Once this is done, your interceptor should not rely on a Session that needs an AuditInterceptor as part of its creation (you may need a separate Session creation mechanism for that. A stateless Session might do the trick)