Reputation: 2435
I have been working with NHibernate, using Fluent NHibernate for mapping. I solved a lot of issues, and started to think myself as experienced in nhibernate. However, this error is quite strange.
This is my model:
public class MessageNew
{
public virtual int Id { get; set; }
public virtual string Content { get; set; }
public virtual string Subject { get; set; }
public virtual User User { get; set; }
public virtual bool IsSent { get; set; }
public virtual string AmazonMessageId { get; set; }
}
And my mapping
public class MessageNewMap : ClassMap<MessageNew>
{
public MessageNewMap()
{
Id(x => x.Id);
Map(x => x.Content).CustomSqlType("text");
Map(x => x.Subject);
Map(x => x.AmazonMessageId);
Map(x => x.IsSent);
References(x => x.User);
}
}
Here where exception occurs:
foreach (var userToSend in usersToSend)
{
string body = MailHelper.BuildSomeBody()
if (userToSend != CurrentUser)
{
MessageNew message = new MessageNew
{
User = userToSend,
IsSent = false,
Content = body,
Subject = subject
};
session.Save(message); // Exception thrown
}
}
The exception details:
NHibernate.NonUniqueObjectException: a different object with the same identifier value was already associated with the session: 1779, of entity: Models.MessageNew
at NHibernate.Engine.StatefulPersistenceContext.CheckUniqueness(EntityKey key, Object obj)
at NHibernate.Event.Default.AbstractSaveEventListener.PerformSaveOrReplicate(Object entity, EntityKey key, IEntityPersister persister, Boolean useIdentityColumn, Object anything, IEventSource source, Boolean requiresImmediateIdAccess)
at NHibernate.Event.Default.AbstractSaveEventListener.SaveWithGeneratedId(Object entity, String entityName, Object anything, IEventSource source, Boolean requiresImmediateIdAccess)
at NHibernate.Event.Default.DefaultSaveOrUpdateEventListener.SaveWithGeneratedOrRequestedId(SaveOrUpdateEvent event)
at NHibernate.Event.Default.DefaultSaveOrUpdateEventListener.EntityIsTransient(SaveOrUpdateEvent event)
at NHibernate.Event.Default.DefaultSaveOrUpdateEventListener.OnSaveOrUpdate(SaveOrUpdateEvent event)
at NHibernate.Impl.SessionImpl.FireSave(SaveOrUpdateEvent event)
at NHibernate.Impl.SessionImpl.Save(Object obj)
Id generator is database driven auto-increment id generator. (not hilo or any other). NHibernate version is 3.2.0 .
I have tried overloading Equals and GetHashCode, no luck.
The UnitOfWork pattern I am using requires not to commit transaction or flush session inside foreach loop. NHibernate says there is another object with same id, but all i am doing is inserting a new object, which does not have any identifier at all.
I am using the same structure all over my project, and it works well everywhere but this. I am suspicious that it might be because of "Content" property, which is text and set to a large string.
What am i missing here? Or NHibernate is missing something?
Upvotes: 11
Views: 29331
Reputation: 407
Add below two lines before Session.Save
or Session.SaveOrUpdate
Session.Clear();
Session.Flush();
This will clear all cached entities with the Session.
Upvotes: -1
Reputation: 481
I had similar problem. I went through a lot of discussions, tutorials and forums, but after writing some unit tests, I realized:
1) session.Contains method works with instances
2)session.Save/SaveorUpdate works with ID
This error shows you have another instances of object with same ID in session.So, contains return false because you are working on different instances and Save/SaveorUpdate throws an exception because there is another object with same ID in session. I've solved my problem like this(my problem was in Job Entity):
Job lJob = lSession.Load<Job>(this.ID);
if(lJob.ID==this.ID)
lSession.Evict(lJob);
lSession.SaveOrUpdate(this);
I hope it helps you
Upvotes: 5
Reputation: 41
[..]
};
session.Clear();
session.Save(message);
Try this, helped me.
Upvotes: -1
Reputation: 669
Sometimes it happend when we assign the object to the same new object. So first check your model and viewmodel that they are not same.
Upvotes: 4
Reputation: 1
maybe a bit late but hope this helps.
I had a similar problem when i was trying to save multiple instances of an object over the same session with an auto generated column on them. My solution was giving a diferent value and assign it mannually for each entity, so nhibernates doesn't recognize it as the same primary key for that entity.
Upvotes: 0
Reputation: 1089
You can use Evict()
to evict an object from a session and then you can do whatever you want.
This error occurs when you have the same object in another session.
Upvotes: 1
Reputation: 52725
My take: you are not declaring an Id generator. Therefore, as soon as you get two MessageNew instances in the session, they'll both have 0 as the Id.
Upvotes: 0
Reputation: 64628
I read some NH code. It basically inserts the new instance into the database to get its id. Then it checks if the id generated by the database is actually unique. If not, you get this exception.
Your database is not generating unique ids. You most probably forgot to set it to an IDENTITY column.
OR the identity starts counting on 0 instead of 1.
Upvotes: 1
Reputation: 14272
You already have another instance of the entity with that id.
Two possible issues:
1 - Your comparison of the entity does not work. You could override equals as suggested or you could change your test case that you use prior to the save:
if (userToSend.Id != CurrentUser.Id)
2 - You are not generating a unique Id for your entity, you need to either assign an Id yourself, have NHibernate generate one or have your sql server do it for you. In your mapping it is implied that an Identity should be used (Fluents default) but have you set up the column in your database to be and Identity column?
Upvotes: 0
Reputation: 16393
That exception usually indicates that you have 2 separate instances of an object with the same identifier value which you are trying to manage over the same session.
Upvotes: 0
Reputation: 30813
messagenew should implement Equals and GetHashCode
public class MessageNew
{
public virtual int Id { get; set; }
public override bool Equals(object obj)
{
var other = obj as MessageNew;
return (other != null) && (IsTransient ? ReferenceEquals(this, other) : Id == other.Id;
}
private int? _cachedHashcode; // because Hashcode should not change
public override int GetHashCode()
{
if (_cachedHashcode == null)
_cachedHashcode = IsTransient ? base.GetHashCode() : Id.GetHashCode();
return _cachedHashcode.Value;
}
public bool IsTransient { get { return Id == 0; } }
}
Upvotes: 1