如何在使用 NHibernate 进行任何插入之前增加 ID

发布于 2024-12-11 12:27:31 字数 682 浏览 0 评论 0原文

看起来 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);
    }
}

我想要实现的就是使用 SELECT MAX 插入带有 NHibernate 生成的 ID 的 Cat 记录(ID) FROM Cats 在任何之前插入。在任何提交之后执行 Session.Flush 都不起作用。我使用 SQL Server Profiler 进行了一些调查,并且此 sql stetement 仅执行一次(在第一次插入时) - 其他插入不会强制检索实际的 MAX(ID)。我知道像 HiLo 这样的其他算法更好,但我无法取代它。

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

浪漫之都 2024-12-18 12:27:31

正如您所发现的,NHibernate Increment id 生成器不适用于多用户环境。您声明使用 HiLo 生成器不是一个选项,因此您只剩下以下选项:

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

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

  • 创建自定义生成器,其中你实现了IIdentifierGenerator 接口来执行您需要的操作

下面是自定义生成器的示例代码它使用通用过程来获取给定表的 ID。这种方法的主要问题是您必须将代码包装在类似 工作单元 模式,以确保“select max(id) ...”和插入由同一数据库事务覆盖。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;
        }
    }
}

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;
        }
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文