Creating a NHibernate object and initializing a Set - nhibernate

I have a Table called Product and I have the Table StorageHistory.
Now, Product contains a reference to StorageHistory in it's mappings
<set name="StorageHistories" lazy="false">
<key column="ProductId" />
<one-to-many class="StorageHistory" />
</set>
And it works, when I retrieve an object from the ORM I get an empty ISet.
What gives me a headache is how to construct the object in the first place.
When I do the following:
var product = new Product();
session.Save(product);
the product.StorageHistories property is NULL and I get a NullReferenceException.
So, how do I add items to that collection, or should I go the way to add the StorageHistory items themselves to the DB?

I always do the following in the ctor of the parent object:
histories = new HashedSet();
This covers the Save() use case. The Load()/Get() etc usecase is covered by NHibernate as you stated.

Why not?
private ISet _StorageHistories;
public virtual ISet StorageHistories {
protected set { _StorageHistories = value;}
get { if (_StorageHistories == null) _StorageHistories = new HashSet();
return _StorageHistories;
}
}
Of course if you go through the trouble of having a private anyway you might as well put it in the constructor.

Related

NHibernate: Updating collections during EventListener "PreUpdateEvent"

I'm trying to write an audit tracking for Nhibernate that hooks into the PreUpdate event. I have an AuditLogEntry class (when, who, etc), that contains a list of AuditLogEntryDetails (i.e. individual properties that changed). If I isolate the AuditLogEntry class from the entity being audited then my code runs with no errors. However, if I add a list of AuditLogEntry's to the entity being audited then my code throws a
collection [DomainObjects.AuditTracking.AuditLogEntry.Details] was not processed by
flush()
assertion failure when I attempt to save the modified list inside the event listener. This only happens when the audited item already has one (or more) AuditLogEntry instance in the list. If there are no entries then a new list is created and added to the entity being audited and this is fine.
I think by isolating the issue to the above it would appear to be around (lazy) loading the existing list to add the new instance of AuditLogEntry too. However I've been unable to progress any further. Adding 'Lazy="False"' to the list mapping doesn't appear to help. I'm really in the early days of using NHibernate, having borrowed concepts from both the HN 3.0 Cookbook and this blog post. My code is very similar to this, but attempts to add the audit history to the item being audited in a list (and as such I think that I need to also do that in the pre, rather than post update event).
A snap shot of the entity interfaces/classes in question are:
public class AuditLogEntry : Entity
{
public virtual AuditEntryTypeEnum AuditEntryType { get; set; }
public virtual string EntityFullName { get; set; }
public virtual string EntityShortName { get; set; }
public virtual string Username { get; set; }
public virtual DateTime When { get; set; }
public virtual IList<AuditLogEntryDetail> Details { get; set; }
}
public interface IAuditTrackedEntity
{
Guid Id { get; }
IList<AuditLogEntry> ChangeHistory { get; set; }
}
public class AuditTrackedEntity : StampedEntity, IAuditTrackedEntity
{
public virtual IList<AuditLogEntry> ChangeHistory { get; set; }
}
public class LookupValue : AuditTrackedEntity
{
public virtual string Description { get; set; }
}
For the mappings I have:
AuditTrackedEntry.hbm.xml:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="DomainObjects" namespace="DomainObjects.AuditTracking">
<class name="AuditLogEntry">
<id name="Id">
<generator class="guid.comb" />
</id>
<version name="Version" />
<property name="AuditEntryType"/>
<property name="EntityFullName"/>
<property name="EntityShortName"/>
<property name="Username"/>
<property name="When" column="`When`"/>
<list name ="Details" cascade="all">
<key column="AuditLogEntryId"/>
<list-index column="DetailsIndex" base="1"/>
<one-to-many class="AuditLogEntryDetail"/>
</list>
</class>
</hibernate-mapping>
lookupvalue.hbm.xml:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="DomainObjects" namespace="DomainObjects">
<class name="LookupValue">
<id name="Id">
<generator class="guid.comb" />
</id>
<discriminator type="string">
<column name="LookupValueType" unique-key="UQ_TypeName" not-null="true" />
</discriminator>
<version name="Version" />
<property name="Description" unique-key="UQ_TypeName" not-null="true" />
<property name="CreatedBy" />
<property name="WhenCreated" />
<property name="ChangedBy" />
<property name="WhenChanged" />
<list name ="ChangeHistory">
<key column="EntityId"/>
<list-index column="ChangeIndex" base="1"/>
<one-to-many class="DomainObjects.AuditTracking.AuditLogEntry"/>
</list>
</class>
</hibernate-mapping>
The EventListener PreUpdate event handler calls the follow code:
The lines that cause the problem are commented near the end of the code block
public void TrackPreUpdate(IAuditTrackedEntity entity, object[] oldState, object[] state, IEntityPersister persister, IEventSource eventSource)
{
if (entity == null || entity is AuditLogEntry)
return;
var entityFullName = entity.GetType().FullName;
if (oldState == null)
{
throw new ArgumentNullException("No old state available for entity type '" + entityFullName +
"'. Make sure you're loading it into Session before modifying and saving it.");
}
var dirtyFieldIndexes = persister.FindDirty(state, oldState, entity, eventSource);
var session = eventSource.GetSession(EntityMode.Poco);
AuditLogEntry auditLogEntry = null;
foreach (var dirtyFieldIndex in dirtyFieldIndexes)
{
if (IsIngoredProperty(persister, dirtyFieldIndex))
continue;
var oldValue = GetStringValueFromStateArray(oldState, dirtyFieldIndex);
var newValue = GetStringValueFromStateArray(state, dirtyFieldIndex);
if (oldValue == newValue)
{
continue;
}
if (auditLogEntry == null)
{
auditLogEntry = new AuditLogEntry
{
AuditEntryType = AuditEntryTypeEnum.Update,
EntityShortName = entity.GetType().Name,
EntityFullName = entityFullName,
Username = Environment.UserName,
//EntityId = entity.Id,
When = DateTime.Now,
Details = new List<AuditLogEntryDetail>()
};
//**********************
// The next three lines cause a problem when included,
// collection [] was not processed by flush()
//**********************
if (entity.ChangeHistory == null)
entity.ChangeHistory = new List<AuditLogEntry>();
entity.ChangeHistory.Add(auditLogEntry);
session.Save(auditLogEntry);
}
var detail = new AuditLogEntryDetail
{
//AuditLogEntryId = auditLogEntry.Id,
PropertyName = persister.PropertyNames[dirtyFieldIndex],
OldValue = oldValue,
NewValue = newValue
};
session.Save(detail);
auditLogEntry.Details.Add(detail);
}
session.Flush();
}
As previously stated, in this configuration I get an assertion failure "collection [] was not processed by flush()". If I remove the three lines above and the list mapping in the lookupcode.hmb.xml then everything works as expected, other than the entity being audited no longer contains a reference to it's own audited items.
we were facing very similar problem, exactly same exception, but in different situation. No solution found yet...
We have NH event listener implementing IPreUpdateEventListener and OnPreUpdate method used for audit log. Everything is fine for simple properties updating, dirty checking works well, but there are problems with lazy collections. When updating some object which has lazy collection and accessing any object field in the event listener OnPreUpdate method, the same exception as mentioned above is thrown. When lazy set to false, problem disappears.
So it seems there is some problem with lazy collections (and no influence of collection initialization before saving). Our problem isn't connected with creating new collection items; only reading existing object, only its field accessing from the event listener causes the problem.
So in your case, maybe, lazy set to false only for the associatioon could fix the problem, but on the other hand probably you really want to have the collection to be lazy. So hard to say, if the problem has resolution or IInterceptor have to be used instead.
Ok, I have found your problem, this line is actually causing the problem.
Details = new List<AuditLogEntryDetail>()
You can't initialize an empty collection before you save because the EntityPersister will not persist the collection, but it will error that the collection has not been processed.
Also, once nHibernate calls event listeners, cascades do not work (not sure if this is by design or not). So even though you are adding the detail item to the collection later, you are only calling save on the detail, not the parent, so the change is not propagated. I would recommend re-factoring so that items are completed in this order...
Detail, then save,
AuditLogEntry, then save,
Entity, then update.
I had exactly same problem while using EventListener. I was looping through properties one-by-one to detect changes, that included enumerating collections. However when I added a check for the collection using NHibernateUtil.IsInitialized(collection), problem disappeared. I wouldn't catch-and-ignore the AssertionFailure exception since it might have unknown side-effects.
There's an issue still open to solve this problem. There's a patch at the end of topic that solved it to me.
https://nhibernate.jira.com/browse/NH-3226

NHibernate Custom Collections hack works outside of a transaction, but not inside

Following the technique described here, I was able to populate a domain object that uses custom collections for its children. The relevant property mapping looks like this:
<component name="Contacts" class="My.CustomList`1[[Domain.Object, DomainAssembly]], MyAssembly">
<set name="InnerList">
<key column="PARENT_ID" />
<one-to-many class="Contact" />
</set>
</component>
My custom collection class exposes the InnerList property as an ICollection like so:
protected System.Collections.ICollection InnerList
{
get
{
return this;
}
set
{
Clear();
foreach (DomainObject o in value)
{
Add(o);
}
}
}
This worked like a charm to load data from the database and not have to abandon my rather useful custom collection class.
Then I moved on to try implement saving, and following the advice given in this thread, decided to wrap every call to NHibernate in a transaction.
Now when I commit following my load, NHibernate throws an InvalidCastException: "Unable to cast object of type 'My.CustomList`1[Domain.Object, DomainAssembly]' to type 'Iesi.Collections.ISet'."
Is there a way to make this work the way I expect it to?
EDIT:
Following the lead provided by Raphael, I tried switching to ICollection<T> which gives me a different InvalidCastException when I commit the transaction: Unable to cast object of type 'My.CustomList`1[Domain.Object]' to type 'NHibernate.Collection.IPersistentCollection'.
Change the property to be of type
IList<T>

How do I change a child's parent in NHibernate when cascade is delete-all-orphan?

I have two entities in a bi-directional one-to-many relationship:
public class Storage
{
public IList<Box> Boxes { get; set; }
}
public class Box
{
public Storage CurrentStorage { get; set; }
}
And the mapping:
<class name="Storage">
<bag name="Boxes" cascade="all-delete-orphan" inverse="true">
<key column="Storage_Id" />
<one-to-many class="Box" />
</bag>
</class>
<class name="Box">
<many-to-one name="CurrentStorage" column="Storage_Id" />
</class>
A Storage can have many Boxes, but a Box can only belong to one Storage.
I have them mapped so that the one-to-many has a cascade of all-delete-orphan.
My problem arises when I try to change a Box's Storage. Assuming I already ran this code:
var storage1 = new Storage();
var storage2 = new Storage();
storage1.Boxes.Add(new Box());
Session.Create(storage1);
Session.Create(storage2);
The following code will give me an exception:
// get the first and only box in the DB
var existingBox = Database.GetBox().First();
// remove the box from storage1
existingBox.CurrentStorage.Boxes.Remove(existingBox);
// add the box to storage2 after it's been removed from storage1
var storage2 = Database.GetStorage().Second();
storage2.Boxes.Add(existingBox);
Session.Flush(); // commit changes to DB
I get the following exception:
NHibernate.ObjectDeletedException : deleted object would be re-saved by cascade (remove deleted object from associations)
This exception occurs because I have the cascade set to all-delete-orphan. The first Storage detected that I removed the Box from its collection and marks it for deletion. However, when I added it to the second Storage (in the same session), it attempts to save the box again and the ObjectDeletedException is thrown.
My question is, how do I get the Box to change its parent Storage without encountering this exception? I know one possible solution is to change the cascade to just all, but then I lose the ability to have NHibernate automatically delete a Box by simply removing it from a Storage and not re-associating it with another one. Or is this the only way to do it and I have to manually call Session.Delete on the box in order to remove it?
See http://fabiomaulo.blogspot.com/2009/09/nhibernate-tree-re-parenting.html
Basically, it boils down to this... You need to define a custom collection type for NHibernate that re-defines what it means to be an orphan. NHibernate's default behavior is to do just as you discovered - to consider a child to be orphaned if it has been removed from the parent. Instead, you need NHibernate to test the child to see if it has been assigned to a new parent. NHibernate does not do this by default because it would require additional information on the one-to-many mapping - it would need to know the name of the corresponding many-to-one property on the child.
Change your Storage mapping to look like this:
<class name="Storage">
<bag name="Boxes" cascade="all-delete-orphan" inverse="true" collection-type="StorageBoxBag">
<key column="Storage_Id" />
<one-to-many class="Box" />
</bag>
</class>
Define a new type named StorageBoxBag (note - this code is written against NHibernate 2.1 - if you are using NH3 you may have to tweak this a bit):
public class StorageBoxBag : IUserCollectionType
{
public object Instantiate(int anticipatedSize)
{
return new List<Box>();
}
public IPersistentCollection Instantiate(ISessionImplementor session, ICollectionPersister persister)
{
return new PersistentStorageBoxBag(session);
}
public IPersistentCollection Wrap(ISessionImplementor session, object collection)
{
return new PersistentStorageBoxBag(session, (IList<Box>)collection);
}
public IEnumerable GetElements(object collection)
{
return (IEnumerable)collection;
}
public bool Contains(object collection, object entity)
{
return ((IList<Box>)collection).Contains((Box)entity);
}
public object IndexOf(object collection, object entity)
{
return ((IList<Box>) collection).IndexOf((Box) entity);
}
public object ReplaceElements(object original, object target, ICollectionPersister persister, object owner, IDictionary copyCache, ISessionImplementor session)
{
var result = (IList<Box>)target;
result.Clear();
foreach (var box in (IEnumerable)original)
result.Add((Box)box);
return result;
}
}
... and a new type named PersistentStorageBoxBag:
public class PersistentStorageBoxBag: PersistentGenericBag<Box>
{
public PersistentStorageBoxBag(ISessionImplementor session)
: base(session)
{
}
public PersistentStorageBoxBag(ISessionImplementor session, ICollection<Box> original)
: base(session, original)
{
}
public override ICollection GetOrphans(object snapshot, string entityName)
{
var orphans = base.GetOrphans(snapshot, entityName)
.Cast<Box>()
.Where(b => ReferenceEquals(null, b.CurrentStorage))
.ToArray();
return orphans;
}
}
The GetOrphans method is where the magic happens. We ask NHibernate for the list of Boxes that it thinks are orphans, and then filter that down to only the set of Boxes that actually are orphans.

nHibernate Collections issue ( check your mapping file for property type mismatches)

I'm getting the following error:
Unable to cast object of type 'NHibernate.Collection.Generic.PersistentGenericSet to type 'Iesi.Collections.Generic.SortedSet.
Invalid mapping information specified for type [Type], check your mapping file for property type mismatches".
Here's my set definition:
<set name="ProcessTrackerDetails" lazy="true" access="field.camelcase-underscore"
sort="natural" cascade="all" inverse="true">
<key column="ProcessTrackerDetailsID"/>
<one-to-many class="ProcessTrackerDetail"></one-to-many>
</set>
And heres the code:
private Iesi.Collections.Generic.SortedSet<ProcessTrackerDetail> _processTrackerDetails = new SortedSet<ProcessTrackerDetail>();
Suggestions?
NHibernate requires interfaces. Try to use ISet<ProcessTrackerDetail> instead of SortedSet<ProcessTrackerDetail>
Change your code to define _processTrackerDetails using the ISet interface.
private ISet<ProcessTrackerDetail> _processTrackerDetails =
new SortedSet<ProcessTrackerDetail>();
You can still assign it to a SortedSet, but I am not sure that it does much when lazy loaded as NHibernate will use it's ISet implementation to do the lazy loading. The sort="natural" in your mapping should take care of the sort order.
If you are using the 'Set' relation type (unique set of items, NHibernate.Collection.Generic.PersistentGenericSet) then you can define your mapping with System.Collections.Generic.ICollection and use System.Collections.Generic.HashSet.
I'm using Castle ActiveRecord and this is the code I am using:
// In the Collections entity mapping
[HasAndBelongsToMany(typeof(Region),
Table = "CollectionRegionAssociation", ColumnKey = "CollectionId", ColumnRef = "RegionId", RelationType = RelationType.Set)]
public virtual System.Collections.Generic.ICollection<Region> Regions { get; set; }
// Creating and saving a new object
var c = new Collection(); // my own entity
c.Regions = new System.Collections.Generic.HashSet<Region>();
c.Regions.Add(new Region() { ... });
c.Save();

Why does NHibernate delete then insert composite-elements on select?

Can someone explain this little mystery to me about how NHibernate handles composite elements.
I have classes that look like this;
public class Blog
{
public virtual int Id
{
get;
private set;
}
public virtual ISet<Comment> Comments
{
get;
set;
}
}
public class Comment
{
public virtual string CommentText
{
get;
set;
}
public virtual DateTime Date
{
get;
set;
}
}
and mappings like this;
<class name="Blog" table="blog">
<id name="Id" column="id" unsaved-value="0">
<generator class="hilo"/>
</id>
<set name="Comments" table="blog_comments">
<key column="blog_id" />
<composite-element class="Comment">
<property name="CommentText" column="comment" not-null="true" />
<property name="Date" column="date" not-null="true" />
</composite-element>
</set>
</class>
However when i perform a select like this;
using (ITransaction transaction = session.BeginTransaction())
{
Blog blog = session.CreateCriteria(typeof(Blog))
.SetFetchMode("Comments", FetchMode.Eager)
.Add(Expression.IdEq(2345))
.UniqueResult();
transaction.Commit();
}
NHibernate issues a select with a join to get the blog with posts BUT then deletes all comments and then inserts the comments! Why is it doing this? If i do not use a transaction then it will ONLY perform the select and not the DELETE and INSERT as I would expect. What am I missing? I am using NHibernate 2.0
I think you need to override Equals() and GetHashCode() on Comment. NHibernate doesn't have an ID to go on for entity equality so you have to define what makes a comment entity equal to another comment.
Could be wrong :)
Edit
From nhibernate.info (8.2)
Note: if you define an ISet of composite elements, it is very important to implement Equals() and GetHashCode() correctly.
And an example of implementing Equals / GetHashCode from nhibernate.info (4.3)
public class Cat
{
...
public override bool Equals(object other)
{
if (this == other) return true;
Cat cat = other as Cat;
if (cat == null) return false; // null or not a cat
if (Name != cat.Name) return false;
if (!Birthday.Equals(cat.Birthday)) return false;
return true;
}
public override int GetHashCode()
{
unchecked
{
int result;
result = Name.GetHashCode();
result = 29 * result + Birthday.GetHashCode();
return result;
}
}
}
My question would be why you are committing if you only need to do a select? I believe the reason it's deleting all the comments is that when you call commit on the transaction, the blog object and it's associated comments are cached in the session that is used to create the transaction. When you call the commit, you are causing all the objects in the session to be saved which is causing the save back to the database. I'm not clear on why it's deleting the comments but it is correct behaviour to save the objects.
I also stumbled upon this today:
NHibernate is deleting my entire
collection and recreating it instead
of updating the table.
This generally happens when NHibernate
can't figure out which items changed
in the collection. Common causes are:
replacing a persistent collection entirely with a new collection instance
passing NHibernate a manually constructed object and calling Update on it.
serializing/deserializing a persistent collection apparently also causes this problem.
updating a with inverse="false" - in this case, NHibernate can't construct SQL to update an individual collection item.
Thus, to avoid the problem:
pass the same collection instance that you got from NHibernate back to it (not necessarily in the same session),
try using some other collection instead of ( or ), or
try using inverse="true" attribute for .