[.NET][NHibernate]Transaction中讀取不到剛剛寫入的資料

很開心在上周末下班前一起和客戶端的.NET架構師解決了開發人員的NHibernate交易使用問題,好久沒用Hibernate這個老牌ORM武器了,連開保險上膛都很生疏,來筆記Hibernate問題解決,順便回憶。

客戶端開發人員的問題是在同一個Transaction中,有三個資料庫的操作,但後面的操作無法讀取到同一個Transaction先前寫入的資料。

用SQL語言來說就是..

BEGIN TRAN
--STEP01
INSERT INTO T1 VALUES ('A')

--STEP02
SELECT * FROM T1
IF @@ROWCOUNT = 0
BEGIN
PRINT '開發人員報案的錯誤'
END

--STEP03
INSERT INTO T1 VALUES ('B')

COMMIT

我們先收集了客戶使用NHibernate的配置: 出問題的環境是SQL Server 2008資料庫,在資料庫連線部分,客戶使用NHibernate的stateless Session,NHibernate的版本是4.0.3.4000。

 


模擬問題環境(SQL資料庫)

打開管理工具,建立測試用的資料庫及資料表

CREATE Database HibernateDb  
use HibernateDb
CREATE TABLE [dbo].[POKERS1](
	[ID] [int] NOT NULL,
	[NAME] [varchar](20) NULL,
	[TITLE] [varchar](10) NULL,
	[COLOR] [varchar](10) NULL)
CREATE TABLE [dbo].[POKERS2](
	[ID] [int] identity NOT NULL,
	[NAME] [varchar](20) NULL,
	[TITLE] [varchar](10) NULL,
	[COLOR] [varchar](10) NULL)

 


模擬問題環境(Visual Studio)

1.新增一個名稱為NHibernateConsole的主控台應用程式專案

2.Nuget安裝Hibernate 套件(專案按右鍵,選管理Nuget),打開NuGet package manager搜尋Hibernate,然後按一下安裝。

3.手動在專案根目錄新增Hibernate組態檔案hibernate.cfg.xml,然後在組態檔案內先加上1個mapping file的設定。

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
  <session-factory>
    <property name="connection.provider">
      NHibernate.Connection.DriverConnectionProvider
    </property>
    <property name="connection.driver_class">
      NHibernate.Driver.SqlClientDriver
    </property>
    <property name="connection.connection_string">
      Server=STANLEY14\SQL2014;database=HibernateDb;Integrated Security=SSPI;
    </property>
    <property name="dialect">
      NHibernate.Dialect.MsSql2012Dialect
    </property>
    <property name="show_sql">true</property>
    <property name="format_sql">true</property>
    <property name="use_sql_comments">true</property>
    <!-- Mapping files -->
    <mapping file="Mappings\Poker1.hbm.xml"  />
  </session-factory>
</hibernate-configuration>

 

4.新增Models資料夾,並建立名稱為Poker1.cs的Model

namespace NHibernateConsole.Models
{
    public class Poker1
    {
        public virtual int Id { get; set; }
        public virtual string Name { get; set; }
        public virtual string Title { get; set; }
        public virtual string Color { get; set; }
   }
}

 

5.新增Mappings資料夾,並建立名稱為Poker1.hbm.xml的XML文件

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" auto-import="true" assembly="NHibernateConsole" namespace="NHibernateConsole.Models">
  <class name="Poker1" table="Pokers1" dynamic-update="true" >
    <cache usage="read-write"/>
    <id name="Id" column="Id" type="int">
    </id>
    <property name="Name"  />
    <property name="Title" />
    <property name="Color" />
  </class>
</hibernate-mapping>

 

6.檔案屬性設定為一律複製

7.新增NhibernateSession.cs類別處理db session

using NHibernate;
using NHibernate.Cfg;
namespace NHibernateConsole
{
    public class NHibernateSession
    {
        public static ISession OpenSession()
        {
            var configuration = new Configuration();
            configuration.Configure();
            ISessionFactory sessionFactory = configuration.BuildSessionFactory();
            return sessionFactory.OpenSession();
        }
        public static IStatelessSession OpenStateLessSession()
        {
            var configuration = new Configuration();
            configuration.Configure();
            ISessionFactory sessionFactory = configuration.BuildSessionFactory();
            return sessionFactory.OpenStatelessSession();
        }
    }
}

 

8.打開program.cs

static void Main(string[] args)
{
    using (IStatelessSession session = NHibernateSession.OpenStateLessSession())
    {
        using (ITransaction transaction = session.BeginTransaction())
        {
            Poker1 p1 = new Poker1 { Id = 1, Name = "Elsa", Title = "Princess", Color = "Disney" };
            session.Insert(p1);

            Poker1 p2 = new Poker1 { Id = 2, Name = "Belle", Title = "Princess", Color = "Disney" };
           session.Insert(p2);

            //開發人員報案事發地點
            var result = session.QueryOver<Poker1>().Where(x => x.Title == "Princess").List();

            Console.WriteLine($"查詢筆數:{result.Count}");
            foreach (var item in result)
            {
                Console.WriteLine("公主:" + item.Name);
            }

            Poker1 p3 = new Poker1 { Id = 3, Name = "Anna", Title = "Princess", Color = "Disney" };
            session.Insert(p3);

            transaction.Commit();
        }
    }
    Console.WriteLine("寫入成功");
    Console.ReadKey();
}

經過了一些步驟之後,可以來測試了~~~

 


測試

偵錯中斷點先設定在program.cs 的foreach哪一行,顯示的資料集合筆數果然是0筆資料

診斷工具(intellitrace事件)只偵測到一次ADO.NET 的事件(Reader)

 


問題原因

後來發現,因為使用了stateless session,預設的batch size是20,她會等到載滿人之後才開車,也是一種調校系統整批寫入或更新效能的方法,但可能客戶這次的情境不適合。

 


解決辦法

在program.cs建立stateless sesssion之後,加上這一行

session.SetBatchSize(1);

或是hibernate.cfg.xml加入adonet.batch_size屬性

<property name="adonet.batch_size">1</property>

 

再重新偵錯後,程式查出剛剛寫進去但尚未commit的2筆資料! 結案~

 

診斷工具(intellitrace事件)偵測到3次ADO.NET 的事件(2次insert,1次Reader)

 

Hibernate是一個Java語言處理ORM的解決方案,2002年就出道了,而且很快有了.NET的兄弟版叫NHibernate,很多從Java轉過來.NET的工程師很自然的選擇她,一種她鄉遇故知的概念。

2008年之後,微軟也推出了Entity Framework (全名ADO.NET Entity Framework) ,她是微軟以 ADO.NET 為基礎所發展出來的物件關聯對應 (O/R Mapping) 解決方案,包含在 Visual Studio 2008 Service Pack 1 以及 .NET Framework 3.5 Service Pack 1 中發表。

 


參考

Hibernate wiki

Entity Framework wiki

http://nhibernate.info/doc/nh/en/index#batch-inserts