I have a problem trying to serialize a graph with cyclic references. This is my scenario:
[DataContract(IsReference = true)]
public class Child
{
[DataMember]
public string name { get; set; }
[DataMember]
public Parent parent { get; set; }
}
[DataContract(IsReference = true)]
public class Parent
{
[DataMember]
public string name { get; set; }
[DataMember]
public List<Child> children { get; set; }
public Parent()
{
children = new List<Child>();
}
}
//Testing method
//Invoke it through WCF Service (basocHttpBinding)
public Parent Test()
{
Parent p = new Parent();
p.name = "Pepe";
p.children.Add(new Child { name = "Juan", parent = p });
return p;
}
When I try to serialize a Object of type Parent, it produces an System.StackOverflowException, because the Cyclic reference.
If I delete in the Child class the DataMember of his navigation property to the Parent, It works perfectly, but I lose the navigation property in the serializated object. I have been researching for a solution, but no Luck. I only found that IsReference Property solves this type of Cyclic reference, but in my case it seems not to work correctly.
Some help will be welcomed!!
Thanks in advance
PARENT CLASS:
[DataContract(IsReference = true)]
[KnownType(typeof(Expediente))]
public partial class Expediente: POCO, IEntidad
{
public Expediente() : base(typeof(Expediente))
{
}
public virtual int Sid
{
get;
set;
}
[DataMember]
public virtual int NumExpediente
{
get;
set;
}
[DataMember]
public virtual ICollection<Policia> Policias
{
get
{
if (_policias == null)
{
var newCollection = new FixupCollection<Policia>();
newCollection.CollectionChanged += FixupPolicias;
_policias = newCollection;
}
return _policias;
}
set
{
if (!ReferenceEquals(_policias, value))
{
var previousValue = _policias as FixupCollection<Policia>;
if (previousValue != null)
{
previousValue.CollectionChanged -= FixupPolicias;
}
_policias = value;
var newValue = value as FixupCollection<Policia>;
if (newValue != null)
{
newValue.CollectionChanged += FixupPolicias;
}
}
}
}
private ICollection<Policia> _policias;
private void FixupPolicias(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
{
foreach (Policia item in e.NewItems)
{
item.Expediente = this;
}
}
if (e.OldItems != null)
{
foreach (Policia item in e.OldItems)
{
if (ReferenceEquals(item.Expediente, this))
{
item.Expediente = null;
}
}
}
}
}
CHILD CLASS:
[DataContract(IsReference = true)]
[KnownType(typeof(Policia))]
public partial class Policia: POCO, IEntidad
{
//Constructor por defecto
public Policia() : base(typeof(Policia))
{
}
[DataMember]
public virtual int Sid
{
get { return _sid; }
set
{
if (_sid != value)
{
if (Expediente != null && Expediente.Sid != value)
{
Expediente = null;
}
_sid = value;
}
}
}
private int _sid;
[DataMember]
public virtual int NumPlaca
{
get;
set;
}
[DataMember]
public virtual Expediente Expediente
{
get { return _expediente; }
set
{
if (!ReferenceEquals(_expediente, value))
{
var previousValue = _expediente;
_expediente = value;
FixupExpediente(previousValue);
}
}
}
private Expediente _expediente;
private void FixupExpediente(Expediente previousValue)
{
if (previousValue != null && previousValue.Policias.Contains(this))
{
previousValue.Policias.Remove(this);
}
if (Expediente != null)
{
if (!Expediente.Policias.Contains(this))
{
Expediente.Policias.Add(this);
}
if (Sid != Expediente.Sid)
{
Sid = Expediente.Sid;
}
}
}
}
To serialize I am using standar DataContractSerializer throught WCF. I only added some behaviour to my WCF services through an attribute to Serilize Poco Proxies. This is the attribute code:
public class ApplyDataContractResolverAttribute : Attribute, IOperationBehavior
{
public ApplyDataContractResolverAttribute()
{
}
public void AddBindingParameters(OperationDescription description, BindingParameterCollection parameters)
{
}
public void ApplyClientBehavior(OperationDescription description, System.ServiceModel.Dispatcher.ClientOperation proxy)
{
DataContractSerializerOperationBehavior dataContractSerializerOperationBehavior =
description.Behaviors.Find<DataContractSerializerOperationBehavior>();
dataContractSerializerOperationBehavior.DataContractResolver =
new ProxyDataContractResolver();
}
public void ApplyDispatchBehavior(OperationDescription description, System.ServiceModel.Dispatcher.DispatchOperation dispatch)
{
DataContractSerializerOperationBehavior dataContractSerializerOperationBehavior =
description.Behaviors.Find<DataContractSerializerOperationBehavior>();
dataContractSerializerOperationBehavior.DataContractResolver =
new ProxyDataContractResolver();
}
public void Validate(OperationDescription description)
{
}
}
POCO base Class. A simple Class with some base Validation functionallity
[DataContract(IsReference = true)]
public class POCO
{
public POCO(Type Tipo)
{
RegistrarMetadatos(Tipo);
}
/// <summary>
/// Este metodo valida la clase y devuelve todos los mensajes de validacion.
/// </summary>
/// <returns>Devuelve una Lista de tipo ValidationResults con todos los resultados de la validación</returns>
public List<ValidationResult> Validar()
{
List<ValidationResult> Resultados = new List<ValidationResult>();
ValidationContext ctx = new ValidationContext(this, null, null);
Validator.TryValidateObject(this, ctx, Resultados, true);
return Resultados;
}
/// <summary>
/// Este metodo valida la clase y en caso de no superar la validación levante una excepción del tipo ValidationEx
/// </summary>
public void TryValidar()
{
List<ValidationResult> Resultados = new List<ValidationResult>();
ValidationContext ctx = new ValidationContext(this, null, null);
Validator.TryValidateObject(this, ctx, Resultados, true);
if (Resultados.Count > 0)
{
throw new ValEx(Resultados);
};
}
/// <summary>
/// Este metodo busca la clase interna que contiene los metadatos con las
/// Validaciones y las adhiere a la clase principal. Parae ello usa la convención "MD"
/// <param name="Tipo">El tipo de la clase que sobre la que se quiere registar los metadatos</param>
/// </summary>
private void RegistrarMetadatos(Type Tipo)
{
string aux = Tipo.AssemblyQualifiedName;
aux = aux.Replace(Tipo.FullName, Tipo.FullName + "+" + Tipo.Name +"MD");
Type Meta = Type.GetType(aux);
if (Meta != null)
{
var DescProv =
new AssociatedMetadataTypeTypeDescriptionProvider(
Tipo, Meta
);
TypeDescriptor.AddProviderTransparent(DescProv, Tipo);
}
}
}