-->
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.  [ 4 posts ] 
Author Message
 Post subject: Many-to-many relationship and XmlSerialization
PostPosted: Fri Aug 12, 2005 1:26 pm 
Beginner
Beginner

Joined: Sat Apr 17, 2004 1:11 am
Posts: 36
All code and exceptions are below the text here. There are other classes in this project but the included code only deals with the problem many-to-many relationship.

I'm doing some testing of NH to see if it suits our needs for a persistence layer for our app. I have a framework whereby you can define services that can run locally, as a .NET Remoted process or as a web service. I've got a schema and mapping that works fine locally but when I get objects that have a many-to-many relationship the XmlSerializer reports a circular reference exception.

Everything works fine when run locally, i.e. the objects are not run through
the XmlSerializer. The lazy-loading works fine (I have to get a session and hold it until after I touch the lazy-loaded collection) and all of the modeled relationships work fine, including the many-to-many shown here.

I also had to use a bag for many-to-many to work at all as a set gave me an exception when trying to retrieve data:

Code:
Unhandled Exception: Framework.Component.FrameworkException: Exception of type 'Framework.Component.FrameworkException' was thrown. ---> Sy
stem.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.InvalidOperationException: Method NHibernateDataAccessImplWebServiceProxy.CreateObjectPersistentObjecto can not be reflected. ---> System.InvalidOperationException: There was an error reflecting 'o
'. ---> System.InvalidOperationException: There was an error reflecting type 'BOM.PersistentObject'. ---> System.InvalidOperationException: There w
as an error reflecting type 'BOM.Office'. ---> System.InvalidOperationException: You must implement a default accessor on Iesi.Collections.ISet bec
ause it inherits from ICollection.
   at System.Xml.Serialization.TypeScope.GetDefaultIndexer(Type type, String memberInfo)
   at System.Xml.Serialization.TypeScope.ImportTypeDesc(Type type, MemberInfo memberInfo, Boolean directReference)
   at System.Xml.Serialization.TypeScope.GetTypeDesc(Type type, MemberInfo source, Boolean directReference)


Am I missing the m-t-m mantra? Implementing it incorrectly? Missing something I need to properly serialize these types of relationships? Mapping incorrectly? I would think that Employee would have a collection of Office objects and an Office would have a collection of Employee objects and that's what's happening properly except for the serialization exercise.

I mean, it's somewhat understandable that this is happening. For e.g., my ToString() implementations in each objects can't just blindly call the ToString() of objects in associated many-to-many collections as you get a stack overflow with the circular reference there. I've just got to believe that there's some way around this as the code works fine locally and there would be no reason not to model something this way except for this hiccup.

Thx...

Here's the code that creates the objects in the first place:

Code:
Employee newEmp = new Employee();
                newEmp.TaxFileNumber = "TaxFileNumber";
                Name n = new Name();
                n.FirstName = "First";
                n.Initial = "I";
                n.LastName = "Last";
                newEmp.LName = n;
                 
                Office newOffice = new Office();
                newOffice.BuildingNumber = "Building1";

                newEmp.Offices.Add(newOffice);
                newOffice.Employees.Add(newEmp);

                dataAccess.CreateObject(newEmp);
                Console.WriteLine("Created New Employee");

                dataAccess.CreateObject(newOffice);
                Console.WriteLine("Created New Office");

...

the CreateObject() call essentially is doing a:

   newObject = (Object)session.Save(o);

with some session mgmt around it.


The two classes and the common base class...

Code:
using System;
using System.Runtime.Serialization;
using System.Xml.Serialization;
namespace BOM
{
    [Serializable]
    [System.Xml.Serialization.XmlTypeAttribute(Namespace = "http://tempuri.org/")]
XmlInclude(typeof(BOM.Office)), XmlInclude(typeof(BOM.Employee)) ]
    public class PersistentObject
    {
        public PersistentObject()
        {
        }

        public override string ToString()
        {
            return "PersistentObject:";
        }
    }
}

=============

using System;
using System.Collections;
using System.Runtime.Serialization;
using System.Text;
using Iesi.Collections;

namespace BOM
{
    [Serializable]
    [System.Xml.Serialization.XmlTypeAttribute(Namespace = "http://tempuri.org/")]
    public class Employee : PersistentObject
    {
        private Int32 _Id;
        private string _TaxFileNumber;
        private Name _Name;
        private IList _Offices = new ArrayList();

        public Int32 Id
        {
            get { return _Id; }
            set { _Id = value; }
        }

        public string TaxFileNumber
        {
            get { return _TaxFileNumber; }
            set { _TaxFileNumber = value; }
        }

        public Name LName
        {
            get { return _Name; }
            set { _Name = value; }
        }

        virtual public IList Offices
        {
            get { return _Offices; }
            set { _Offices = value; }
        }

        public Employee()
        {
        }

    }
}

====

using System;
using System.Collections;
using System.Runtime.Serialization;
using System.Text;
using Iesi.Collections;

namespace BOM
{
    [Serializable]
    [System.Xml.Serialization.XmlTypeAttribute(Namespace = "http://tempuri.org/")]
    public class Office : PersistentObject
    {
        private Int32 _Id;
        private string _BuildingNumber;
        private IList _Employees = new ArrayList();

        public Int32 Id
        {
            get { return _Id; }
            set { _Id = value; }
        }

        public string BuildingNumber
        {
            get { return _BuildingNumber; }
            set { _BuildingNumber = value; }
        }

        virtual public IList Employees
        {
            get { return _Employees; }
            set { _Employees = value; }
        }

        public Office()
        {
        }

    }
}



Mapping for these two classes...

Code:
<class name="BOM.Employee,BOM" table="employees">
      <id name="Id">
         <generator class="NHibernate.Id.TableHiLoGenerator">
            <param name="table">lastemployees</param>
            <param name="column">LASTID</param>
         </generator>
      </id>
      <property name="TaxFileNumber"/>
      <component name="LName" class="BOM.Name,BOM">
         <property name="FirstName"/>
         <property name="Initial"/>
         <property name="LastName"/>
      </component>
      <bag name="Offices" table="employee_office" lazy="true">
         <key>
            <column name="employee_id" not-null="true"/>
         </key>
         <many-to-many class="BOM.Office,BOM">
            <column name="office_id" not-null="true"/>
         </many-to-many>
      </bag>
   </class>

   <class name="BOM.Office,BOM" table="offices">
      <id name="Id">
         <generator class="NHibernate.Id.TableHiLoGenerator">
            <param name="table">lastoffices</param>
            <param name="column">LASTID</param>
         </generator>
      </id>
      <property name="BuildingNumber"/>
      <bag name="Employees" table="employee_office" lazy="true">
         <key column="office_id"/>
         <many-to-many class="BOM.Employee,BOM" column="employee_id"/>
      </bag>
   </class>


The exception...

Code:
Unhandled Exception: System.Web.Services.Protocols.SoapException: System.Web.Services.Protocols.SoapException: Server was unable to process request. ---> System.InvalidOperationException: There was an error generating the XML document. ---> System.InvalidOperationException: A circular reference was detected while serializing an object of type Employee.
[/code]


Top
 Profile  
 
 Post subject:
PostPosted: Sat Aug 13, 2005 4:21 am 
Contributor
Contributor

Joined: Wed May 11, 2005 4:59 pm
Posts: 1766
Location: Prague, Czech Republic
I don't have much experience with XML serialization, but from what I read there will be problems with serializing dictionaries and sets. Also, to break circular references I believe you have to mark one of the properties using XmlIgnore attribute.

Hopefully somebody with more experience will correct me if I'm wrong.


Top
 Profile  
 
 Post subject:
PostPosted: Mon Aug 15, 2005 11:15 pm 
Beginner
Beginner

Joined: Mon Aug 15, 2005 11:09 pm
Posts: 23
Ok first off. Yeah Sergey's right on the money. I haven't used remoting but for webservices you'll want to import System.Xml.Serialization and add a XmlIgnoreAttribute() attribute to collections and references which are going to be circular.

The other option is to write your own object graph serialization methods, but that


Top
 Profile  
 
 Post subject:
PostPosted: Tue Aug 16, 2005 10:27 am 
Beginner
Beginner

Joined: Sat Apr 17, 2004 1:11 am
Posts: 36
Thx for the responses. I ended up solving this by taking advantage of the fact that we've already built a framework around the local, remote and web service deployment scenarios. I generate everything from an interface and implementation and all of the different things that get deployed use the BL in the implementation. I ended up in the generated web service using the BinaryFormatter to serialize the value coming back from the web service and stuffing it into a Payload Object. Then the web service proxy that I generate on the client side gets the Payload Object, deserializes the data and casts it to the returned Type and ships it back to the caller. This also means that we get some performance benefits as we're not shipping back XML but a Base64 encoded string from the BinaryFormatter. This avoids any issues with the XmlSerializer. It still runs but it's only serializing a Payload Object that has a Byte[] in it.

I think this works nicely. From the client perspective, you don't see any of this - no matter if the Component is deployed locally, remotely or as a web service you get your data and it's handled in the wrapper classes that enable this functionality. We won't be exposing these web services except through our framework (because it's a shrink-wrapped commercial product) so it won't matter that the return types are always these Payload Objects. If you use our framework then you see each service and each method appropriately. I know it'd be better to expose each method signature as it should exist but you also have to deal with method overloading and other issues which our stuff deals with as well.

Thx again for the responses...

benster


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