NHibernate会话+使用ASP.NET MVC 3进行事务处理

3

我目前正在编写一个新的应用程序,尽管公司标准是使用NHibernate(因为这是所有项目都通用的标准),但我选择使用ASP.NET MVC 3,因为它现在已经非常成熟。我在控制器中实现了我的事务处理(这似乎是你应该这样做的方式),所以在我的根控制器中看起来像这样:

[TransactionPerRequest]
public class FbsController : Controller
{

}

然后,我所有的控制器都继承自FbsController。之所以这样做是因为我的90%操作都会访问数据库,所以为了剩下10%的操作去创建和处理事务(这些很少执行),不值得为每个操作添加[TransactionPerRequest]标记。
关于NHibernate sessions的问题一直困扰着我。在存储库类中,我有类似以下内容的代码,虽然在其他项目中可能不同:
    public void Add(User user)
    {
        using (ISession session = NHibernateHelper.OpenSession())
        {
            session.Save(user);
        }
    }

    public void Remove(User user)
    {
        using (ISession session = NHibernateHelper.OpenSession())
        {
            session.Delete(user);
        }
    }

    public User GetById(int userId)
    {
        using (ISession session = NHibernateHelper.OpenSession())
        {
            return session.QueryOver<User>()
                .Where(c => c.UserID == userId)
                .SingleOrDefault();
        }
    }

因此,对于我存储库中的大多数函数,我必须打开会话。有没有办法避免这种行为,以便我不必在每个存储库方法内部打开会话?这似乎有点违反直觉,因为我通常必须为每个函数都执行此操作。我想知道其他人在处理散布在代码中的事务和会话问题时采用了什么解决方案。
实际上,我希望我的存储库方法看起来像以下内容:
    public void Add(User user)
    {
        session.Save(user);
    }

    public void Remove(User user)
    {
        session.Delete(user);
    }

    public User GetById(int userId)
    {
        return session.QueryOver<User>()
            .Where(c => c.UserID == userId)
            .SingleOrDefault();
    }

所有事情都隐式地处理。


3个回答

10

1
这个没问题,但如果你正在使用仓储模式并需要在你的仓储中访问会话怎么办?他没有涉及到那个,会话只能在控制器中使用,因此使用 QueryOver 对数据库的请求需要从控制器中完成。 - Kieran Senior
@Kezzer,你可以编写一个包装类来封装 HttpContext.Current.Items["NHibernateSession"],然后配置你的 DI 框架以将其传递到你的仓储构造函数中。 - Darin Dimitrov
非常好的链接,正是我在寻找的。谢谢! - Tx3

4

我会做类似以下的事情:

在我的 Global.asax.cs 文件中:

public static ISessionFactory SessionFactory { get; set; }

然后在Application_Start中定义:

protected void Application_Start()
{
    AreaRegistration.RegisterAllAreas();
    RegisterRoutes(RouteTable.Routes);

    var nhConfig = new Configuration().Configure();
    SessionFactory = nhConfig.BuildSessionFactory();
}

接下来创建这个类:

[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class NHSession : ActionFilterAttribute
{
    public NHSession()
    {
        Order = 100;
    }

    protected ISessionFactory sessionFactory
    {
        get
        {
                return MvcApplication.SessionFactory;
        }
    }

    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        var session = sessionFactory.OpenSession();
        CurrentSessionContext.Bind(session);
        session.BeginTransaction();
    }

    public override void OnResultExecuted(ResultExecutedContext filterContext)
    {
        var session = CurrentSessionContext.Unbind(sessionFactory);
        if (session != null)
        {
            if (session.Transaction.IsActive)
            {
                try
                {
                    session.Transaction.Commit();
                }
                catch
                {
                    session.Transaction.Rollback();
                }
            }
            session.Close();
        }
    }
}

那么我的通用存储库大致如下:

public class Repository<T> : IRepository<T>
{
    private readonly ISessionFactory SessionFactory;
    public Repository(ISessionFactory sessionFactory)
    {
        SessionFactory = sessionFactory;
    }
    public ISession Session
    {
        get
        {
            return SessionFactory.GetCurrentSession();
        }
    }
    public T Get(long id)
    {
        return Session.Get<T>(id);
    }
}

我具体实现的仓库是:

public class CmsContentRepository : Repository<CmsContent>, ICmsContentRepository
{
    public CmsContentRepository(ISessionFactory sessionFactory) : base(sessionFactory) { }
}

另外一个需要注意的是,我会像这样装饰我的控制器:

[NHSession]
public ViewResult Revisions(int id)
{
    var model = Service.CmsContentRepository.Get(id);
    return View("Revisions", model);
}

这使我能够在请求中使用一个工作单元。基本上,当一个请求进来并开始一个会话时,SessionFactory 被传递到库的构造函数中。我在这里使用 DI,但这是可选的。如果检测到错误,则会回滚会话,否则在请求结束时提交。我建议使用 NHProf,因为它可以帮助你理解会话管理(如果没有正确设置)。


0
我使用StructureMap来自动启动一个会话,当第一次调用ISession时,然后通过HttpRequest缓存会话。这使得我可以在请求期间使用惰性加载和事务,而最少的编码麻烦。
以下是我的引导程序代码,它使用Fluent NHibernate和StructureMap为我设置了一切。
public class Bootstrapper
{
    public static ISessionFactory DBSessionFactory { get; set; }
    public static ISession DBSession { get; set; }

    public static void InitializeObjectFactory()
    {
        ObjectFactory.Initialize(x =>
                                     {
                                         x.PullConfigurationFromAppConfig = true;
                                         x.Scan(y =>
                                                    {
                                                        y.Assembly(Assembly.GetAssembly(typeof(AccountController)));
                                                        y.Assembly(Assembly.GetAssembly(typeof(IMyProject)));
                                                        y.WithDefaultConventions();
                                                    }
                                             );

                                         // these are for NHibernate
                                         x.ForRequestedType<ISessionFactory>()
                                             .CacheBy(InstanceScope.Singleton)
                                             .TheDefault.Is.ConstructedBy(GetDBSessionFactory);

                                         // open session at beginning of every http request 
                                         // (the session is disposed at end of http request in global.asax's Application_EndRequest)
                                         x.ForRequestedType<ISession>()
                                             .CacheBy(InstanceScope.HttpContext)
                                             .TheDefault.Is.ConstructedBy(GetDBSession);
                                     });
    }

    public static ISessionFactory CreateSessionFactory()
    {
        return GetFluentConfiguration()
            .BuildSessionFactory();
    }

    public static ISessionFactory GetDBSessionFactory()
    {
        if (DBSessionFactory == null)
        {
            DBSessionFactory = CreateSessionFactory();
        }
        return DBSessionFactory;
    }

    public static ISession GetDBSession()
    {
        if (DBSession == null)
        {
            DBSession = CreateSession();
        }
        return DBSession;
    }

    public static ISession CreateSession()
    {
        return GetDBSessionFactory()
            .OpenSession();
    }

    public static FluentConfiguration GetFluentConfiguration()
    {
        string commandTimeout = ConfigurationManager.AppSettings["MyDBCommandTimeout"];
        return Fluently.Configure()
            .Database(// use your db configuration )
            .Mappings(m =>
                          {
                              m.HbmMappings
                                  .AddFromAssemblyOf<MyEO>();
                              m.FluentMappings
                                  .AddFromAssemblyOf<MyEO>()
                                  .AddFromAssemblyOf<MyEOMap>();
                          })
            .ExposeConfiguration(
                cfg =>
                    {
                        // command_timeout sets the timeout for the queries
                        cfg.SetProperty("command_timeout", commandTimeout);
                    }
            );
    }
}

在全局.asax文件的Application_Start()方法中调用Bootstrapper.InitializeObjectFactory();,如下所示:
protected void Application_Start()
{
    RegisterRoutes(RouteTable.Routes);
    Bootstrapper.InitializeObjectFactory();
    ...
}

在您的Application_EndRequest()中关闭会话:
protected void Application_EndRequest()
{
    // ensure that we aren’t leaking ISessions on every web request
    if (Bootstrapper.DBSession != null)
    {
        if (Bootstrapper.DBSession.IsOpen)
        {
             Bootstrapper.DBSession.Close();
        }
        Bootstrapper.DBSession.Dispose();
        Bootstrapper.DBSession = null;
    }

    HttpContextBuildPolicy.DisposeAndClearAll();
}

现在你只需要调用

ObjectFactory.GetInstance<ISession>() 

从任何地方(我将其包装在一个辅助类中,以保持我的代码简洁)StructureMap 将为您提供缓存的会话。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接