2
votes

I am trying to expose my data model (code first entity framework) via WCF web services over JSON. The model has multiple many-to-many relationships and has lazy loading enabled. Our web services should be able to return only the first level of multiple child objects. I continue to receive the following error:

"Cannot evaluate expression because the current thread is in a stack overflow state."

I realize that this error is occurs when serializing data that has a circular reference.

After reading another thread I attempted the following remedies to handle the circular reference:

  • WCF and DataContractSerializer: explicitly mark your entities with DataContract[IsReference=true] and all properties with [DataMember] attributes. This will allow you to use circular references. If you are using T4 template to generate entities you must modify it to add these attributes for you.
  • WCF and DataContractSerializer: implicit serialization. Mark one of related navigation properties with [IgnoreDataMember] attribute so that property is not serialized.
  • XmlSerializer: mark one fo related navigation properties with [XmlIgnore] attribute
  • Other serializations: mark one of related navigation properties with [NonSerialized] (+1 for Haz he was the first to mention this) for common serialization or [ScriptIgnore] for some JSON related serialization.

None of these different approaches have worked. All I really need is the first level of the children objects. I do not need the children of the children so to speak. I added the following code to manually break the references to the child objects of the first level children, and this worked but is not a valid solution:

    [WebInvoke(Method = "POST", ResponseFormat = WebMessageFormat.Json, UriTemplate = "GetReportTypes")]
    public List<ReportType> GetReportTypes()
    {
        List<ReportType> result = BusinessLogic.GetReportTypes(_context).ToList();
        foreach (var x in result)
        {
            foreach (var y in x.Sections)
            {
                y.ReportType = null;
            }

        };
        return result;
    }

I am looking for a solid approach to handling the serialization of objects with many-to-many relationships in EF exposed via WCF -OR- simply to just break the references below the first level children. I am open to making changes to the data model or reconfiguring the web service somehow.

For completeness, listed below is all of my relevant code:

[DataContract(IsReference = true)]
[Table("ReportType", Schema = "Reporting")]
public class ReportType
{
    [Key]
    [Column("ID")]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    [DataMember]
    public int Id { get; set; }

    [DataMember]
    public virtual ICollection<ReportTypeSection> Sections { get; set; }
}


[Table("Section", Schema = "Reporting")]
[DataContract(IsReference = true)]
public class Section
{
    [Key]
    [Column("ID")]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    [DataMember]
    public int Id { get; set; }

    [DataMember]
    public virtual ICollection<ReportTypeSection> ReportTypes { get; set; }
}

[Table("ReportTypeSection", Schema = "Reporting")]
[DataContract(IsReference=true)]
public class ReportTypeSection
{
    [Column("ID")]
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    [DataMember]
    public int Id { get; set; }

    [Column("ReportTypeID")]
    [Required(ErrorMessage = "Report Type Section Foreign Key Report Type ID is Required")]
    [DataMember]
    public int ReportTypeId { get; set; }

    [Column("SectionID")]
    [Required(ErrorMessage = "Report Type Section Foreign Key Section ID is Required")]
    [DataMember]
    public int SectionId { get; set; }

    [DataMember]
    public virtual ReportType ReportType { get; set; }

    [DataMember]
    public virtual Section Section { get; set; }

}

public class BusinessLogic
{
    public static IEnumerable<ReportType> GetReportTypes(IDatabaseContext context)
    {
        IEnumerable<ReportType> result = context.ReportTypes
            .Include(a => a.Sections)
            .AsEnumerable();
        return result;
    }
}

public class ReportConfigurationService : IReportConfigurationService
{
    private IEmspeedDatabaseContext _context;
    public ReportConfigurationService(IEmspeedDatabaseContext context)
    {
        _context = context;
    }

    [WebInvoke(Method = "POST", ResponseFormat = WebMessageFormat.Json, UriTemplate = "GetReportTypes")]
    public List<ReportType> GetReportTypes()
    {
        List<ReportType> result = BusinessLogic.GetReportTypes(_context).ToList();
        return result;
    }
}

[ServiceContract]
public interface IReportConfigurationService
{

    [OperationContract]
    [ApplyDataContractResolver]
    List<ReportType> GetReportTypes();
}
2

2 Answers

2
votes

If you want just single level you must turn off lazy loading and use eager loading to load just a first level of children entities. This may still cause some backward serialization from child to parent if such navigation property exist. To fully avoid all serialization problems you must use DTOs (data transfer objects) which will model only data and unidirectional relations you want to use. You will fill DTOs in service operation and return it => you will have full control over serialization by structuring your DTO which will be independent on your entity model.

0
votes

Just avoid to put the [DataMember] attribute over the property who have the ciclic reference (In this case I use eagerly loading):

[DataContract]
public class Persona
{
    [DataMember]
    [Key]
    public int IdPersona { get; set; }
    [DataMember]
    public string Nombre { get; set; }
    [DataMember]
    public List<Domicilio> Domicilios { get; set; }
}

[DataContract]
public class Domicilio
{
    [DataMember]
    [Key]
    public int IdDomicilio { get; set; }
    [DataMember]

    public Persona persona { get; set; }
}