innov83r
innov83r

Reputation: 463

Ninject/NHibernate configuring multiple databases; more than one binding available when binding to provider Ninject.Web.MVC3 and .NET 4.5

I'm having an issue trying to add a second db connection into the mix.

The error:

enter image description here


NinjectConfigurator.cs

When I comment out the binding for the sybase db I don't get any errors. I've tried to put a conditional .WhenClassHas<CLASS>() on the bindings to ISessionFactory to no avail.

...
container.Bind<ISessionFactory>().ToProvider<MsSqlSessionFactoryProvider>().InSingletonScope().Named("mssql");
container.Bind<ISession>().ToProvider<MsSqlSessionProvider>().WhenClassHas<MsSqlNhibernateSessionAttribute>().InRequestScope();

container.Bind<ISessionFactory>().ToProvider<SybaseSessionFactoryProvider>().InSingletonScope().Named("sybase");
container.Bind<ISession>().ToProvider<SybaseSessionProvider>().WhenClassHas<NhibernateSessionAttribute>().InRequestScope();

container.Bind<ICurrentSessionContextAdapter>().To<CurrentSessionContextAdapter>();
...

MsSqlSessionFactoryProvider.cs

public class MsSqlSessionFactoryProvider : Provider<ISessionFactory>
{
    protected override ISessionFactory CreateInstance(IContext context)
    {
        return FluentNHibernate.Cfg.Fluently.Configure()
            .Database(MsSqlConfiguration.MsSql2012.ConnectionString(c => c.FromConnectionStringWithKey("MsSqlDbConnection")))
            .CurrentSessionContext("web")
            .Mappings(m => m.FluentMappings.AddFromAssemblyOf<SqlCommandFactory>())
            .BuildSessionFactory();
    }
}

MsSqlSessionProvider.cs

public class MsSqlSessionProvider : Provider<ISession>
{
    protected override ISession CreateInstance(IContext context)
    {
        var sessionFactory = context.Kernel.Get<ISessionFactory>("mssql");

        if (!CurrentSessionContext.HasBind(sessionFactory))
        {
            var session = sessionFactory.OpenSession();
            CurrentSessionContext.Bind(session);
        }
        return sessionFactory.GetCurrentSession();
    }
}

SybaseSessionFactoryProvider.cs

public class SybaseSessionFactoryProvider : Provider<ISessionFactory>
{
    protected override ISessionFactory CreateInstance(IContext context)
    {
        return FluentNHibernate.Cfg.Fluently.Configure()
            .Database(
                OdbcConfiguration.Sybase.ConnectionString(c => c.FromConnectionStringWithKey("dbConnection")))
            .ExposeConfiguration(c => c.SetProperty(NHibernate.Cfg.Environment.Hbm2ddlKeyWords, "none"))
            .CurrentSessionContext("web")
            .Mappings(m => m.FluentMappings.AddFromAssemblyOf<OdbcCommandFactory>())
            .BuildSessionFactory();
    }
}

SybaseSessionProvider.cs

public class SybaseSessionProvider : Provider<ISession>
{
    protected override ISession CreateInstance(IContext context)
    {
        var sessionFactory = context.Kernel.Get<ISessionFactory>("sybase");
        if (!CurrentSessionContext.HasBind(sessionFactory))
        {
            var session = sessionFactory.OpenSession();
            CurrentSessionContext.Bind(session);
        }
        return sessionFactory.GetCurrentSession();
    }
}

UPDATE:

MsSqlNhibernateSessionAttribute.cs

using System.Web.Mvc;

namespace Common
{
    public class MsSqlNhibernateSessionAttribute : ActionFilterAttribute/*, IActionFilter*/
    {
        private readonly IActionTransactionHelper _actionTransactionHelper;
        private readonly IActionExceptionHandler _actionExceptionHandler;

        public MsSqlNhibernateSessionAttribute()
            : this(WebContainerManager.Get<IActionTransactionHelper>(),
                  WebContainerManager.Get<IActionExceptionHandler>())
        {
        }

        public MsSqlNhibernateSessionAttribute(
            IActionTransactionHelper actionTransactionHelper,
            IActionExceptionHandler actionExceptionHandler)
        {
            _actionTransactionHelper = actionTransactionHelper;
            _actionExceptionHandler = actionExceptionHandler;
        }

        public override void OnActionExecuting(ActionExecutingContext actionContext)
        {
            _actionTransactionHelper.BeginTransaction();
        }

        public override void OnActionExecuted(ActionExecutedContext actionExecutedContext)
        {
            _actionTransactionHelper.EndTransaction(actionExecutedContext);
            _actionTransactionHelper.CloseSession();
            _actionExceptionHandler.HandleException(actionExecutedContext);
        }
    }
}

NhibernateSessionAttribute.cs

using System.Web.Mvc;

namespace Common
{
    public class NhibernateSessionAttribute : ActionFilterAttribute
    {
        private readonly ISybaseActionTransactionHelper _sybaseActionTransactionHelper;
        private readonly IActionExceptionHandler _actionExceptionHandler;

        public NhibernateSessionAttribute()
            : this(WebContainerManager.Get<ISybaseActionTransactionHelper>(),
                  WebContainerManager.Get<IActionExceptionHandler>())
        {
        }

        public NhibernateSessionAttribute(
            ISybaseActionTransactionHelper actionTransactionHelper,
            IActionExceptionHandler actionExceptionHandler)
        {
            _sybaseActionTransactionHelper = actionTransactionHelper;
            _actionExceptionHandler = actionExceptionHandler;
        }

        public override void OnActionExecuting(ActionExecutingContext actionContext)
        {
            _sybaseActionTransactionHelper.BeginTransaction();
        }

        public override void OnActionExecuted(ActionExecutedContext actionExecutedContext)
        {
            _sybaseActionTransactionHelper.EndTransaction(actionExecutedContext);
            _sybaseActionTransactionHelper.CloseSession();
            _actionExceptionHandler.HandleException(actionExecutedContext);
        }
    }
}

I'm injecting it into the controller and have tried to inject it into the ActionTransactionHelper classes, but when I do that the error changes to "Error activating ISessionFactory. No matching bindings were found..." I'm including the controller and one ActionTransactionHelper as they are both the same implementation (I wasn't sure if I could have a single transaction helper class or not).

Controller

using System.Web.Mvc;
using Common;
using Data.SqlServer;
using NHibernate;
using Warehouse.Actions;
using Warehouse.Actions.VendorManagement;

namespace Warehouse.Controllers
{
    [MsSqlNhibernateSession]
    public class VendorManagementController : Controller
    {
        private readonly ISession _session;

        public VendorManagementController(ISession session) 
        {
            _session = session;
        }

        // GET: Vendor
        public ActionResult Index()
        {
            ViewVendorManagementAction viewVendorManagementAction = new ViewVendorManagementAction();
            return viewVendorManagementAction.Create(_session);
        }

        public ActionResult Vendor(Vendor vendor)
        {
            ViewVendorFormAction viewVendorFormAction = new ViewVendorFormAction();
            return viewVendorFormAction.Create(Request, _session);
        }

        public ActionResult SaveVendor(Vendor vendor)
        {
            SaveVendorAction saveVendorAction = new SaveVendorAction(_session);
            var newVendor = saveVendorAction.Save(vendor);
            var returnUrl = Url.Action("Vendor") + "?Id=" + newVendor.Id;
            return Redirect(returnUrl);
        }

        public ActionResult UpdateVendor(Vendor vendor)
        {
            UpdateVendorAction updateVendorAction = new UpdateVendorAction(_session);
            updateVendorAction.Update(vendor);
            var returnUrl = Url.Action("Vendor") + "?Id=" + vendor.Id;
            return Redirect(returnUrl);
        }
    }
}

ActionTransactionHelper.cs

using System.Web.Mvc;
using NHibernate;

namespace Common
{
    public class ActionTransactionHelper : IActionTransactionHelper
    {
        private readonly ISessionFactory _sessionFactory;
        private readonly ICurrentSessionContextAdapter _currentSessionContextAdapter;

        public ActionTransactionHelper(
            ISessionFactory sessionFactory,
            ICurrentSessionContextAdapter currentSessionContextAdapter)
        {
            _sessionFactory = sessionFactory;
            _currentSessionContextAdapter = currentSessionContextAdapter;
        }

        public void BeginTransaction()
        {
            var session = _sessionFactory.GetCurrentSession();

            if (session != null)
            {
                session.BeginTransaction();
            }
        }

        public bool TransactionHandled { get; private set; }

        public void EndTransaction(ActionExecutedContext filterContext)
        {
            var session = _sessionFactory.GetCurrentSession();
            if (session == null) return;
            if (!session.Transaction.IsActive) return;

            if (filterContext.Exception == null)
            {
                session.Flush();
                session.Transaction.Commit();
            }
            else
            {
                session.Transaction.Rollback();
            }

            TransactionHandled = true;
        }

        public bool SessionClosed { get; private set; }

        public void CloseSession()
        {
            if (_currentSessionContextAdapter.HasBind(_sessionFactory))
            { 
                var session = _sessionFactory.GetCurrentSession();
                session.Close();
                session.Dispose();
                _currentSessionContextAdapter.Unbind(_sessionFactory);

                SessionClosed = true;
            }
        }
    }
}

Upvotes: 0

Views: 306

Answers (2)

innov83r
innov83r

Reputation: 463

I figured out the solution to my issue. Thanks @BatteryBackupUnit for your help. It ended up being that I had to decorate my injection of ISessionFactory in the ActionTransactionHelper's with [Named("corresponding name from NinjectConfiguration binding to ISessionFactory")].

Ninject Documentation for contextual named bindings

For Example:

Use .Named("name") in the configuration file.

container.Bind<ISessionFactory>().ToProvider<MsSqlSessionFactoryProvider>().InSingletonScope().Named("mssql");

Then when injecting it get it by using [Named("name")]

public class ActionTransactionHelper : IActionTransactionHelper
{
        private readonly ISessionFactory _sessionFactory;
        private readonly ICurrentSessionContextAdapter _currentSessionContextAdapter;

        public ActionTransactionHelper(
            [Named("mssql")] ISessionFactory sessionFactory,
            ICurrentSessionContextAdapter currentSessionContextAdapter)
        {
            _sessionFactory = sessionFactory;
            _currentSessionContextAdapter = currentSessionContextAdapter;
        }
}

Upvotes: 0

BatteryBackupUnit
BatteryBackupUnit

Reputation: 13233

The WhenClassHas<Attribute> binding checks the class X where the bound type Y is injected into for an Attribute Z.

For example, when you have a binding

Bind<IFoo>().To<Foo>().WhenClassHas<MarkerAttribute>();

and:

[Marker]
public class Bar {
    public Bar(IFoo foo) ...
}

this satifies the condition.

However, given:

public class ZZZ
{
    public ZZZ(IFoo foo) ..
}

[Marker]
public class YYY {
    public YYY(ZZZ zzz) ..
}

this does not satisfy the condition. IFoo is injected into ZZZ which does not have the [Marker] attribute.

Also, when injecting into the [Marker] attribute itself, this does not satisfy the condition as the [Marker] attribute itself does not have the [Marker] attribute on its class declaration.

Upvotes: 1

Related Questions