如何在NHibernate的任何插入之前增加ID



看起来NH只获得一次MAX(ID),首先插入,然后在内部存储此值,这导致我在其他进程插入数据时出现一些问题。然后我没有实际的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);
    }
}

所有我想要实现的是插入我的Cat记录与ID由NHibernate使用SELECT MAX(ID) FROM Cats 在任何插入之前生成。执行会话。在任何提交失败后刷新。我用SQL Server分析器做了一些调查,这个SQL语句只执行一次(在第一次插入时)——其他插入并不强制检索实际的MAX(ID)。我知道像HiLo这样的算法更好,但我无法取代它。

正如您所发现的,NHibernate增量id生成器并不打算在多用户环境中使用。您声明使用HiLo生成器不是一个选项,因此您只剩下以下选项:

  • 使用本机生成器并更改id列以使用数据库支持的身份机制

  • 使用指定的生成器并编写代码来确定下一个有效的id

  • 创建一个自定义生成器,在其中实现IIdentifierGenerator接口来做你需要的事情

下面是一个自定义生成器的示例代码,它使用一个通用过程来获取给定表的ID。这种方法的主要问题是,您必须将代码包装在类似于工作单元模式的东西中,以确保"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;
        }
    }
}

最新更新