2011-10-25 43 views
4

NH看起來像MAX只需要一次插入一次,然後在內部存儲該值,當其他進程插入數據時,這會導致一些問題。然後我沒有實際的ID和重複的關鍵異常被拋出。如何在使用NHibernate進行任何插入之前增加ID

讓我們想象一下,我們有表Cats

CREATE TABLE Cats(ID int, Name varchar(25)) 

然後,我們都有相應的與FluentNhibernate

public class CatMap : ClassMap<Cat> 
{ 
    public CatMap() 
    { 
     Id(m=>m.ID).GeneratedBy.Increment(); 
     Map(m=>.Name); 
    } 
} 

所有我想要實現的是通過NHibernate的使用SELECT MAX(ID) FROM Cats生成的ID的插入我的Cat記錄進行映射之前插入任何。在任何提交dosnt工作後執行Session.Flush。我已經使用SQL Server分析器進行了一些調查,並且此sql執行只執行一次(在第一次插入時) - 其他插入操作不強制執行實際MAX(ID)檢索。我知道像HiLo這樣的其他算法更好,但我無法替代它。

回答

3

正如您發現的那樣,NHibernate Increment id生成器並不適用於多用戶環境。幽州使用希洛生成器是不是一種選擇,那麼你留下了這些選項:

  • 使用Native發電機並更改其ID列使用的數據庫支持的身份機制

  • 使用分配生成器和寫代碼,以確定下一個有效ID

  • 創建自定義生成器,你實現IIdentifierGenerator界面做你需要什麼

以下是自定義生成器的示例代碼,它使用通用proc來獲取給定表的ID。這種方法的主要問題是,你必須將代碼包裝成Unit of Work模式,以確保'select max(id)...「和insert被同一個數據庫事務覆蓋。IIdentifierGenerator鏈接具有XML映射你需要線了這個自定義的生成。

using System; 
using System.Collections.Generic; 
using System.Data; 
using NHibernate.Dialect; 
using NHibernate.Engine; 
using NHibernate.Id; 
using NHibernate.Persister.Entity; 
using NHibernate.Type; 

namespace YourCompany.Stuff 
{ 
    public class IdGenerator : IIdentifierGenerator, IConfigurable 
    { 
     private string _tableName; 
     // The "select max(id) ..." query will go into this proc: 
     private const string DefaultProcedureName = "dbo.getId"; 

     public string ProcedureName { get; protected set; } 
     public string TableNameParameter { get; protected set; } 
     public string OutputParameter { get; protected set; } 

     public IdGenerator() 
     { 
      ProcedureName = DefaultProcedureName; 
      TableNameParameter = "@tableName"; 
      OutputParameter = "@newID"; 
     } 

     public object Generate(ISessionImplementor session, object obj) 
     { 
      int newId; 
      using (var command = session.Connection.CreateCommand()) 
      { 
       var tableName = GetTableName(session, obj.GetType()); 

       command.CommandType = CommandType.StoredProcedure; 
       command.CommandText = ProcedureName; 

       // Set input parameters 
       var parm = command.CreateParameter(); 
       parm.Value = tableName; 
       parm.ParameterName = TableNameParameter; 
       parm.DbType = DbType.String; 

       command.Parameters.Add(parm); 

       // Set output parameter 
       var outputParameter = command.CreateParameter(); 
       outputParameter.Direction = ParameterDirection.Output; 
       outputParameter.ParameterName = OutputParameter; 
       outputParameter.DbType = DbType.Int32; 

       command.Parameters.Add(outputParameter); 

       // Execute the stored procedure 
       command.ExecuteNonQuery(); 

       var id = (IDbDataParameter)command.Parameters[OutputParameter]; 

       newId = int.Parse(id.Value.ToString()); 

       if (newId < 1) 
        throw new InvalidOperationException(
         string.Format("Could not retrieve a new ID with proc {0} for table {1}", 
             ProcedureName, 
             tableName)); 
      } 

      return newId; 
     } 

     public void Configure(IType type, IDictionary<string, string> parms, Dialect dialect) 
     { 
      _tableName = parms["TableName"]; 
     } 

     private string GetTableName(ISessionImplementor session, Type objectType) 
     { 
      if (string.IsNullOrEmpty(_tableName)) 
      { 
       //Not set by configuration, default to the mapped table of the actual type from runtime object: 
       var persister = (IJoinable)session.Factory.GetClassMetadata(objectType); 

       var qualifiedTableName = persister.TableName.Split('.'); 
       _tableName = qualifiedTableName[qualifiedTableName.GetUpperBound(0)]; //Get last string 
      } 

      return _tableName; 
     } 
    } 
} 
+0

通過實現自定義ID生成器。 – Dariusz

+0

@Dariusz我堅持用了類似的問題完成,你可以請提供你如何實現的呢? – nozzleman

+0

正如我一些額外的信息已經說過,我已經實現了我的自定義IdGenerator @nozzleman – Dariusz

相關問題