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.  [ 9 posts ] 
Author Message
 Post subject: Associations without containers
PostPosted: Wed May 28, 2008 3:45 am 
Newbie

Joined: Wed May 28, 2008 3:17 am
Posts: 10
Hi, I'm new to nHibernate although I do have several years of .Net, SQL & ORMs up my sleave. Its controlling the nHibernate mappings that I think I'm having trouble with! (Due to restrictions I can't quote actual code here.. sorry!)

I have two pretty standard tables, e.g. Parent & Child

Code:
[tParent]
ParentId  int identity, pk
Name

[tChild]
ChildId   int, identity, pk
ParentId int, foreign key to Parent
Name


These map to objects in C#, however note the lack of a container object in the Parent:

Code:
public class Parent
{
   private int m_id;
   private string m_parentName;

/* Set / Get properties Id & ParentName edited out for brevity */
}

public class Child
{
   private int m_id;
   private int m_parentId;
   private string m_childName;

/* ... properties */
}

Now, they are associated (and a foreign key constraint appears in the db), but not in the objects themselves. The mappings I currently have are:

Code:
<class name="Parent" table="tParent>
<id name="Id" column="ParentId"> <generator class="identity"/> </id>
<property name="ParentName" column="name" />
</class>

<class name="Child" table="tChild>
<id name="Id" column="ChildId"> <generator class="identity"/> </id>
<property name="ParentId" column="ParentId" />
<property name="ChildName" column="name" />
</class>


Now, what I want to be able to do is retrieve Parent objects where the child objects meet certain critria, using the nHibernate Criteria objects (or in fact, vice versa - Child objects of a certain parent)

for e.g., retrieve all parents who have a child named 'george'

Code:
Criteria parentSearch = session.CreateCriteria(typeof(Parent));

// this won't work... but what will?
Criteria childSearch = parentSearch.CreateCriteria(typeof(Child));
childCritiera.Add(new EqExpression("ChildName", "george"));

List returnable = parentSearch.List()


The real question is, how can create the Parent.hbm.xml and the Child.hbm.xml mappings so that nHibernate can work out the relationships, but without having the container in the Parent class? All the documentation I've read and seen on the web assumes that the Parent class has an IList (or somesuch) in which to store the Child objects. Admittedly, in that case it becomes an awful lot easier. However, I'm nable to change those classes.

Any ideas?

(yes, I know it's all psuedo code, but its all I can let out, unfortunately. I'll try to make a test project to post, but it will take some time.)

Thanks to all for your help.

Phil


Top
 Profile  
 
 Post subject:
PostPosted: Wed May 28, 2008 4:44 am 
Expert
Expert

Joined: Thu Dec 14, 2006 5:57 am
Posts: 1185
Location: Zurich, Switzerland
You can use a subquery to retrieve all the parent ids that match and then select the partners out of this list:

Code:
DetachedCriteria childs = DetacheCriteria.For(typeof(Child))
    .Add(Expressions.Eq("ChildName", "george"))
    .SetProjection(Projections.DistinctProperty("ParentId"));

ICriteria parents = session.CreateCriteria(typeof(Parent))
   .Add( Subqueries.PropertyIn("Id", childs))
   .List();

_________________
--Wolfgang


Top
 Profile  
 
 Post subject:
PostPosted: Wed May 28, 2008 5:03 am 
Newbie

Joined: Wed May 28, 2008 3:17 am
Posts: 10
Hi wolli,

thanks for that... I had something along those lines, but when I do try to do the criteria.List() I get the following exception

Key cannot be null.

StackTrace:

" at System.Collections.Hashtable.get_Item(Object key)\r\n at NHibernate.Impl.SessionFactoryImpl.GetEntityPersister(Type theClass)\r\n at NHibernate.Loader.Criteria.CriteriaQueryTranslator.GetPropertyMapping(Type entityName)\r\n at NHibernate.Loader.Criteria.CriteriaQueryTranslator.GetType(ICriteria subcriteria, String propertyName)\r\n at NHibernate.Loader.Criteria.CriteriaQueryTranslator.GetTypeUsingProjection(ICriteria subcriteria, String propertyName)\r\n at NHibernate.Loader.Criteria.CriteriaQueryTranslator.GetTypedValue(ICriteria subcriteria, String propertyName, Object value)\r\n at NHibernate.Expression.SimpleExpression.GetTypedValues(ICriteria criteria, ICriteriaQuery criteriaQuery)\r\n at NHibernate.Loader.Criteria.CriteriaQueryTranslator.GetQueryParameters()\r\n at NHibernate.Expression.SubqueryExpression.InitializeInnerQueryAndParameters(ICriteriaQuery criteriaQuery)\r\n at NHibernate.Expression.SubqueryExpression.ToSqlString(ICriteria criteria, ICriteriaQuery criteriaQuery, IDictionary enabledFilters)\r\n at NHibernate.Loader.Criteria.CriteriaQueryTranslator.GetWhereCondition(IDictionary enabledFilters)\r\n at NHibernate.Loader.Criteria.CriteriaJoinWalker..ctor(IOuterJoinLoadable persister, CriteriaQueryTranslator translator, ISessionFactoryImplementor factory, CriteriaImpl criteria, Type rootEntityName, IDictionary enabledFilters)\r\n at NHibernate.Loader.Criteria.CriteriaLoader..ctor(IOuterJoinLoadable persister, ISessionFactoryImplementor factory, CriteriaImpl rootCriteria, Type rootEntityName, IDictionary enabledFilters)\r\n at NHibernate.Impl.SessionImpl.Find(CriteriaImpl criteria, IList results)\r\n at NHibernate.Impl.SessionImpl.Find(CriteriaImpl criteria)\r\n at NHibernate.Impl.CriteriaImpl.List()\r\n at
/* remainder are my classes */


Top
 Profile  
 
 Post subject:
PostPosted: Wed May 28, 2008 5:12 am 
Expert
Expert

Joined: Thu Dec 14, 2006 5:57 am
Posts: 1185
Location: Zurich, Switzerland
It's hard to say without the real classes and mappings :-( ... can you try to setup a test case or post your version of the criteria which caused the error ?

_________________
--Wolfgang


Top
 Profile  
 
 Post subject:
PostPosted: Thu May 29, 2008 12:04 am 
Newbie

Joined: Wed May 28, 2008 3:17 am
Posts: 10
Hi Wolli, after putting together the sample app, I think I've found the error. As far as I can work out, it seems to be a bug in nHibernate, although I would like confirmation that I'm doing things corrently first!

The nHibernate Criteria & DetachedCriteria are created for interfaces. The interfaces are mapped to classes using the proxy element of the class mapping. Attempting to get the criteria.List(), with a DetachedCriteria subquery for an interface seems to throw the exception. For some reason, nHibernate isn't searching the mapping for a class based on the interface. The next error that pops up is a Mapping Exception: 'Unknown Entity Class: <interface name>'

Some code to reproduce:

Parent and Child interfaces and classes
Code:
    public interface IParent
    {
        int ParentId { get; set; }
        string Name { get; set; }
    }

    public interface IChild
    {
        int ChildId { get; set; }
        int ParentId { get; set; }
        string Name { get; set; }
    }

    public class Parent : IParent
    {
        private int m_id;
        private string m_name;

        #region IParent Members

        public int ParentId
        {
            get { return m_id; }
            set { m_id = value; }
        }

        public string Name
        {
            get { return m_name; }
            set { m_name = value; }
        }

        #endregion
    }

    class Child : IChild
    {
        private int m_id;
        private int m_parentid;
        private string m_name;

        #region IChild Members

        public int ChildId
        {
            get { return m_id; }
            set { m_id = value; }
        }
       
        public int ParentId
        {
            get { return m_parentid; }
            set { m_parentid = value; }
        }

        public string Name
        {
            get { return m_name; }
            set { m_name = value; }
        }

        #endregion

    }


The mapping files

Code:
<class name="TestProj.Parent, TestProj"
       proxy="TestProj.IParent, TestProj"
       table="tParent"
       lazy="false">

  <id name="ParentId" column="Id" type="Int32" length="4" unsaved-value="0">
    <generator class="identity" />
  </id>
 
  <property name="Name" column="Name" type="String" length="50"/>
 
</class>

<class name="TestProj.Child, TestProj"
       proxy="TestProj.IChild, TestProj"
       table="tChild"
       lazy="false">

  <id name="ChildId" column="Id" type="Int32" length="4" unsaved-value="0">
    <generator class="identity" />
  </id>

  <property name="ParentId" column="ParentId" type="Int32" length="4" />
  <property name="Name" column="Name" type="String" length="50"/>
 
</class>


The code to do the query:

Code:
            ICriteria criteria = session.CreateCriteria(typeof(IChild));

            // note - subCriteria based on an interface!
            DetachedCriteria subCriteria = DetachedCriteria.For(typeof(IParent));
            subCriteria.SetProjection(Projections.Distinct(Projections.Property("ParentId")));
            subCriteria.Add(Expression.Eq("Name", "Phil"));

            criteria.Add(Subqueries.PropertyIn("ParentId", subCriteria));

            IList SearchResult;

            try
            {
                session.Connection.Open();

                // this line breaks if SubCriteria is for IParent,
                // but returns correct results for a Parent
                SearchResult = criteria.List();
            }
            finally
            {
                session.Connection.Close();
            }


I hope this helps. I've used this way of referencing the interface, as it is what I've seen before (elsewhere in our code base). However if there is another way of defining the mapping, then I'm all ears.

Unfortunately, I can't change the code to use the actual objects as opposed to interfaces.

Thanks for all your help so far. I'm glad to get this resolved!

Phil


Top
 Profile  
 
 Post subject:
PostPosted: Thu May 29, 2008 12:56 am 
Newbie

Joined: Wed May 28, 2008 3:17 am
Posts: 10
Finally cracked it!!! (at least in the test project :-)

The documentation is not obvious on how to map interfaces and classes - it does say to use a subclass, but not how to force the mapping from the interface-class to the only class-subclass available.

I've redefined the mapping as follow. The trick is to use a simple discriminator formula, and provide that value as the discriminator-value in the subclass.

Now to test it on the real thing!!

Regards,
phil

Code:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">

  <class name="TestProj.IParent, TestProj" table="tParent" lazy="false" >

    <id name="ParentId" column="Id" type="Int32" length="4" unsaved-value="0">
      <generator class="identity" />
    </id>
    <discriminator formula="0"/>

    <property name="Name" column="Name" type="String" length="50"/>


    <subclass name="TestProj.Parent, TestProj" lazy="false" discriminator-value="0">
      <property name="Name" column="Name" type="String" length="50"/>
    </subclass>
  </class>
 
</hibernate-mapping>


Top
 Profile  
 
 Post subject:
PostPosted: Thu May 29, 2008 2:32 am 
Expert
Expert

Joined: Thu Dec 14, 2006 5:57 am
Posts: 1185
Location: Zurich, Switzerland
Your first approach with interfaces for the proxies won't work, since if you map class Parent with a proxy interface IParent, you have to build your criteria still for Parent and not for IParent. The result will be IParent objects.

You have to map the interfaces, checkout chapter 8.1 of the reference documentation. Depending on the strategy you use, you'll need discriminator columns/formulas or not.

_________________
--Wolfgang


Top
 Profile  
 
 Post subject:
PostPosted: Thu May 29, 2008 4:50 am 
Newbie

Joined: Wed May 28, 2008 3:17 am
Posts: 10
Hey there

When working with a simple criteria search (not subqueries for example), then the original proxy strategy worked just fine. And as you say, it returned a list of IParent, which were actually Parent objects underneath.

I have attempted to read the manual, but I can't say its abundantly clear! I'm happy with the way it works using a descriminator, but am curious to see how to achieve the same result without using one. Are you able to provide an example?

Thanks for all your help.

Phil


Top
 Profile  
 
 Post subject:
PostPosted: Mon Jun 02, 2008 3:22 pm 
Expert
Expert

Joined: Thu Dec 14, 2006 5:57 am
Posts: 1185
Location: Zurich, Switzerland
Hi Phil,

sorry, I don't have a example for you, because I only used the "table-per-class-hierarchy" hierarchy yet. Since you can't change the classes, I assume you also can't change the tables, so "table-per-concrete-class" isn't usable here, since you have to use an "any" association to link parent and child which would need a colum, indicating the class type. Only thing I can think of, is mapping the complete interfaces, without a subclass. If you don't have more than one implementing class, this might work. You can try to skip the discriminator then. "table-per-subclass" would give you at least two tables or (if that's working at all) a self-join, which would be a quit awkward solution.

_________________
--Wolfgang


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 9 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.