580

I tried to serialize POCO class that was automatically generated from Entity Data Model .edmx and when I used

JsonConvert.SerializeObject 

I got the following error:

Error Self referencing loop detected for type System.data.entity occurs.

How do I solve this problem?

Grigory Zhadko
  • 1,234
  • 1
  • 16
  • 29
NevenHuynh
  • 6,269
  • 5
  • 18
  • 25
  • 1
    possible duplicate of [Serialize one to many relationships in Json.net](http://stackoverflow.com/questions/5769200/serialize-one-to-many-relationships-in-json-net) – bkaid Sep 13 '11 at 05:37
  • when you are using Linq and MVC : http://stackoverflow.com/a/38241856 – aDDin Sep 05 '16 at 04:14
  • when using .NET Core 2: https://stackoverflow.com/a/48709134/4496145 – Dave Skender Jan 31 '19 at 00:06
  • 4
    This error happened to me, when I wanted to serialize the result of an `async` method call (a `Task`) and forgot to prefix the `await` statement. – Uwe Keim Mar 13 '19 at 06:45

26 Answers26

576

That was the best solution https://docs.microsoft.com/en-us/archive/blogs/hongyes/loop-reference-handling-in-web-api

Fix 1: Ignoring circular reference globally

(I have chosen/tried this one, as have many others)

The json.net serializer has an option to ignore circular references. Put the following code in WebApiConfig.cs file:

 config.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling 
= Newtonsoft.Json.ReferenceLoopHandling.Ignore; 

The simple fix will make serializer to ignore the reference which will cause a loop. However, it has limitations:

  • The data loses the looping reference information
  • The fix only applies to JSON.net
  • The level of references can't be controlled if there is a deep reference chain

If you want to use this fix in a non-api ASP.NET project, you can add the above line to Global.asax.cs, but first add:

var config = GlobalConfiguration.Configuration;

If you want to use this in .Net Core project, you can change Startup.cs as:

  var mvc = services.AddMvc(options =>
        {
           ...
        })
        .AddJsonOptions(x => x.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore);

Fix 2: Preserving circular reference globally

This second fix is similar to the first. Just change the code to:

config.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling 
     = Newtonsoft.Json.ReferenceLoopHandling.Serialize;     
config.Formatters.JsonFormatter.SerializerSettings.PreserveReferencesHandling 
     = Newtonsoft.Json.PreserveReferencesHandling.Objects;

The data shape will be changed after applying this setting.

[
   {
      "$id":"1",
      "Category":{
         "$id":"2",
         "Products":[
            {
               "$id":"3",
               "Category":{
                  "$ref":"2"
               },
               "Id":2,
               "Name":"Yogurt"
            },
            {
               "$ref":"1"
            }
         ],
         "Id":1,
         "Name":"Diary"
      },
      "Id":1,
      "Name":"Whole Milk"
   },
   {
      "$ref":"3"
   }
]

The $id and $ref keeps the all the references and makes the object graph level flat, but the client code needs to know the shape change to consume the data and it only applies to JSON.NET serializer as well.

Fix 3: Ignore and preserve reference attributes

This fix is decorate attributes on model class to control the serialization behavior on model or property level. To ignore the property:

 public class Category 
    { 
        public int Id { get; set; } 
        public string Name { get; set; } 
       
        [JsonIgnore] 
        [IgnoreDataMember] 
        public virtual ICollection<Product> Products { get; set; } 
    } 

JsonIgnore is for JSON.NET and IgnoreDataMember is for XmlDCSerializer. To preserve reference:

 // Fix 3 
        [JsonObject(IsReference = true)] 
        public class Category 
        { 
            public int Id { get; set; } 
            public string Name { get; set; } 
         
           // Fix 3 
           //[JsonIgnore] 
           //[IgnoreDataMember] 
           public virtual ICollection<Product> Products { get; set; } 
       } 
        
       [DataContract(IsReference = true)] 
       public class Product 
       { 
           [Key] 
           public int Id { get; set; } 
        
           [DataMember] 
           public string Name { get; set; } 
        
           [DataMember] 
           public virtual Category Category { get; set; } 
       }

JsonObject(IsReference = true)] is for JSON.NET and [DataContract(IsReference = true)] is for XmlDCSerializer. Note that: after applying DataContract on class, you need to add DataMember to properties that you want to serialize.

The attributes can be applied on both json and xml serializer and gives more controls on model class.

Pang
  • 9,073
  • 146
  • 84
  • 117
Bishoy Hanna
  • 11,358
  • 2
  • 33
  • 32
  • 10
    Fix 3 is worked for me. Just simply remove DataContract and DataMember attributes, and put JsonObject(IsReference = true) on DTOs. And it works. Thanks. – maestro May 15 '14 at 11:08
  • I'm sorry, Can you tell me: where is the ``config`` property from on your first solution? – qakmak Dec 02 '14 at 13:12
  • 1
    try this one GlobalConfiguration.Configuration – Bishoy Hanna Dec 04 '14 at 00:51
  • 1
    Fix 3 has the advantage that it works on client code where there is no GlobalConfiguration – dumbledad Aug 28 '15 at 16:27
  • 1
    @BishoyHanna, can you edit your answer to allow it to be used from normal ASP.NET applications? You can use my suggested edit: https://stackoverflow.com/review/suggested-edits/17797683 – NH. Nov 06 '17 at 21:20
  • 2
    Using `[JsonIgnore]` above the attribute worked for me. – Nathan Beck Sep 17 '18 at 18:14
  • 1
    Fix 3 is the best way to do this – brandonstrong Feb 13 '19 at 18:21
  • 1
    Great answer! "Fix 3" is the way to go. However, XML serialization still didn't work for me so I had to either [make the navigation properties non-virtual](https://stackoverflow.com/a/18360200/8967612) or [disable proxy creation for Web API controllers](https://stackoverflow.com/a/23372600/8967612) to support both JSON and XML. – 41686d6564 stands w. Palestine Apr 01 '20 at 06:44
  • Fix 3 may be attractive, but I believe Fix 3 also has the disadvantage of possibly modifying auto-generated code, thus the additional disadvantage of having to keep track of these changes if the code is ever regenerated. Serializing a model for final output to an API consumer, for example, should really never have need to maintain a circular reference, therefore Fix 1 is the way to go in that case. – iGanja Mar 31 '21 at 22:17
  • @BishoyHanna I wonder which is the best approach when it comes to updating an existing code base with a lot of serialization going for other data structures. Can one always assume it is safe for existing serialization to set ReferenceLoopHandling to Ignore? I would guess so since I would expect this should thrown an exception in some cases otherwise, right? – smn.tino Jun 16 '21 at 16:00
  • @smn.tino, Yes I believe thats the safest as it will not throw any exception because of loop – Bishoy Hanna Jun 18 '21 at 03:32
  • 1
    For me the 2nd option worked but only once I deleted the PreserveReferencesHandling part – Luis Gouveia Jul 28 '21 at 14:25
522

Use JsonSerializerSettings

  • ReferenceLoopHandling.Error (default) will error if a reference loop is encountered. This is why you get an exception.
  • ReferenceLoopHandling.Serialize is useful if objects are nested but not indefinitely.
  • ReferenceLoopHandling.Ignore will not serialize an object if it is a child object of itself.

Example:

JsonConvert.SerializeObject(YourPOCOHere, Formatting.Indented, 
new JsonSerializerSettings 
{ 
        ReferenceLoopHandling = ReferenceLoopHandling.Serialize
});

Should you have to serialize an object that is nested indefinitely you can use PreserveObjectReferences to avoid a StackOverflowException.

Example:

JsonConvert.SerializeObject(YourPOCOHere, Formatting.Indented, 
new JsonSerializerSettings 
{ 
        PreserveReferencesHandling = PreserveReferencesHandling.Objects
});

Pick what makes sense for the object you are serializing.

Reference http://james.newtonking.com/json/help/

DalSoft
  • 9,939
  • 3
  • 39
  • 50
  • 72
    I encountered the error when serializing a datatable. I used `ReferenceLoopHandling = ReferenceLoopHandling.Ignore` for it to work –  Dec 20 '12 at 15:09
  • 11
    If there are reference loops in the data, using `ReferenceLoopHandling.Serialize` will cause the serializer to go into an infinite recursive loop and overflow the stack. – Brian Rogers Jun 10 '14 at 14:23
  • 2
    Correct. As the question is about an EF model also a valid concern. Amended to give all available options. – DalSoft Jun 10 '14 at 23:00
  • 1
    I've encountered this same error when trying to serialize an object... however, the object doesn't have any references other than an enum type.. – Marin Feb 26 '15 at 11:35
  • 2
    for me EF is the main cause for this problem because self referenced entities are all over the place. – Teoman shipahi May 18 '15 at 04:15
  • For the problem in the question i.e. 'Self referencing loop detected with type', 3rd option works to fix it i.e. `ReferenceLoopHandling.Ignore` – viking Aug 17 '21 at 10:15
70

The fix is to ignore loop references and not to serialize them. This behaviour is specified in JsonSerializerSettings.

Single JsonConvert with an overload:

JsonConvert.SerializeObject(YourObject, Formatting.Indented,
    new JsonSerializerSettings() {
        ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
    }
);

Global Setting with code in Application_Start() in Global.asax.cs:

JsonConvert.DefaultSettings = () => new JsonSerializerSettings {
     Formatting = Newtonsoft.Json.Formatting.Indented,
     ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
};

Reference: https://github.com/JamesNK/Newtonsoft.Json/issues/78

smockle
  • 2,022
  • 16
  • 19
48

The simplest way to do this is to install Json.NET from nuget and add the [JsonIgnore] attribute to the virtual property in the class, for example:

    public string Name { get; set; }
    public string Description { get; set; }
    public Nullable<int> Project_ID { get; set; }

    [JsonIgnore]
    public virtual Project Project { get; set; }

Although these days, I create a model with only the properties I want passed through so it's lighter, doesn't include unwanted collections, and I don't lose my changes when I rebuild the generated files...

Sam Jones
  • 4,189
  • 2
  • 34
  • 41
21

In .NET Core 1.0, you can set this as a global setting in your Startup.cs file:

using System.Buffers;
using Microsoft.AspNetCore.Mvc.Formatters;
using Newtonsoft.Json;

// beginning of Startup class

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvc(options =>
        {
            options.OutputFormatters.Clear();
            options.OutputFormatters.Add(new JsonOutputFormatter(new JsonSerializerSettings(){
                ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
            }, ArrayPool<char>.Shared));
        });
    }
Caleb
  • 2,096
  • 3
  • 19
  • 31
17

If you're using .NET Core 2.x, update your ConfigureServices section in Startup.cs

https://docs.microsoft.com/en-us/ef/core/querying/related-data/serialization

    public void ConfigureServices(IServiceCollection services)
    {
    ...

    services.AddMvc()
        .AddJsonOptions(options => 
          options.SerializerSettings.ReferenceLoopHandling = 
            Newtonsoft.Json.ReferenceLoopHandling.Ignore
        );

    ...
    }

If you're using .NET Core 3.x - 5.0, without MVC, it would be:

services.AddControllers()
  .AddNewtonsoftJson(options =>
      options.SerializerSettings.ReferenceLoopHandling =
        Newtonsoft.Json.ReferenceLoopHandling.Ignore
   );

For .NET 6.0, the only difference is it now goes in Program.cs.

This reference loop handling is almost mandatory if you're using Entity Framework and database-first design pattern.

Dave Skender
  • 571
  • 4
  • 10
  • 4
    what if i dont use `services.AddMvc()`? – prisar Nov 22 '18 at 07:05
  • 2
    is this a bad practice? – Renan Coelho Dec 16 '18 at 19:33
  • 1
    At first glance you might think this is a bad practice as it might override "intentional design" of avoiding the old "infinite loop" problem. However, if you think about your use cases for classes, you may need them to refer to each other. For example, you may want to access both Trees>Fruits and also Fruits>Trees. – Dave Skender Oct 12 '19 at 20:08
  • 1
    Also, if you're using a database-first design pattern with something like Entity Framework, depending on how you setup your foreign keys in your database, it will automatically create these cyclical references, so you pretty much have to use this setting if you're reverse engineering your classes. – Dave Skender Oct 12 '19 at 20:10
10

We can add these two lines into DbContext class constructor to disable Self referencing loop, like

public TestContext()
        : base("name=TestContext")
{
    this.Configuration.LazyLoadingEnabled = false;
    this.Configuration.ProxyCreationEnabled = false;
}
Sanjay Nishad
  • 1,397
  • 13
  • 26
  • This is one of the simplest one and **working like a charm**. Voted up, thanks a lot... – Murat Yıldız Jul 04 '18 at 20:31
  • 1
    Like I wrote in the other question: I dont like this kind of answers because you are turning off a feature of EF6 that is enabled by default and this piece of code might break other parts of the program. You should explain what this does and what kind of repercussions it has. – El Mac Jul 26 '18 at 07:32
  • @ElMac you are right, but if we don't need that feature so why can't use this solution? – Sanjay Nishad Jul 28 '18 at 11:09
  • 1
    @SanjayNishad I don't mind if you don't need the feature. It's just about the users that don't know what they are disabling. – El Mac Jul 28 '18 at 14:04
10

To serialize usin NEWTONSOFTJSON when you have loop issue, in my case I did not need modify global.asax or either apiconfig. I just use JsonSerializesSettings ignoring Looping handling.

JsonSerializerSettings jss = new JsonSerializerSettings();
jss.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
var lst = db.shCards.Where(m => m.CardID == id).ToList();
string json = JsonConvert.SerializeObject(lst, jss);
Carlos Barini
  • 129
  • 1
  • 4
  • 2
    If anyone else came here for a one liner to go in the watch window so it's text searchable: ```Newtonsoft.Json.JsonConvert.SerializeObject(objToSerialize, new Newtonsoft.Json.JsonSerializerSettings() {ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore});``` – Graham Jul 17 '19 at 09:03
7

You can apply an attribute to the property too. The [JsonProperty( ReferenceLoopHandling = ... )] attribute is well suited to this.

For example:

/// <summary>
/// Represents the exception information of an event
/// </summary>
public class ExceptionInfo
{
    // ...code omitted for brevity...

    /// <summary>
    /// An inner (nested) error.
    /// </summary>
    [JsonProperty( ReferenceLoopHandling = ReferenceLoopHandling.Ignore, IsReference = true )]
    public ExceptionInfo Inner { get; set; }

    // ...code omitted for brevity...    
}

Hope that helps, Jaans

Jaans
  • 4,538
  • 4
  • 38
  • 47
  • That's the one I needed. My root object inherited from a certain model but it also had a property with that same model. When both had the same ids value I had this self reference loop issue. Adding the ignore on the propery fixed this. Thanks! – Kevin Cloet Jun 26 '20 at 13:27
4

To ignore loop references and not to serialize them globally in MVC 6 use the following in startup.cs:

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvc().Configure<MvcOptions>(options =>
        {
            options.OutputFormatters.RemoveTypesOf<JsonOutputFormatter>();
            var jsonOutputFormatter = new JsonOutputFormatter();
            jsonOutputFormatter.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
            options.OutputFormatters.Insert(0, jsonOutputFormatter);
        });
    }
GerardBeckerleg
  • 851
  • 1
  • 8
  • 14
3

Just update services.AddControllers() in Startup.cs file

services.AddControllers()
  .AddNewtonsoftJson(options =>
      options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
   );
2

Use this in WebApiConfig.cs class :

var json = config.Formatters.JsonFormatter;
json.SerializerSettings.PreserveReferencesHandling = Newtonsoft.Json.PreserveReferencesHandling.Objects;
config.Formatters.Remove(config.Formatters.XmlFormatter);
Guillaume Fache
  • 813
  • 10
  • 21
Anand Kumar
  • 121
  • 2
  • 5
2

For me I had to go a different route. Instead of trying to fix the JSON.Net serializer I had to go after the Lazy Loading on my datacontext.

I just added this to my base repository:

context.Configuration.ProxyCreationEnabled = false;

The "context" object is a constructor parameter I use in my base repository because I use dependency injection. You could change the ProxyCreationEnabled property anywhere you instantiate your datacontext instead.

http://techie-tid-bits.blogspot.com/2015/09/jsonnet-serializer-and-error-self.html

Xipooo
  • 1,336
  • 1
  • 12
  • 13
2

I had this exception and my working solution is Easy and Simple,

Ignore the Referenced property by adding the JsonIgnore attribute to it:

[JsonIgnore]
public MyClass currentClass { get; set; }

Reset the property when you Deserialize it:

Source = JsonConvert.DeserializeObject<MyObject>(JsonTxt);
foreach (var item in Source)
        {
            Source.MyClass = item;
        }

using Newtonsoft.Json;

Mayer Spitz
  • 2,254
  • 1
  • 18
  • 25
2

People have already talked about [JsonIgnore] being added to the virtual property in the class, for example:

[JsonIgnore]
public virtual Project Project { get; set; }

I will also share another option, [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] which omits the property from serialization only if it is null:

[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public virtual Project Project { get; set; }
KirstieBallance
  • 1,014
  • 9
  • 25
Ali Raza
  • 90
  • 8
2

Team:

This works with ASP.NET Core; The challenge to the above is how you 'set the setting to ignore'. Depending on how you setup your application it can be quite challenging. Here is what worked for me.

This can be placed in your public void ConfigureServices(IServiceCollection services) section.

services.AddMvc().AddJsonOptions(opt => 
        { 
      opt.SerializerSettings.ReferenceLoopHandling =
      Newtonsoft.Json.ReferenceLoopHandling.Ignore;
        });
FlyingV
  • 1,577
  • 15
  • 15
2

In .Net 5.x, update your ConfigureServices method in startup.cs with the below code

public void ConfigureServices(IServiceCollection services)
{
    ----------------
    ----------------
    services.AddMvc().AddJsonOptions(options =>
    {
        options.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.Preserve;
    });
    ------------------
}

By default, serialization (System.Text.Json.Serialization) does not support objects with cycles and does not preserve duplicate references. Use Preserve to enable unique object reference preservation on serialization and metadata consumption to read preserved references on deserialization. MSDN Link

Syam
  • 193
  • 7
1

My Problem Solved With Custom Config JsonSerializerSettings:

services.AddMvc(
  // ...
               ).AddJsonOptions(opt =>
                 {
                opt.SerializerSettings.ReferenceLoopHandling =
                    Newtonsoft.Json.ReferenceLoopHandling.Serialize;
                opt.SerializerSettings.PreserveReferencesHandling =
                    Newtonsoft.Json.PreserveReferencesHandling.Objects;
                 });
Amin Golmahalle
  • 2,911
  • 2
  • 20
  • 31
1

For .NET Core 3.0, update the Startup.cs class as shown below.

public void ConfigureServices(IServiceCollection services)
{
...

services.AddControllers()
    .AddNewtonsoftJson(
        options => options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
    );

...
}

See: https://devblogs.microsoft.com/aspnet/asp-net-core-updates-in-net-core-3-0-preview-5/

Jimmy
  • 41
  • 5
1

C# code:

            var jsonSerializerSettings = new JsonSerializerSettings
            {
                ReferenceLoopHandling = ReferenceLoopHandling.Serialize,
                PreserveReferencesHandling = PreserveReferencesHandling.Objects,
            };

            var jsonString = JsonConvert.SerializeObject(object2Serialize, jsonSerializerSettings);

            var filePath = @"E:\json.json";

            File.WriteAllText(filePath, jsonString);
  • This is essentially the same guidance as offered in @DalSoft‘s highly-rated answer from eight years ago, but with far less explanation. – Jeremy Caney Jun 01 '20 at 06:31
  • Hope It will solve issue but please add explanation of your code with it so user will get perfect understanding which he/she really wants. – Jaimil Patel Jun 01 '20 at 06:39
0

Simply place Configuration.ProxyCreationEnabled = false; inside the context file; this will solve the problem.

public demEntities()
    : base("name=demEntities")
{
    Configuration.ProxyCreationEnabled = false;
}
Laurel
  • 5,771
  • 12
  • 29
  • 54
fraka
  • 1
  • 2
0

Please also make sure to use await and async in you method. You can get this error if your object are not serialized properly.

maxspan
  • 12,194
  • 13
  • 68
  • 95
0

I was facing the same problem and I tried using JsonSetting to ignore the self-referencing error its kinda work until I got a class which self-referencing very deeply and my dot-net process hangs on Json writing value.

My Problem

    public partial class Company : BaseModel
{
    public Company()
    {
        CompanyUsers = new HashSet<CompanyUser>();
    }

    public string Name { get; set; }

    public virtual ICollection<CompanyUser> CompanyUsers { get; set; }
}

public partial class CompanyUser
{
    public int Id { get; set; }
    public int CompanyId { get; set; }
    public int UserId { get; set; }

    public virtual Company Company { get; set; }

    public virtual User User { get; set; }
}

public partial class User : BaseModel
{
    public User()
    {
        CompanyUsers = new HashSet<CompanyUser>();
    }

    public string DisplayName { get; set; }
    public virtual ICollection<CompanyUser> CompanyUsers { get; set; }

}

You can see the problem in User class it's referencing to CompanyUser class which is a self-referencing.

Now, I'm calling the GetAll Method which includes all the relational properties.

cs.GetAll("CompanyUsers", "CompanyUsers.User");

On this stage my DotNetCore process hangs on Executing JsonResult, writing value ... and never come. In my Startup.cs, I've already set the JsonOption. For some reason EFCore is including nested property which I'm not asking Ef to give.

    options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;

expected behavior should be this

Hey EfCore can you please include "CompanyUsers" data as well in my Company class so that i can easily access the data.

then

Hey EfCore can you also please include the "CompanyUsers.User" data as well so that i can easily access the data like this Company.CompanyUsers.First().User.DisplayName

at this stage i should only get this "Company.CompanyUsers.First().User.DisplayName" and it should not give me Company.CompanyUsers.First().User.CompanyUsers which causing the self-referencing issue; Technically it shouldn't give me User.CompanyUsers as CompanyUsers is a navigational property. But, EfCore get very excited and giving me User.CompanyUsers.

So, I decided to write an extension method for property to be excluded from the object (it's actually not excluding it's just setting the property to null). Not only that it will also work on array properties as well. below is the code I'm also going to export the nuget package for other users (not sure if this even helps someone). Reason is simple because I'm too lazy to write .Select(n => new { n.p1, n.p2}); I just don't want to write select statement to exclude only 1 property!

This is not the best code (I'll update at some stage) as I have written in a hurry and though this might help someone who wants to exclude (set null) in the object with arrays also.

    public static class PropertyExtensions
{
    public static void Exclude<T>(this T obj, Expression<Func<T, object>> expression)
    {
        var visitor = new PropertyVisitor<T>();
        visitor.Visit(expression.Body);
        visitor.Path.Reverse();
        List<MemberInfo> paths = visitor.Path;
        Action<List<MemberInfo>, object> act = null;

        int recursiveLevel = 0;
        act = (List<MemberInfo> vPath, object vObj) =>
        {

            // set last propert to null thats what we want to avoid the self-referencing error.
            if (recursiveLevel == vPath.Count - 1)
            {
                if (vObj == null) throw new ArgumentNullException("Object cannot be null");

                vObj.GetType().GetMethod($"set_{vPath.ElementAt(recursiveLevel).Name}").Invoke(vObj, new object[] { null });
                return;
            }

            var pi = vObj.GetType().GetProperty(vPath.ElementAt(recursiveLevel).Name);
            if (pi == null) return;
            var pv = pi.GetValue(vObj, null);
            if (pi.PropertyType.IsArray || pi.PropertyType.Name.Contains("HashSet`1") || pi.PropertyType.Name.Contains("ICollection`1"))
            {
                var ele = (IEnumerator)pv.GetType().GetMethod("GetEnumerator").Invoke(pv, null);

                while (ele.MoveNext())
                {
                    recursiveLevel++;
                    var arrItem = ele.Current;

                    act(vPath, arrItem);

                    recursiveLevel--;
                }

                if (recursiveLevel != 0) recursiveLevel--;
                return;
            }
            else
            {
                recursiveLevel++;
                act(vPath, pv);
            }

            if (recursiveLevel != 0) recursiveLevel--;

        };

        // check if the root level propert is array
        if (obj.GetType().IsArray)
        {
            var ele = (IEnumerator)obj.GetType().GetMethod("GetEnumerator").Invoke(obj, null);
            while (ele.MoveNext())
            {
                recursiveLevel = 0;
                var arrItem = ele.Current;

                act(paths, arrItem);
            }
        }
        else
        {
            recursiveLevel = 0;
            act(paths, obj);
        }

    }

    public static T Explode<T>(this T[] obj)
    {
        return obj.FirstOrDefault();
    }

    public static T Explode<T>(this ICollection<T> obj)
    {
        return obj.FirstOrDefault();
    }
}

above extension class will give you the ability to set the property to null to avoid the self-referencing loop even arrays.

Expression Builder

    internal class PropertyVisitor<T> : ExpressionVisitor
{
    public readonly List<MemberInfo> Path = new List<MemberInfo>();

    public Expression Modify(Expression expression)
    {
        return Visit(expression);
    }


    protected override Expression VisitMember(MemberExpression node)
    {
        if (!(node.Member is PropertyInfo))
        {
            throw new ArgumentException("The path can only contain properties", nameof(node));
        }

        Path.Add(node.Member);
        return  base.VisitMember(node);
    }
}

Usages:

Model Classes

    public class Person
{
    public string Name { get; set; }
    public Address AddressDetail { get; set; }
}

public class Address
{
    public string Street { get; set; }
    public Country CountryDetail { get; set; }
    public Country[] CountryDetail2 { get; set; }
}

public class Country
{
    public string CountryName { get; set; }
    public Person[] CountryDetail { get; set; }
}

Dummy Data

           var p = new Person
        {
            Name = "Adeel Rizvi",
            AddressDetail = new Address
            {
                Street = "Sydney",
                CountryDetail = new Country
                {
                    CountryName = "AU"
                }
            }
        };

        var p1 = new Person
        {
            Name = "Adeel Rizvi",
            AddressDetail = new Address
            {
                Street = "Sydney",
                CountryDetail2 = new Country[]
                {
                    new Country{ CountryName = "AU", CountryDetail = new Person[]{ new Person { Name = "A1" }, new Person { Name = "A1" }, new Person { Name = "A1" } } },
                    new Country{ CountryName = "AU", CountryDetail = new Person[]{ new Person { Name = "A2" }, new Person { Name = "A1" }, new Person { Name = "A1" } } },
                    new Country{ CountryName = "AU", CountryDetail = new Person[]{ new Person { Name = "A3" }, new Person { Name = "A1" }, new Person { Name = "A1" } } },
                    new Country{ CountryName = "AU", CountryDetail = new Person[]{ new Person { Name = "A4" }, new Person { Name = "A1" }, new Person { Name = "A1" } } },
                    new Country{ CountryName = "AU", CountryDetail = new Person[]{ new Person { Name = "A5" }, new Person { Name = "A1" }, new Person { Name = "A1" } } },
                    new Country{ CountryName = "AU", CountryDetail = new Person[]{ new Person { Name = "A6" }, new Person { Name = "A1" }, new Person { Name = "A1" } } },
                    new Country{ CountryName = "AU", CountryDetail = new Person[]{ new Person { Name = "A7" }, new Person { Name = "A1" }, new Person { Name = "A1" } } },
                    new Country{ CountryName = "AU", CountryDetail = new Person[]{ new Person { Name = "A8" }, new Person { Name = "A1" }, new Person { Name = "A1" } } },
                    new Country{ CountryName = "AU", CountryDetail = new Person[]{ new Person { Name = "A9" }, new Person { Name = "A1" }, new Person { Name = "A1" } } },
                    new Country{ CountryName = "AU", CountryDetail = new Person[]{ new Person { Name = "A1" }, new Person { Name = "A1" }, new Person { Name = "A1" } } },
                    new Country{ CountryName = "AU", CountryDetail = new Person[]{ new Person { Name = "A2" }, new Person { Name = "A1" }, new Person { Name = "A1" } } },
                    new Country{ CountryName = "AU", CountryDetail = new Person[]{ new Person { Name = "A3" }, new Person { Name = "A1" }, new Person { Name = "A1" } } },
                    new Country{ CountryName = "AU", CountryDetail = new Person[]{ new Person { Name = "A4" }, new Person { Name = "A1" }, new Person { Name = "A1" } } },
                    new Country{ CountryName = "AU", CountryDetail = new Person[]{ new Person { Name = "A5" }, new Person { Name = "A1" }, new Person { Name = "A1" } } },
                    new Country{ CountryName = "AU", CountryDetail = new Person[]{ new Person { Name = "A6" }, new Person { Name = "A1" }, new Person { Name = "A1" } } },
                    new Country{ CountryName = "AU", CountryDetail = new Person[]{ new Person { Name = "A7" }, new Person { Name = "A1" }, new Person { Name = "A1" } } },
                    new Country{ CountryName = "AU", CountryDetail = new Person[]{ new Person { Name = "A8" }, new Person { Name = "A1" }, new Person { Name = "A1" } } },
                    new Country{ CountryName = "AU", CountryDetail = new Person[]{ new Person { Name = "A9" }, new Person { Name = "A1" }, new Person { Name = "A1" } } },

                }
            }
        };

Cases:

Case 1: Exclude only property without any array

p.Exclude(n => n.AddressDetail.CountryDetail.CountryName);

Case 2: Exclude property with 1 array

p1.Exclude(n => n.AddressDetail.CountryDetail2.Explode().CountryName);

Case 3: Exclude property with 2 nested array

p1.Exclude(n => n.AddressDetail.CountryDetail2.Explode().CountryDetail.Explode().Name);

Case 4: EF GetAll Query With Includes

var query = cs.GetAll("CompanyUsers", "CompanyUsers.User").ToArray();
query.Exclude(n => n.Explode().CompanyUsers.Explode().User.CompanyUsers);
return query;

You have notice that Explode() method its also a extension method just for our expression builder to get the property from array property. Whenever there is a array property use the .Explode().YourPropertyToExclude or .Explode().Property1.MyArrayProperty.Explode().MyStupidProperty. Above code helps me to avoid the self-referencing so deep as deep i want. Now i can use GetAll and exclude the property which i don;t want!

Thank you for reading this big post!

Adeel Rizvi
  • 104
  • 2
  • 2
0

I've inherited a database application that serves up the data model to the web page. Serialization by default will attempt to traverse the entire model tree and most of the answers here are a good start on how to prevent that.

One option that has not been explored is using interfaces to help. I'll steal from an earlier example:

public partial class CompanyUser
{
    public int Id { get; set; }
    public int CompanyId { get; set; }
    public int UserId { get; set; }

    public virtual Company Company { get; set; }

    public virtual User User { get; set; }
}

public interface IgnoreUser
{
    [JsonIgnore]
    User User { get; set; }
}

public interface IgnoreCompany
{
    [JsonIgnore]
    User User { get; set; }
}

public partial class CompanyUser : IgnoreUser, IgnoreCompany
{
}

No Json settings get harmed in the above solution. Setting the LazyLoadingEnabled and or the ProxyCreationEnabled to false impacts all your back end coding and prevents some of the true benefits of an ORM tool. Depending on your application the LazyLoading/ProxyCreation settings can prevent the navigation properties loading without manually loading them.

Here is a much, much better solution to prevent navigation properties from serializing and it uses standard json functionality: How can I do JSON serializer ignore navigation properties?

Dan
  • 1
-1

For not looping this worked for me-
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,

I've solved it all here - Entity Framework children serialization with .Net Core 2 WebAPI https://gist.github.com/Kaidanov/f9ad0d79238494432f32b8407942c606

Will appreciate any remarks. maybe someone can use it sometime.

Tzvi Gregory Kaidanov
  • 3,002
  • 3
  • 24
  • 31
-2

I liked the solution that does it from Application_Start() as in the answer here

Apparently I could not access the json objects in JavaScript using the configuration within my function as in DalSoft's answer as the object returned had "\n \r" all over the (key, val) of the object.

Anyway whatever works is great (because different approaches work in different scenario based on the comments and questions asked) though a standard way of doing it would be preferable with some good documentation supporting the approach.

Community
  • 1
  • 1
rey_coder
  • 402
  • 8
  • 12