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]