我正在开发一个消息传递系统,其中有一个名为 Buffer 的对象,当消息通过系统时,它存储传入消息及其位置(用于消息跟踪)。我遇到了一些问题,这些问题表明我的应用程序或 NHibernate 存在根本性错误。我正在使用 Fluent NHibernate 进行映射。
我有两个对象:Buffer和BufferMessageLocation。Buffer 是数据库中的一个表,每条记录代表一个缓冲区消息(在代码中由 Buffer 类的实例表示)。BufferMessageLocation 本质上是一个查找值,可以有 5 个不同的值:0-4(每个代表一个位置)。每个 Buffer 对象都包含一个 BufferMessageLocation。BufferMessageLocation 在数据库中表示为 Table[int ID, string Name]。
我必须进行一些分离的实体处理,因为我的应用程序中缓冲区消息的生命周期将比 NHibernate 会话的生命周期长。
这表示:
- 打开新会话,从数据库中获取缓冲区消息,关闭会话
- 在我的应用程序中进行一些处理,将缓冲区消息的位置更新为 {ID=0, Name="TestLocation"}。
- 打开新会话,通过调用 Session.Load< BufferMessageLocation >(0) 获取新位置,关闭会话。
- 将缓冲区消息的 BufferMessageLocation 更新为在步骤 3 中检索到的位置。
- 打开新会话,在缓冲区消息上调用 Session.Merge(buffer),关闭会话。
从我读过的关于 NHibernate 的所有内容来看,这应该有效。然而,出于某种原因,NHibernate 正在尝试将新记录插入到 BufferMessageLocation 中,而不是仅仅更新 Buffer 记录。
这是错误消息:
could not insert: [AutomationBase.RepositoryNS.DataAccess.NHMG.Fluent.DTO.DBBufferMessageLocation][SQL: INSERT INTO tblBufferMessageLocation (Name) VALUES (?); select SCOPE_IDENTITY()]
内部异常是:
Cannot insert the value NULL into column 'ID', table 'AutomationNew.dbo.tblBufferMessageLocation'; column does not allow nulls. INSERT fails.\r\nThe statement has been terminated.
引发此错误的生成的 SQL 是:
NHibernate: INSERT INTO tblBufferMessageLocation (Name) VALUES (@p0); select SCOPE_IDENTITY();@p0 = 'TestLocation' [Type: String (4000)]
因此,每当我将缓冲区的位置更新为该值时,它似乎都在尝试插入一个带有 Name="TestLocation" (已经存在)和 ID=null 的新 BufferMessageLocation。这种情况有两个问题:
当我只是引用刚从数据库中获取的记录时,为什么要插入新记录?
为什么插入新记录时没有为 BufferMessageLocation 的 ID 提供值?
值得注意的是,这只发生在我将位置更新为 ID=0 时。我尝试将 Name="TestLocation" 记录的 ID 更改为 1、2、3、4 和 5,它们都可以工作。除了 0 之外,几乎所有 ID 值都有效。我什至尝试将该记录移至 ID=5,插入一条 ID=0 的新 BufferMessageLocation 记录,并将缓冲区消息的位置更新为该新记录,但问题仍然存在. 因此,似乎 ID=0 会导致 NHibernate 的记录检测机制出现一些问题,因为所有其他 int ID 值都有效。
另一个需要注意的重要事情是,这只发生在我使用多个会话时。如果我在一个会话中完成所有这些工作,那么一切正常。我的猜测是,当调用 Session.Merge 来合并分离的实体时,它无法检测到已经存在 ID=0 的 BufferMessageLocation。
如果有人知道为什么会发生这种情况和/或如果这是 NHibernate 的问题或我做错了什么,我会很感激反馈。我的全部代码如下。
很抱歉这么长的帖子,但这是一个非常具体的问题,所以我不得不详细说明。
==================================================== ========================
应用程序代码(请忽略类/对象名称上的“DB”前缀,它们用于分层):
class Program
{
static void Main(string[] args)
{
DBBuffer dbBuffer;
DBInstruction instruction;
DBBufferMessageLocation bufferMessageLocation;
using (ISession session = FNHHelper.OpenSession())
{
using (ITransaction tran = session.BeginTransaction())
{
dbBuffer = session.QueryOver<DBBuffer>().List().First();
instruction = session.Load<DBInstruction>(2);
bufferMessageLocation = session.Load<DBBufferMessageLocation>(0);
tran.Commit();
}
}
//do application processing
dbBuffer.BufferMessageLocation = bufferMessageLocation;
//required attributes
dbBuffer.Instruction = instruction;
dbBuffer.Processed = false;
dbBuffer.FromProducer = false;
using (ISession session = FNHHelper.OpenSession())
{
using (ITransaction tran = session.BeginTransaction())
{
session.Merge(dbBuffer); <======== This is where the error is thrown
tran.Commit();
}
}
}
}
DTO CLASSES(用于此目的的域类):
public partial class DBBuffer : IRepositoryEntity
{
public System.Guid ID { get; set; }
public DBLog Log { get; set; }
public DBError Error { get; set; }
public DBInstruction Instruction { get; set; }
public DBBufferMessageLocation BufferMessageLocation { get; set; }
public DateTime BufferTime { get; set; }
public string ClientIP { get; set; }
public string Description { get; set; }
public bool Processed { get; set; }
public bool FromProducer { get; set; }
}
public partial class DBBufferMessageLocation : IRepositoryLookup
{
public int ID { get; set; }
public string Name { get; set; }
public IList<DBBuffer> Buffers { get; set; }
public DBBufferMessageLocation()
{
Buffers = new List<DBBuffer>();
}
}
流利的映射:
public partial class DBBufferMap : ClassMap<DBBuffer> {
public DBBufferMap() {
Table("tblBuffer");
Id(x => x.ID).GeneratedBy.Assigned().Column("ID");
References(x => x.Log).Column("LogID").Cascade.All();
References(x => x.Error).Column("ErrorID").Cascade.All();
References(x => x.Instruction).Column("InstructionID").Cascade.All();
References(x => x.BufferMessageLocation).Column("BufferMessageLocationID").Cascade.All();
Map(x => x.BufferTime).Column("BufferTime").Not.Nullable();
Map(x => x.ClientIP).Column("ClientIP");
Map(x => x.Description).Column("Description");
Map(x => x.Processed).Column("Processed").Not.Nullable();
Map(x => x.FromProducer).Column("FromProducer").Not.Nullable();
}
}
public partial class DBBufferMessageLocationMap : ClassMap<DBBufferMessageLocation> {
public DBBufferMessageLocationMap() {
Table("tblBufferMessageLocation");
Id(x => x.ID).GeneratedBy.Identity().Column("ID");
Map(x => x.Name).Column("Name").Not.Nullable();
HasMany(x => x.Buffers).KeyColumn("BufferMessageLocationID");
}
}