2013-03-21 37 views
0

使這個測試應用程序同時運行的最佳方式是什麼?Nhibernate和Concurrency

當我的程序完成後,控制檯應用程序將打印員工數爲4057。它應該是20000,因爲我有20線程遞增員工計數1000次。

Program.cs的

using System; 
using System.Data; 
using System.Diagnostics; 
using System.Threading.Tasks; 
using DataAccess; 
using NHibernate; 
using NHibernate.Cfg; 
using NHibernate.Tool.hbm2ddl; 

namespace NhibernatePlayground 
{ 
    internal class Program 
    { 
     private static ISessionFactory _sessionFactory; 
     private static Configuration _configuration; 
     private static int TotalThreadCount = 20; 
     private static int LoopCount = 1000; 

     private static void Main(string[] args) 
     { 

      _configuration = BuildConfiguration(); 
      var se = new SchemaExport(_configuration); 
      se.Drop(true, true); 
      se.Create(false, true); 


      _sessionFactory = _configuration.BuildSessionFactory(); 

      int companyId = Seed(); 

      Stopwatch sw = new Stopwatch(); 
      sw.Start(); 
      Task[] tasks = new Task[TotalThreadCount]; 
      for (int i = 0; i < TotalThreadCount; i ++) 
      { 
       tasks[i] = Task.Factory.StartNew(() => IncreaseEmployeeCount(LoopCount, companyId)); 
      } 

      //Block until all tasks complete. 
      Task.WaitAll(tasks); 
      sw.Stop(); 

      Console.WriteLine("Employee Count: " + GetEmployeeCount(companyId)); 
      Console.WriteLine("Total Milliseconds: " + sw.ElapsedMilliseconds); 
      Console.ReadKey(); 
     } 

     private static Configuration BuildConfiguration() 
     { 
      Configuration configuration = new Configuration(); 
      configuration.Configure(); // A 
      configuration.AddAssembly(typeof (Company).Assembly); // B    
      return configuration; 
     } 

     private static void IncreaseEmployeeCount(int count, int companyId) 
     { 
      for (int i = 0; i < count; i++) 
      { 
       using (ISession _session = _sessionFactory.OpenSession()) 
       { 
        using (ITransaction _transaction = _session.BeginTransaction()) 
        { 
         var company = _session.Get<Company>(companyId); 
         company.EmployeeCount++; 
         _session.Save(company); 
         _transaction.Commit(); 
        } 
       } 
      } 
     } 

     private static int Seed() 
     { 
      using (ISession _session = _sessionFactory.OpenSession()) 
      { 
       using (ITransaction _transaction = _session.BeginTransaction()) 
       { 
        Company company = new Company 
         { 
          CompanyName = "Angus" 
         }; 

        _session.Save(company); 
        _transaction.Commit(); 
        return company.Id; 
       } 
      } 
     } 

     private static int GetEmployeeCount(int companyId) 
     { 
      using (ISession _session = _sessionFactory.OpenSession()) 
      { 
       using (ITransaction _transaction = _session.BeginTransaction()) 
       { 
        Company company = _session.Get<Company>(companyId); 
        return company.EmployeeCount; 
       } 
      } 
     } 
    } 
} 

Company.hbm.xml

<?xml version="1.0" encoding="utf-8" ?> 
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="DataAccess" namespace="DataAccess" > 

    <class name="Company"> 
    <id name="Id"> 
     <generator class="hilo" /> 
    </id> 

    <property name="IsActive" /> 

    <property name="CompanyName" /> 

    <property name="EmployeeCount" /> 

    <set name="Users" table="Users_Companies" cascade="none"> 
     <key column="CompanyId"/> 
     <many-to-many column="UserId" class="UserProfile" /> 
    </set>  
    </class> 

</hibernate-mapping> 

Company.cs

using Iesi.Collections.Generic; 

namespace DataAccess 
{ 
    public class Company : Entity<int> 
    { 
     public Company() 
     { 
      //it's not the best practice to initialize virtual properties in constructor but we're controlling 
      //the inheritance in this so doing this should be fine 
      // http://stackoverflow.com/a/469577/89605 
      Users = new HashedSet<UserProfile>(); 
     } 

     public Company(string name) 
      : this() 
     { 
      CompanyName = name; 
      IsActive = true; 
     } 

     public virtual string CompanyName { set; get; } 

     public virtual bool IsActive { set; get; } 

     public virtual ISet<UserProfile> Users { set; get; } 

     public virtual int EmployeeCount { set; get; } 

     public virtual void CopyTo(Company target) 
     { 
      target.CompanyName = CompanyName; 
      target.IsActive = IsActive; 
     } 
    } 
} 
+0

我不認爲這是關於NHibernate的。增量(get + set)本質上是線程不安全的。猜測你唯一的選擇是同步'IncreaseEmployeeCount'調用(例如,通過使用鎖或互斥鎖)。 – rsenna 2013-03-21 18:16:32

+0

由於您在幾乎相同的問題(http://stackoverflow.com/q/15484204/1236044)中嘗試過IsolationLevel.Serializable,因此您似乎嘗試了不同的隔離級別類型。你可以試試IsolationLevel.RepeatableRead – jbl 2013-03-22 09:18:16

+0

@rsenna我已經閱讀了一些在哪裏(但忘記了在哪裏)使用鎖不建議,但我可能是錯的。 – burnt1ce 2013-03-22 13:35:47

回答

0

你需要抓住一個寫在讀取對象時鎖定數據庫。探索鎖定模式參數Get()或ISession.Lock()方法。這將是悲觀的鎖定,這將適用於這樣的場景(頻繁寫入相同的數據)。另一種方法是樂觀鎖定,當對同一數據不頻繁寫入時效果最好。爲此,請探索NHibernate的<版本>映射元素。