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.  [ 8 posts ] 
Author Message
 Post subject: bidirectional mapping - webservices and circular objects
PostPosted: Tue Apr 15, 2008 8:42 am 
Newbie

Joined: Mon Apr 14, 2008 10:22 am
Posts: 6
hi to everybody. i'm new in nhibernate developement and got some real problems solving this error. At first i have to excuse my bad english but i have to try because after a couple of days now i'm near to give up for this or build a workaround.
i prepared a small app just to have a try how it should work. reading a lot in the documentation and on other websites i solved a lot of errors. in the end i always came to the same point an could not find a solution. if there is already a post in here i'm sorry cause i haven't found it yet.

the main problem right now is a circular reference wich produces a error when i debug my webservice. i have a typical parent-child collection should be seralized by the webservice. here the main parts in my app. the hibernate stuff is placed in an assembly and will be accessed by an web app.

Mapping documents:

Child:
Code:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" namespace="basis.DataClasses" assembly="basis">
  <class name="basis.DataClasses.Child" table="t_child">

  <id name="Id" column="child_id" type="Int32">
    <generator class="identity" />
  </id>

  <property name="Name" column="child_name" type="String" not-null="true" />
 
  <many-to-one name="TheParent" class="basis.DataClasses.Parent" column="parent_id" not-null="true" />
   
</class>
</hibernate-mapping>


Parent:
Code:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" namespace="basis.DataClasses" assembly="basis">
  <class name="basis.DataClasses.Parent" table="t_parent">

    <id name="Id" column="parent_id" type="Int32">
      <generator class="identity" />
    </id>

    <property name="Name" column="parent_name" type="String" not-null="true" />

    <bag name="TheChildren" inverse="true">
      <key column="parent_id"/>
      <one-to-many class="basis.DataClasses.Child"/>
    </bag>

  </class>
</hibernate-mapping>


Data Classes:

Child:
Code:
public class Child
    {
        #region KEYS

        private int _id;
        public virtual int Id
        {
            get { return _id; }
            set { _id = value; }
        }

        private int _parent_id;
        public virtual int Parent_id
        {
            get { return _parent_id; }
            set { _parent_id = value; }
        }


        #endregion

        #region ATTRIBUTES

        private string _name;
        public virtual string Name
        {
            get { return _name; }
            set { _name = value; }
        }

        #endregion

        #region CONSTRUCTORS

        public Child() { }

        #endregion

        #region RELATED

        private Parent theParent;
        public virtual Parent TheParent
        {
            get { return theParent; }
            set { theParent = value; }
        }

        #endregion

    }


Parent:
Code:
[XmlInclude(typeof(Child))]
public class Parent
    {
        #region KEYS

        private int _id;
        public virtual int Id
        {
            get { return _id; }
            set { _id = value; }
        }

        #endregion

        #region ATTRIBUTES

        private string _name;
        public virtual string Name
        {
            get { return _name; }
            set { _name = value; }
        }
       
        #endregion

        #region CONSTRUCTORS

        public Parent() { }

        #endregion

        #region RELATED

        private IList theChildren;
        public virtual IList TheChildren
        {
            get { return theChildren; }
            set { theChildren = value; }
        }

        #endregion

    }


Session Provider:[b]
The Session is stored in HttpContext an closed by the global.asax on Request_end.

[b]controllerClass examples[b]
for each data class i produced a controllerClass which inherits a basic DAO class for insert/update and save. here an example for the parent.
Code:
public class CParent : BaseDAO
    {
        public Parent GetById(object id)
        {
            return (Parent)GetItem(typeof(Parent), id);
        }

        public List<Parent> GetAll()
        {
            List<Parent> list = new List<Parent>();

            IList result = GetItems(typeof(Parent));

            foreach (Parent item in result)
            {
                list.Add(item);
            }

            return list;
        }
    }


and the method GetItems in BaseDAO (session is not really closed)
Code:
public virtual IList GetItems(Type type)
        {
            IList items = null;
            ISession session = SessionProvider.GetCurrentSession();
            ITransaction trans = null;

            try
            {
                trans = session.BeginTransaction();

                items = session.CreateCriteria(type).List();

                trans.Commit();

                return items;
            }
            catch (Exception ex)
            {
                trans.Rollback();

                throw new DataAccessException("Error getting items", ex);
            }
            finally
            {
                SessionProvider.CloseSession();
            }

        }


[b]rest of code[b]
i created another class which is called by the webservice.
Code:
public Parent[] getAllParents()
        {
            CChild childController = new CChild();
            CParent parentController = new CParent();

            List<Parent> parentList = parentController.GetAll();

            Parent[] parentArray = new Parent[parentList.Count];
            int i = 0;
            foreach (Parent _item in parentList)
            {
                NHibernateUtil.Initialize(_item);
                NHibernateUtil.Initialize(_item.TheChildren);
                parentArray[i++] = _item;
            }
            return parentArray;
        }


and finally the webmethod in the web app (which includes the assembly) just return an array from the method before
Code:
[WebMethod]
    public Parent[] Parents()
    {
        AppInit _appinit = new AppInit();
        return _appinit.getAllParents();
    }


[b]Hibernate version: 1.2.1.4000

Dev Enviroment: Visual Studio 2005 / MSSQL Server

now here is the exception. if i have not read about all these lazy loading stuff and possibility to create bidirectional mapping files i would expect the error because it is logical in way. i really have tried a lot of things and now i'm at the point that i have missed something.

Full stack trace of any exception that occurs:
System.InvalidOperationException: There was an error generating the XML document. ---> System.InvalidOperationException: A circular reference was detected while serializing an object of type basis.DataClasses.Parent.
at System.Xml.Serialization.XmlSerializationWriter.WriteStartElement(String name, String ns, Object o, Boolean writePrefixed, XmlSerializerNamespaces xmlns)
at Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationWriter1.Write3_Parent(String n, String ns, Parent o, Boolean isNullable, Boolean needType)
at Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationWriter1.Write2_Child(String n, String ns, Child o, Boolean isNullable, Boolean needType)
at Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationWriter1.Write1_Object(String n, String ns, Object o, Boolean isNullable, Boolean needType)
at Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationWriter1.Write3_Parent(String n, String ns, Parent o, Boolean isNullable, Boolean needType)
at Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationWriter1.Write4_ArrayOfParent(Object o)
at Microsoft.Xml.Serialization.GeneratedAssembly.ArrayOfParentSerializer.Serialize(Object objectToSerialize, XmlSerializationWriter writer)
at System.Xml.Serialization.XmlSerializer.Serialize(XmlWriter xmlWriter, Object o, XmlSerializerNamespaces namespaces, String encodingStyle, String id)
--- End of inner exception stack trace ---
at System.Xml.Serialization.XmlSerializer.Serialize(XmlWriter xmlWriter, Object o, XmlSerializerNamespaces namespaces, String encodingStyle, String id)
at System.Xml.Serialization.XmlSerializer.Serialize(TextWriter textWriter, Object o)
at System.Web.Services.Protocols.XmlReturnWriter.Write(HttpResponse response, Stream outputStream, Object returnValue)
at System.Web.Services.Protocols.HttpServerProtocol.WriteReturns(Object[] returnValues, Stream outputStream)
at System.Web.Services.Protocols.WebServiceHandler.WriteReturns(Object[] returnValues)
at System.Web.Services.Protocols.WebServiceHandler.Invoke()



i hope this is enough to get the point. so what is my mistake. did i understood the whole thing in a wrong way or is it just a small mistake in the code files. please do inform me if a created the architecture in right way.

thank for answers even if they should be: read the manual again.


Top
 Profile  
 
 Post subject:
PostPosted: Tue Apr 15, 2008 9:11 am 
Expert
Expert

Joined: Thu Dec 14, 2006 5:57 am
Posts: 1185
Location: Zurich, Switzerland
The exceptions sounds like a problem with the serialization of your objects caused by the web service. I would say, that has nothing to do with hibernate (the mapping looks correct). I think the XmlSerializer walks through the parent object and serializes the children, where it fails to serialize "TheParent", which in fact would result in a circular reference.

I'm not sure how to solve your problem. Look up the documentation of the XmlSerializer to prevent it from serializing the "TheParent" link. You can try [XmlIgnore] on "TheParent" but that will result in a null reference when you deserialize the objects, I think ...

_________________
--Wolfgang


Top
 Profile  
 
 Post subject:
PostPosted: Tue Apr 15, 2008 9:23 am 
Newbie

Joined: Mon Apr 14, 2008 10:22 am
Posts: 6
thanks a lot for your fast reply. i already thought about the xmlignore with the same result as you said. this is not really a solution. so if the problem is the xml serializer is their a way to cut the relation from child back to parent in this case.
i was thinking about the option to set the max_fetch_depth to 0 but this didn't help a all.
so i think the solution is to create the mapping file just unidirectional and create some getter setter for the mapping the other way round.
as i said i already spend a lot of time searching a solution.

but how is it in the end. i tought hibernate would just load the attribute won access with the lazy loading. so if i just initialize the first level and pass these objects to the webservice why is the seralizer trying to go further?

but anyway if this is a problem with the xml stuff my only chance is to have a workaround with the getter setter and th one way mapping


Top
 Profile  
 
 Post subject:
PostPosted: Tue Apr 15, 2008 10:11 am 
Expert
Expert

Joined: Thu Dec 14, 2006 5:57 am
Posts: 1185
Location: Zurich, Switzerland
Quote:
so if the problem is the xml serializer is their a way to cut the relation from child back to parent in this case.

You can map the collection as uni-directional, which removes "TheParent" from the child objects. Just skip the many-to-one association in Child and remove the inverse attribute on the collection in Parent. But you also loose the not null constraint on the parent_id column, because now hibernate will first insert the child and then update the row with the parent id, because the association is owned by the Parent object.

Quote:
i was thinking about the option to set the max_fetch_depth to 0 but this didn't help a all.

With that parameter you can configure the depth for join fetches, which does not have any effect on your problem.

Quote:
but how is it in the end. i tought hibernate would just load the attribute won access with the lazy loading. so if i just initialize the first level and pass these objects to the webservice why is the seralizer trying to go further?

I suppose, the serializer "makes" the get and hibernate does what it has to do in that case, it loads the object.

_________________
--Wolfgang


Top
 Profile  
 
 Post subject:
PostPosted: Tue Apr 15, 2008 11:29 am 
Newbie

Joined: Mon Apr 14, 2008 10:22 am
Posts: 6
thanks again for your reply. about creating the mapping files just one way is not really such a nice way to solve this i think. this will end up in creating a lot of getter and setter methods to access the related attributes.
if there is no mistake in the way i did this example my decission goes to create an extra layer which gets data from hibernate and passes the real needed business object to the webservice. i do not really like the idea of mapping the data twice but in the end it seems to be more efficient and clear in the design of the whole application. the other way would be breaking up the advantages hibernate offers.
so am i right in this way that there is no way around these two possibilities.

breaking up the bidirectional mapping and get the data by own getter and setter in the controllerClass

create an extra layer to map the real needed data again.

sorry for asking again but i have to decide this for a real big application. so a wrong decission will cause a lot of work if i strated the wrong way.


Top
 Profile  
 
 Post subject:
PostPosted: Tue Apr 15, 2008 11:39 am 
Expert
Expert

Joined: Thu Dec 14, 2006 5:57 am
Posts: 1185
Location: Zurich, Switzerland
I do not have enough experience using XmlSerialzer and webservices, so I don't want to push you into any direction. In my opinion the hibernate part is ok as you initially planned it (bi-directional mapping). Doing it this or that way is both "valid". If you really need the parent link on the children, I would do it with the mapping.
Some thoughts to the serialization. Before separating the whole thing, you can try [XmlIgnore] on "TheParent" and "fix" the null reference after deserializing. I suppose somewhere after deserializing the object, you know, that you deal with a Parent. Then you can iterate through the children and set "TheParent" to it's original value.

_________________
--Wolfgang


Top
 Profile  
 
 Post subject:
PostPosted: Tue Apr 15, 2008 4:31 pm 
Expert
Expert

Joined: Tue Aug 23, 2005 5:52 am
Posts: 335
If I remember correctly, there is a way to get around this but it will harm your interoperability.

You can change the way your web methods are bound to the WSDL to use RPC/Literal style encoding which supports circular references. I wouldn't recommend it, but I think it addresses your question.

See here for further info:

http://msdn2.microsoft.com/en-us/library/dkwy2d72(VS.71).aspx

Honestly though, I'd consider using DTOs that don't represent the circular reference instead if you're going to transmit object data across a service boundary.

Cheers,

Symon.

_________________
Symon Rottem
http://blog.symbiotic-development.com


Top
 Profile  
 
 Post subject:
PostPosted: Wed Apr 16, 2008 10:39 am 
Newbie

Joined: Mon Apr 14, 2008 10:22 am
Posts: 6
for everybody who is interessted in this i found a solution handling the circular objects. i was thinking a little more about the problem and woke up this morning with the idea of setting the xmlignore tag dynamically in the webservice. so i just changed the return type of the webservice to xmldocument and serialized the objects by myself. now i'm able to work with the xmlattributeoverrides and can decide to ignore an attribute directly before the serialization. here the code a just prepared to test if this is working:

Code:
[WebMethod]
    public XmlDocument Childs()
    {
        AppInit _appinit = new AppInit();
        Child[] childArray=_appinit.getAllChilds();
       
        StringWriter strW = new StringWriter();
        XmlSerializer s = getXmlSerializer(typeof(Child[]), typeof(Parent), "TheChildren");
        s.Serialize(strW,childArray);

        XmlDocument xmlD = new XmlDocument();
        xmlD.LoadXml(strW.ToString());

        return xmlD;
    }

    public XmlSerializer getXmlSerializer(Type serializerType,Type ignoreClassType, String ignoreItem)
    {
        XmlAttributeOverrides xmlOver = new XmlAttributeOverrides();
        XmlAttributes xmlAtt = new XmlAttributes();

        xmlAtt.XmlIgnore = true;
        xmlOver.Add(ignoreClassType, ignoreItem, xmlAtt);

        XmlSerializer xmlSer = new XmlSerializer(serializerType, xmlOver);
        return xmlSer;
    }


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