These old forums are deprecated now and set to read-only. We are waiting for you on our new forums!
More modern, Discourse-based and with GitHub/Google/Twitter authentication built-in.

All times are UTC - 5 hours [ DST ]



Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 10 posts ] 
Author Message
 Post subject: Question about polymorphism and inheritance
PostPosted: Sat Sep 22, 2007 3:45 pm 
Newbie

Joined: Wed Apr 04, 2007 9:40 am
Posts: 12
Need help with Hibernate? Read this first:
http://www.hibernate.org/ForumMailingli ... AskForHelp

Hibernate version: 1.0.2

Mapping documents: <?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.0">
<class name="Competition.Business.Entity, Competition.Business" table="t_entities" lazy="true">
<cache usage="nonstrict-read-write"/>
<id name="ID" column="EntityID" type="int" unsaved-value="0">
<generator class="native" />
</id>

<property name="Name" column="Name" type="String" length="200" not-null="false" />
<property name="IsPrivate" not-null="false" />

<component name="Roles" class="Competition.Business.RoleSet, Competition.Business">
<parent name="Parent"/>
<set name="RolesActor" where="EndDate is null" inverse="true" lazy="true">
<key column="ActorId"/>
<one-to-many class="Competition.Business.Role, Competition.Business"/>
</set>
<set name="RolesTarget" where="EndDate is null" inverse="true" lazy="true" >
<key column="TargetId"/>
<one-to-many class="Competition.Business.Role, Competition.Business"/>
</set>
</component>
<component name="RolesHistory" class="Competition.Business.RoleSet, Competition.Business">
<parent name="Parent"/>
<set name="RolesActor" where="EndDate is not null" lazy="true" inverse="true" >
<key column="ActorId"/>
<one-to-many class="Competition.Business.Role, Competition.Business" />
</set>
<set name="RolesTarget" where="EndDate is not null" lazy="true" inverse="true" >
<key column="TargetId"/>
<one-to-many class="Competition.Business.Role, Competition.Business" />
</set>
</component>

<!-- Users -->
<joined-subclass name="Competition.Business.User, Competition.Business" table="t_users" lazy="true">
<key column="UserID"/>
<property name="LoginName" column="LoginName" type="String" length="20" not-null="false" />
<property name="Password" type="String" length="20" not-null="false"/>
<property name="EmailAddress" column="EmailAddress" type="String" length="40" not-null="false"/>
<property name="LastLogon" type="DateTime" not-null="true"/>
<property name="CreationDate" type="DateTime" not-null="true"/>
<property name="IsVerified" type="Boolean" not-null="true"/>
<property name="ConfirmationCode" type="String" length="50" not-null="false"/>
<property name="ForumUserID" not-null="true"/>
<property name="BirthDate" type="Nullables.NHibernate.NullableDateTimeType, Nullables.NHibernate" not-null="false"/>
<property name="Sex" not-null="false"/>
<property name="FirstName" type="String" length="100" not-null="true"/>
<property name="MiddleName" type="String" length="100" not-null="true"/>
<property name="LastName" type="String" length="100" not-null="true"/>
<property name="SecurityQuestion" type="String" length="200" not-null="false"/>
<property name="SecurityAnswer" type="String" length="200" not-null="false"/>
<property name="Comment" type="String" length="1000" not-null="true"/>
<property name="LastPasswordChangeDate" type="Nullables.NHibernate.NullableDateTimeType, Nullables.NHibernate" not-null="false"/>
<property name="LastLockoutDate" type="Nullables.NHibernate.NullableDateTimeType, Nullables.NHibernate" not-null="false"/>
<property name="IsApproved" type="Boolean" not-null="true"/>
<property name="IsLockedOut" type="Boolean" not-null="true"/>

<bag name="CompetitionInscriptions" inverse="true" lazy="true" >
<key column="UserID"/>
<one-to-many class="Competition.Business.CompetitionInscription, Competition.Business"/>
</bag>

</joined-subclass>

<!-- Teams -->
<joined-subclass name="Competition.Business.Team, Competition.Business" table="t_teams" lazy="true">
<key column="TeamID"/>
<property name="AcceptRequests" />
</joined-subclass>

<!-- Competitions-->
<joined-subclass name="Competition.Business.Competition, Competition.Business" table="t_competitions" lazy="true">
<key column="CompetitionID"/>
<property name="Description" type="String" length="1000" not-null="true" />
<property name="ForumID" not-null="true"/>
<property name="DateInscriptionStarts" type="Nullables.NHibernate.NullableDateTimeType, Nullables.NHibernate"/>
<property name="DateInscriptionEnds" type="Nullables.NHibernate.NullableDateTimeType, Nullables.NHibernate"/>

<set name="Inscriptions" inverse="true" lazy="true">
<key column="CompetitionID"/>
<one-to-many class="Competition.Business.CompetitionInscription, Competition.Business"/>
</set>

<bag name="Events" inverse="true" lazy="true">
<key column="CompetitionID"/>
<one-to-many class="Competition.Business.CompetitionEvent, Competition.Business"/>
</bag>

</joined-subclass>


Code between sessionFactory.openSession() and session.close():

Name and version of the database you are using: SQL Express 05

Good day folks,

I've got the following problem with NHibernate and I have no clue on how to fix it within the NH framework. My mappings are working really well except for the issue I'm highlighting here.

I have an object called Entity that is inherited by multiple classes like User, Teams, Events... I'm using a table-per-subclass mapping. In my code I'm loading objects from their IDs like this: m_session.Load(type, id).

To show the problem I'm going to give an example. I have and object of type User with ID 1 and an object of type Team with ID 2. Using m_session.Load(typeof(User),1) works as normal. However: m_session.Load(typeof(Team),1) causes problem because it is not returning null (or more accurately throwing an exception for missing object). It returns an object, of the correct type Team, but of course, the properties are not initialized and don't contain any meaningful data.

I was expecting an exception while using m_session.Load(typeof(Team),1) when 1 is the ID of a User and it is causing me major headaches. So my question is: how can I ensure that when I load an object of a specific tpye it will not load a different object from the same base object? (the same problem happens between any of the types derived from Entity).

I hope I explained it clearly...

Thanks,
Alain-Daniel


Top
 Profile  
 
 Post subject:
PostPosted: Sat Sep 22, 2007 7:07 pm 
Hibernate Team
Hibernate Team

Joined: Tue Jun 13, 2006 11:29 pm
Posts: 315
Location: Calgary, Alberta, Canada
Given the mapping you have, unless you know what the sub-type is for a given ID, you would have to call Load() with the parent type:

Code:
Team team = m_session.Load(typeof(Entity), 1) as Team;
if (team == null) { /* handle error */ }

My suggestion is to change your mapping so User, Team, and other subclasses become root classes (i.e. mapped using the <class> element). Your C# classes can still inherit from Entity; only now NHibernate is oblivious of it.

I can understand your motivation for making Entity the root class; if my guess is correct, you want to only map the common properties once. However, my suggestion is to drop the Entity mapping, "promote" the other classes to <class>, and live with a bit of duplication. In my opinion, the duplication in the hbm files are much more manageable than some hidden bug that may surface at a later date.

Hope this helps.

_________________
Karl Chu


Top
 Profile  
 
 Post subject:
PostPosted: Sat Sep 22, 2007 7:23 pm 
Newbie

Joined: Wed Apr 04, 2007 9:40 am
Posts: 12
Thanks for your answer. However, I don't think I can easily (or even at all) get rid of the Entity class because I am using it in collections where the polymorphims is important: NHIbernate loads the proper instance of the class automatically. I'm not sure if I could do it at all without the subclassing mapping.

As for you code example

Code:
Team team = m_session.Load(typeof(Entity), 1) as Team;
if (team == null) { /* handle error */ }


If the ID 1 was a User and I ran the code, the team variable would NOT be null, which is exactly my problem. I have tried using the 'is' keyword to determine the underlying type, but that doesn't work either because Load returns a proxy class.

Granted, I could force the object to initialize since it is obvious that it will eventually be initialized at some later point. However, I'd rather find a cleaner solution.

Alain-Daniel


Top
 Profile  
 
 Post subject:
PostPosted: Sat Sep 22, 2007 8:41 pm 
Hibernate Team
Hibernate Team

Joined: Tue Jun 13, 2006 11:29 pm
Posts: 315
Location: Calgary, Alberta, Canada
Note the "as Team" at the end. If ID=1 is a "User", the type conversion fails and therefore "team" would have null value.

_________________
Karl Chu


Top
 Profile  
 
 Post subject:
PostPosted: Mon Sep 24, 2007 9:24 am 
Newbie

Joined: Wed Apr 04, 2007 9:40 am
Posts: 12
Good day again,

I've tried your solution, but it didn't work as it returned null all the time. Then I found this in the NHibernate doc that explains why:

Quote:
Firstly, instances of Cat will never be castable to DomesticCat, even if the underlying instance is an instance of DomesticCat.

Cat cat = (Cat) session.Load(typeof(Cat), id); // instantiate a proxy (does not hit the db)
if ( cat.IsDomesticCat ) // hit the db to initialize the proxy
{
DomesticCat dc = (DomesticCat) cat; // Error!
....
}


I haven't found a solution yet. What confuses me the most is that the debuging interface in VS tells me what is the real base type of the object, but I cannot find a way to get the same thing programatically!

Alain-Daniel


Top
 Profile  
 
 Post subject:
PostPosted: Mon Sep 24, 2007 11:28 am 
Expert
Expert

Joined: Tue Aug 23, 2005 5:52 am
Posts: 335
Try using NHibernateUtil.GetClass(object) - that'll tell you the type of the entity you loaded.

Cheers,

Symon.


Top
 Profile  
 
 Post subject:
PostPosted: Mon Sep 24, 2007 3:33 pm 
Beginner
Beginner

Joined: Tue Sep 14, 2004 1:03 pm
Posts: 33
Location: Calgary, Alberta Canada
I use the same sort of pattern in one of my systems. However, I have an additional column in my entities table called type. I have triggers on my tables of my entity types that store the appropriate type in the entities type column. (just the schema and table name e.g. 'ADM.LABS')

The reason I've added the column is just for traversing the generic collections of type IList<Enitity>. Because we only get a proxy entity from the persistence layer, if I need to get the specific object, I need some way to know which one to get.

I bring this up only because I trying to find an instance of when you would try to Get(type, id) or Load(type, id) by the ID of the wrong type. I'm assuming by iterating through a list of the entity type.


Top
 Profile  
 
 Post subject:
PostPosted: Mon Sep 24, 2007 4:03 pm 
Newbie

Joined: Wed Apr 04, 2007 9:40 am
Posts: 12
GetClass huh? I didn't know of that function, it is working great however thanks. I can write a snippet that is going to check the type of the object before returning it. That will do fine. Thank you.

Alain-Daniel


Top
 Profile  
 
 Post subject:
PostPosted: Mon Sep 24, 2007 4:11 pm 
Newbie

Joined: Wed Apr 04, 2007 9:40 am
Posts: 12
I like the table idea, I hadn't thought of it.

When I have a list of objects of unknown types, I am using the Visitor pattern. There is some information about it there: http://en.wikipedia.org/wiki/Visitor_pattern.

As for why I might have an ID of the wrong type, it is because it is a website. The query string contains the ID of the a user or a team or a competition. Some users fudge with the query string, trying out different IDs... hence the problem.

I could change the IDs to a GUID maybe, but it is working fine as-is (and it doesn't remove the risk). Proofing the load functions against wrong types seems the right fix for now.

Alain-Daniel


Top
 Profile  
 
 Post subject:
PostPosted: Mon Sep 24, 2007 5:00 pm 
Beginner
Beginner

Joined: Tue Sep 14, 2004 1:03 pm
Posts: 33
Location: Calgary, Alberta Canada
I too thought about GUIDs or more of a COMB where one part of the GUID would hold information about the type.
If your using DAOs, rather than using the schema in the type field of the entity, you could use the object type instead. That way you may be able to inject the type part into the query using an HQL query like so

Code:
string queryString = "from :type where ID = :id"
IQuery query = session.CreateQuery(queryString);
query.SetParameter("type", entity.Type); //e.g. MyNamespace.Team
query.SetParameter("id", entity.ID);
return query.UniqueResult();//You should get a team object


you could then replace the type name using the string of the exact type stored in the entity table and it should get you the concrete class of the exact type you need, whereby allowing you to use the visitor pattern as the method for the concrete class will be available.


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 10 posts ] 

All times are UTC - 5 hours [ DST ]


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Search for:
© Copyright 2014, Red Hat Inc. All rights reserved. JBoss and Hibernate are registered trademarks and servicemarks of Red Hat, Inc.