Reputation: 1382
I'm working on a messaging system and in it there is an object called Buffer that stores incoming messages and their locations (for message tracking) as the message is travelling through the system. I am running into some issues that point to something fundamentally wrong with either my application or with NHibernate. I'm using Fluent NHibernate for my mappings.
I have two objects: Buffer and BufferMessageLocation. Buffer is a table in the database and each record represents a buffer message (which are represented by an instance of the Buffer class in code). BufferMessageLocation is essentially a lookup value that can have 5 different values: 0-4 (each of which represent a location). Each Buffer object contains a BufferMessageLocation. BufferMessageLocation is is represented as a Table[int ID, string Name] in the database.
I have to do some detached entity processing because the lifespan of a buffer message in my app will be longer than an NHibernate Session's lifespan.
This means:
From all that I've read about NHibernate, this should work. However, for some reason, NHibernate is trying to INSERT a new record into BufferMessageLocation, instead of just updating the Buffer record.
Here's the error message:
could not insert: [AutomationBase.RepositoryNS.DataAccess.NHMG.Fluent.DTO.DBBufferMessageLocation][SQL: INSERT INTO tblBufferMessageLocation (Name) VALUES (?); select SCOPE_IDENTITY()]
The InnerException is:
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.
The SQL that is generated that throws this error is:
NHibernate: INSERT INTO tblBufferMessageLocation (Name) VALUES (@p0); select SCOPE_IDENTITY();@p0 = 'TestLocation' [Type: String (4000)]
So it looks like it's trying to insert a new BufferMessageLocation with Name="TestLocation" (which already exists) and ID=null whenever I update the buffer's location to that value. There are two things wrong with this scenario:
Why is it inserting a new record when I am simply referencing a record that I just got from the database?
Why is it not providing a value for the ID of the BufferMessageLocation when inserting the new record?
It's worth noting that this only happens when I'm updating the location to ID=0. I've tried changing the ID of the Name="TestLocation" record to 1,2,3,4 and 5, and they all work. Pretty much every value for ID works except for 0. I've even tried moving that record to ID=5, inserting a new BufferMessageLocation record with ID=0 and updating the buffer message's location to this new record, but the problem is still there. So it would seem that ID=0 is causing some issues with NHibernate's record detection mechanism, since all other int ID values work.
Another important thing to note is that this only happens when I use multiple sessions. If I do all of it in a single session, it all works fine. My guess is that Session.Merge, when called for merging detached entities, is not able to detect that there is already a BufferMessageLocation with ID=0.
If anyone has any idea about why this is happening and/or if this is a problem with NHibernate or something that I'm doing wrong, I'd appreciate the feedback. The entirety of my code follows.
Sorry for such a long post, but this is a very specific problem so I had to go into detail.
=========================================================================
APPLICATION CODE (please ignore the "DB" prefixes on class/object names, they are for separation of layers):
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 (Domain classes for this purpose):
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>();
}
}
FLUENT MAPPINGS:
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");
}
}
Upvotes: 2
Views: 1294
Reputation: 30803
Id == 0 is treated special as "Id not set" change the unset value to something different and initialize it in the constructor
public DBBufferMessageLocationMap()
{
Id(x => x.ID, "ID").GeneratedBy.Identity().UnsavedValue(DBBufferMessageLocation.UNSET_ID);
}
public const int UNSET_ID = -1;
public DBBufferMessageLocation()
{
Id = UNSET_ID;
}
Upvotes: 2