Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Getting JSON Serialization Entity Framework Self Reference Loop error even after ProxyCreation false when using explicit Include

JSON Serialization (ASP.Net Web API) fails because of self-referencing loop (it’s a common problem, Reason: an entity being requested lazy loads child entities and every child has a back reference to parent entity).

Work around I found, but doesn’t help me:

  1. Use [JsonIgnore] for navigation properties to be ignored: This solution works but doesn’t apply in my case. For Example: To get a Customer information along with his Orders, I would quickly add [JsonIgnore] to Customer property in Order class, but when I want to get an Order information along with the Customer details, since there’s [JsonIgnore] on Customer property, it won’t include Customer details.
  2. Change JSON.Net Serializer Settings to Preserve References: Can’t Preserve because I don’t need Circular referenced data.
  3. Disable Proxy Creation at the Data Context and use explicit loading(this should ideally solve the problem): Disabling proxy creation stops Lazy Loading and returns data without error, but when I explicitly Include child entities, I again the get the unexpected self-referencing loop error! The error is at the back-reference level to parent entity.

Any experiences along the same lines/suggestions?

like image 1000
Akash Budhia Avatar asked Jun 05 '13 20:06

Akash Budhia


3 Answers

I tried all the suggested solutions but didn't work. Ended up with Overriding the JSON.Net Serializer’s DefaultContractResolver to this:

public class FilterContractResolver : DefaultContractResolver
{
    Dictionary<Type, List<string>> _propertiesToIgnore;

    public FilterContractResolver(Dictionary<Type, List<string>> propertiesToIgnore)
    {
        _propertiesToIgnore = propertiesToIgnore;
    }

    protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
    {
        var property = base.CreateProperty(member, memberSerialization);
        List<string> toIgnore;
        property.Ignored |= ((_propertiesToIgnore.TryGetValue(member.DeclaringType, out toIgnore) || _propertiesToIgnore.TryGetValue(member.DeclaringType.BaseType, out toIgnore)) && toIgnore.Contains(property.PropertyName));
        return property;
    }
}

Then created a Static Class which returns a dictionary of Properties to be Ignored based on the Controller:

public static class CriteriaDefination
{
    private static Dictionary<string, Dictionary<Type, List<string>>> ToIgnore = new Dictionary<string, Dictionary<Type, List<string>>>
    {
        {
            "tblCustomer", new Dictionary<Type, List<string>>{
                {
                    typeof(tblCustomer), new List<string>{
                        //include all
                    }
                },
                {
                    typeof(tblOrder), new List<string>{
                        "tblCustomer"//ignore back reference to tblCustomer
                    }
                }
            }
        },
        {
            "tblOrder", new Dictionary<Type, List<string>>{
                {
                    typeof(tblCustomer), new List<string>{
                        "tblOrders"//ignore back reference to tblOrders
                    }
                },
                {
                    typeof(tblOrder), new List<string>{
                        //include all
                    }
                }
            }
        }
    };
    public static Dictionary<Type, List<string>> IgnoreList(string key)
    {
        return ToIgnore[key];
    }
}

And inside every controller change the JSON Formatter something like:

GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.ContractResolver = new FilterContractResolver(CriteriaDefination.IgnoreList("tblCustomer"));
like image 153
Akash Budhia Avatar answered Oct 11 '22 15:10

Akash Budhia


This is what I ended up settling on, hopefully it helps someone else.

Say the EF classes are structured like this:

public partial class MyEF
{
  public virtual ICollection<MyOtherEF> MyOtherEFs {get; set;}
}
public partial class MyOtherEF
{
  public virtual MyEF MyEF {get; set;}
}

To keep serialization form happening in JSON.NET, you can extend the class and add a method with the name "ShouldSerialize" + property name like so:

public partial class MyEF
{
  public bool ShouldSerializeMyOtherEFs() { return false; }
}

If you wanted to get a little more fancy, you could add logic in the method so that it would serialize in certain cases. This allows you to keep serialization logic out of the EF Model First code creation as long as this code is in a different physical code file.

like image 2
Shane Bishop Avatar answered Oct 11 '22 16:10

Shane Bishop


Instead of letting the Entity Framework generate the model, use Code First with an existing database. Now you are more in control.

See this blog entry from Scott Guthrie

like image 1
oeoren Avatar answered Oct 11 '22 15:10

oeoren