1

我的 ASP.NET MVC 4 项目正在使用 NHibernate(存储库后面)和 Castle Windsor,使用 AutoTx 和 NHibernate 设施。我遵循了 haf 编写的指南,我可以创建和读取对象。

我的 PersistenceInstaller 看起来像这样

public class PersistenceInstaller : IWindsorInstaller
{
    public void Install(Castle.Windsor.IWindsorContainer container, Castle.MicroKernel.SubSystems.Configuration.IConfigurationStore store)
    {
        container.AddFacility<AutoTxFacility>();            
        container.Register(Component.For<INHibernateInstaller>().ImplementedBy<NHibernateInstaller>().LifeStyle.Singleton);
        container.AddFacility<NHibernateFacility>(
            f => f.DefaultLifeStyle = DefaultSessionLifeStyleOption.SessionPerWebRequest);          
    }
}

NHibernateInstaller 直接来自NHib Facility Quickstart

我在我的基础存储库中使用 ISessionManager ...

protected ISession Session
{
    get
    {
        return _sessionManager.OpenSession();
    }
}

public virtual T Commit(T entity)
{
    Session.SaveOrUpdate(entity);            
    return entity;
}

最后,导致问题的我的应用程序代码:

[HttpPost]
[ValidateAntiForgeryToken]        
[Transaction]
public ActionResult Maintain(PrescriberMaintainViewModel viewModel)
{           
    if (ModelState.IsValid)
    {
        var prescriber = UserRepository.GetPrescriber(User.Identity.Name);

        //var prescriber = new Prescriber { DateJoined = DateTime.Today, Username = "Test" };                
        prescriber.SecurityQuestion = viewModel.SecurityQuestion;
        prescriber.SecurityAnswer = viewModel.SecurityAnswer;
        prescriber.EmailAddress = viewModel.Email;
        prescriber.FirstName = viewModel.FirstName;
        prescriber.LastName = viewModel.LastName;
        prescriber.Address = new Address
                                {
                                    Address1 = viewModel.AddressLine1,
                                    Address2 = viewModel.AddressLine2,
                                    Address3 = viewModel.AddressLine3,
                                    Suburb = viewModel.Suburb,
                                    State = viewModel.State,
                                    Postcode = viewModel.Postcode,
                                    Country = string.Empty
                                };

        prescriber.MobileNumber = viewModel.MobileNumber;
        prescriber.PhoneNumber = viewModel.PhoneNumber;
        prescriber.DateOfBirth = viewModel.DateOfBirth;
        prescriber.AHPRANumber = viewModel.AhpraNumber;
        prescriber.ClinicName = viewModel.ClinicName;
        prescriber.ClinicWebUrl = viewModel.ClinicWebUrl;
        prescriber.Qualifications = viewModel.Qualifications;
        prescriber.JobTitle = viewModel.JobTitle;


        UserRepository.Commit(prescriber);          
    }

    return View(viewModel);
}

上面的代码将保存一个新的处方者(通过取消注释掉注释掉的行等来测试)。

我正在使用 NHProf 并已确认没有将 sql 发送到数据库进行更新。我可以看到正在执行的读取,仅此而已。

在我看来,NHibernate 无法识别实体被更改,因此不会生成 sql。或者可能没有提交事务?

我已经在网上搜索了几个小时,现在试图解决这个问题,作为最后的绝望行为,我已经在 SO 上发布了。有任何想法吗?:)

哦,在 NHProf 中,我看到了三个会话(1 个用于来自 repo 的 GetPrescriber 调用,一个用于更新(没有 sql)- 一个用于我对基类的 actionfilter 中的某些操作)。我还收到有关使用隐式事务的警报。这让我很困惑,因为我认为我正在做我需要做的一切来获得交易——使用 AutoTx 和 Transaction 属性。根据我的 Windsor 配置,我还希望每个 web 请求只有 1 个会话。

更新:似乎在花一天时间阅读 NHibernateFacility 和 AutoTx Facility 的源代码以进行自动交易之后,AutoTx 没有在我的 INHibernateInstaller 实现上设置拦截器。这似乎意味着每当 SessionManager 调用 OpenSession 时,它都会调用不带参数的默认版本,而不是接受拦截器的版本。AutoTxFacility 在内部向 windsor 注册 TransactionInterceptor,以便可以通过使用 AutoTx 的 TransactionalComponentInspector 的 Windsor 在我的 INHibernateInstaller 混凝土上添加拦截器

github上的AutoTxFacility源码

4

3 回答 3

2

对我来说,它看起来像是为每次调用存储库创建会话。一个会话应该跨越整个业务操作。它应该在开始时打开并在最后提交和处置。

这段代码中还有其他奇怪的东西。

  • Commit 是一个与 SaveOrUpdate 完全不同的概念。
  • 而且您也不需要告诉 NH 存储更改。您不需要为会话中已经存在的对象调用 session.Save。无论如何,它们都被存储了。您只需要在添加新对象时调用 session.Save 即可。
  • 确保您将事务用于整个业务操作。
于 2012-12-04T06:38:10.193 回答
1

上面的代码片段中有一个最有可能“意外”的部分。并通过 NHProf 的观察证明

哦,在 NHProf 中,我看到了三个会话(1 个用于来自 repo 的 GetPrescriber 调用,一个用于更新(没有 sql)- 一个用于在基类上的 actionfilter 中的某些操作)。

调用OpenSession()会触发会话实例的创建。

protected ISession Session
{
    get { return _sessionManager.OpenSession(); }
}

因此,每当代码访问该Session属性时,都会创建新的会话实例(一次又一次)。一个会话用于获取,一个用于 udpate,一个用于过滤......

正如我们在这里看到的,返回的会话SessionManager.OpenSession()必须用于整个范围(工作单元、Web 请求......)

http://docs.castleproject.org/Windsor.NHibernate-Facility.ashx

我们需要的语法,si 创建一个会话(第一次访问时)并重用它直到 enf 范围(然后正确关闭它,提交或回滚事务......)。无论如何,现在的第一件事就是以Session这种方式更改属性:

ISession _session;
protected ISession Session
{
    get 
    { 
      if (_session == null)
      {
         _session = sessionFactory.OpenSession();
      }
      return _session; 
    }
}
于 2012-12-04T07:19:22.123 回答
0

在昨天花了一整天的时间在 github 上搜索 AutoTx 和 NHibernate 工具但一无所获之后,我开始了一个干净的项目,试图复制这个问题。不幸的是,对于复制,一切正常!我在源代码上运行了 Update-Package 并关闭了新版本的 Castle.Transactions 并且我运行正常。我确实对自己的代码做了一些小的调整。那是删除 UserRepository.Commit 行。

我不需要修改我打开会话的方式。这是由 SessionManager 实例处理的。随着对 Castle.Transactions 的更新,正在识别 Transaction 属性并正在创建一个事务(NHProf 中不再有警报证明了这一点)。

于 2012-12-05T22:04:25.350 回答