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

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();

Related

NHIbernate lazy load a parent object

I have two objects, Case and Note. A Case can have gobs of Notes, like, in the thousands. We are trying to load them asynchronously, in batches, and stream them to the UI so there is no delay waiting for them all to load.
The class/mappings are
public class Case
{
public virtual IList<Note> Notes { get; protected set; }
}
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="SCMS.TAMS.BusinessEntities" namespace="SCMS.TAMS.BusinessEntities">
<class name="Case" table="Cases">
<bag name="Notes" inverse="true" cascade="all" lazy="true">
<key column="CaseID" />
<one-to-many class="Note" />
</bag>
</class>
</hibernate-mapping>
public class Note
{
public virtual Case Case {get; set;}
public virtual long CaseId {get; set;}
}
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="SCMS.TAMS.BusinessEntities" namespace="SCMS.TAMS.BusinessEntities" default-lazy="true">
<class name="Note" table="CaseNotes">
<many-to-one name="Case" column="CaseID"/>
<property name="CaseId" column="CaseID" />
</class>
</hibernate-mapping>
Now, when I call
NHibernateSession.Query<Note>().Where(n => n.CaseId == 123).Skip(0).Take(10).ToList();
to load the first 10 Notes for Case 123, the thing loads the Case object, which takes about 30 seconds because there's lots of other things on it, and other logic when it gets loaded, etc., none of which I need/want at this time. All I want/need are the 10 Notes.
I've tried all sorts of variations on this mapping and none of them have worked. What am I doing wrong here?
How are you using this query? is it some thing for the UI? liking showing in a grid or something? or are you performing business logic in a component?
Either way you want to project into another object. Your query right now returns a list of notes which is then going to load that parent object per the mappings.
So if you are using this query to send the information to the UI of an asp.net mvc application, project directly into your view model
NHibernateSession.Query<Note>().Where(n => n.CaseId == 123).Select(n => new SomeViewModel { Prop1 = n.Prop1, Prop2 = n.Prop2 ...}).Skip(0).Take(10).ToList();
or create an anonymous object
NHibernateSession.Query<Note>().Where(n => n.CaseId == 123).Select n => new { n.Prop1, n.Prop2, ...}).Skip(0).Take(10).ToList();
This will keep the parent object from loading. It also has the added benefit that you are only querying the information you need because the query be limited to the data you are projecting.
Important to know is that if all above is true...
this is the real mapping (which is not it is just an obvious extract)
<class name="Note" table="CaseNotes">
<many-to-one name="Case" column="CaseID"/>
...
this is the class (again extract without ID)
public class Note
{
public virtual Case Case {get; set;}
public virtual long CaseId {get; set;}
}
and that would be a UNIT TEST statement to load notes:
var list = NHibernateSession
.Query<Note>()
.Where(n => n.CaseId == 123)
.Skip(0).Take(10)
.ToList();
then NHibernate will NEVER load the Case object. Never. Because:
NHibernate is lazy, just live with it
The reason, the trigger to load related reference (Case property) must be something explicit.
Mostly:
there is usage of the Case object somewhere. E.g. in override of the GetHashCode() the Case.ID is used
Or:
there is a serialization or DTO conversion which does touch the Case property
In those case, NHibernate must load that...
So, create few unit tests with basic queries and assure that your the is really as shown above. Then it will work as expected

NHibernate returns id outside of session, but not through a property

I have an interface like this:
public interface ICoreType {
int TypeID {get;}
}
And an NHibernate class that implements it like this:
public class DueDateType : ICoreType {
public virtual int DueDateTypeID {get;set;}
...
public virtual int TypeID { get{ return this.DueDateTypeID; } }
}
Here's my mapping:
<class name="DueDateType" table="tdfDueDateType" lazy="true" >
<cache usage="read-write" include="all"/>
<id name="DueDateTypeID" column="DueDateTypeID" >
<generator class="identity"/>
</id>
...
</class>
When I get an instance of the object from NHibernate, and then return it outside the scope of the active session it was created in, I can get the value DueDateTypeID just fine, but accessing TypeID throws a LazyInitializationException. There is no mapping for TypeID, as I simply want it to return whatever DueDateTypeID's value is.
Marking the class lazy="false" in my mapping erases the problem. From what I've been reading, that is because there is no proxy generated for a non-lazy mapped class.
I would like to use the lazy loading features of NHibernate in this case; do I need to implement IInterceptor or some such, in order to get NHibernate to treat this property differently?
The problem is that all members of the entity trigger lazy initialization when accessed (except of the id). NH can't know what you actually are doing within the member. When triggering lazy initialization outside of the session, you get an error.
Suggestions:
Try to avoid any access to entities outside the session. It is only safe when turning off lazy loading - which is usually not a way to go.
Don't wrap the id. If you access TypeID, the entity is initialized although it is not necessary, which is bad for performance.

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: QuerySyntaxException: 'Class' is not mapped

I have the following class:
public class MyClass
{
private List<long> _myList = new List<long>();
public virtual string MyID { get; set; }
public virtual string MyData
{
get
{
return SomeStaticClass.Serialize(_myList);
}
set
{
_myList = SomeStaticClass.Deserialize<List<long>>(value);
}
}
public virtual List<long> MyList
{
get { return _myList; }
}
}
And the following mapping file:
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="MyNamespace"
namespace="MyNamespace">
<class name="MyNamespace.MyClass" table="MY_TABLE">
<id name="MyID" column="MY_ID" type="System.String">
<generator class="assigned"></generator>
</id>
<property name="MyData" column="MY_DATA"></property>
</class>
</hibernate-mapping>
When I try to run the following line:
session.Delete("From MyClass m");
I am getting a QuerySyntaxException with the message "MyClass is not mapped [From MyClass s]".
When I change the name of the "MyID" field to "ID" in the mapping file, the exception becomes
NHibernate.PropertyNotFoundException: Could not find a getter for property 'ID' in class 'MyNamespace.MyClass'.
so I am assuming it can find the mapping file. I made sure that the mapping file is an embedded resource, checked and dobule checked the namespace and class names in the mapping file. What may cause the error? I think it may relate to the MyList property which is not mapped but I am not sure since I am using non-mapped properties on my other classes without a problem.
EDIT: I tried overriding this class, with a class which has no "MyData" property and redefining "MyList" property as string. I am still receiving the same error for my overridden class.
EDIT 2: Tried with a very simple class with the same property names with same return types and only simple get; set; blocks. I still get the same error. I am almost sure that nhibernate can see my mapping files because if I change the name of a single property, it gives me PropertyNotFound instead of "class in not mapped".
How are you loading the hbms? If they are resources, make sure you've actually set the files to be embedded resources in Visual Studio
In case of mapping
<class name="MyClass" table="MY_TABLE">
you should use, for example:
session.CreateQuery("from MyClass")
but not:
session.CreateQuery("from MY_TABLE")
what about if you use
session.Delete("From MyNamespace.MyClass m");
I was just looking at the HQL reference and noticed in their cat example they use fully qualified objects, i.e. Eg.Cat.
You should set the related *.hbm.xml as Embedded Resource.
Make sure your "Build Action of the file" is "Embedded Resource".
I had this problem. I forgot to put hbm in the name of mapping XML files.
it seems a bit strange you are specifying the namespace twice in the mapping file. I would try just specifying the name attribute as just "MyClass" instead of "MyNamespace.MyClass" so it would be
<class name="MyClass" table="MY_TABLE">
I had a similar problem like this. Basically, I included a new project into the solution and I did not map the namespace in the hibernate.cfg.xml file.

Creating a NHibernate object and initializing a Set

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.