How to increment ID before any insert with NHibernate

半腔热情 提交于 2020-01-03 18:42:17

问题


It looks like NH gets MAX(ID) only once, at first insert and then stores this value internally, this causes me some problems when other processes inserts data. Then I have not actual ID and duplicate key exception is thrown.

Lets imagine we have table Cats

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

Then we have corresponding mapping done with FluentNhibernate

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

All I want to achieve is to insert my Cat records with ID's generated by NHibernate using SELECT MAX(ID) FROM Cats before any insert. Executing Session.Flush after any commit dosnt work. I'v done some investigation using SQL Server profiler, and this sql stetement is executed only once (at first insert) - other inserts doesnt force to retreive actual MAX(ID). I know that other algorithms like HiLo are better, but I cant replace it.


回答1:


As you found out, the NHibernate Increment id generator was not intended for use in a multi-user environment. You state that using a HiLo generator is not an option so you're left with these options:

  • use the Native generator and change the id column to use the database supported identity mechanism

  • use the Assigned generator and write code to determine the next valid id

  • create a Custom generator where you implement the IIdentifierGenerator interface to do what you need

Below is sample code for a custom generator that uses a generalized proc to get an ID for a given table. The main issue with this approach is that you must wrap the code in something like a Unit of Work pattern to ensure the 'select max(id) ..." and the insert are covered by the same database transaction. The IIdentifierGenerator link has the XML mapping you need to wire up this custom generator.

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;
        }
    }
}


来源:https://stackoverflow.com/questions/7887901/how-to-increment-id-before-any-insert-with-nhibernate

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!