问题
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