2013-03-21 39 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的<版本>映射元素。