我尝试序列化从实体数据模型.edmx自动生成的POCO类,当我使用

JsonConvert.SerializeObject 

我得到了以下错误:

为System.data.entity类型检测到自我引用循环错误。

我怎么解决这个问题?


当前回答

我也面临着同样的问题,我尝试使用JsonSetting来忽略自引用错误,它的某种工作,直到我得到一个类,自引用非常深入,我的。net进程挂在Json写入值。

我的问题

    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; }

}

你可以在User类中看到问题,它引用了CompanyUser类,这是一个自引用。

现在,我正在调用包含所有关系属性的GetAll方法。

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

在这个阶段,我的DotNetCore进程挂着执行JsonResult,写入值…永远不要来。在Startup.cs中,我已经设置了JsonOption。出于某种原因,EFCore包含了嵌套属性,我没有要求Ef给出。

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

预期的行为应该是这样的

嘿,EfCore,你能把“公司用户”数据也包括在我的 公司类,以便我可以很容易地访问数据。

then

嘿,EfCore,你能把“公司用户”也包括进来吗?用户数据为 这样我就可以像这样轻松地访问数据 .User.DisplayName Company.CompanyUsers.First ()

在这个阶段,我应该只得到这个"Company.CompanyUsers.First(). user。DisplayName"它不应该给我company。companyusers。first (). user。导致自引用问题的CompanyUsers;从技术上讲,它不应该给我User。CompanyUsers作为CompanyUsers是一个导航属性。EfCore非常兴奋,给了我user。companyusers。

所以,我决定为属性写一个扩展方法来排除对象(它实际上不是排除它只是设置属性为null)。不仅如此,它还适用于数组属性。下面是我还将为其他用户导出nuget包的代码(不确定这是否甚至有助于某人)。原因很简单,因为我懒得写.Select(n => new {n.p1, n.p2});我只是不想写选择语句只排除1个属性!

这不是最好的代码(我会在某个阶段更新),因为我写得很匆忙,尽管这可能会帮助那些想要排除(设置null)对象数组的人。

    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();
    }
}

上面的扩展类将使您能够将属性设置为null,以避免自引用循环甚至数组。

表达式构建器

    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:

模型类

    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; }
}

虚拟数据

           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" } } },

                }
            }
        };

例:

情况1:只排除没有任何数组的属性

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

案例2:排除带有1个数组的属性

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

案例3:排除带有2个嵌套数组的属性

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

案例4:包含EF GetAll查询

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

你已经注意到,explosion()方法也是一个扩展方法,用于我们的表达式构建器从数组属性中获取属性。只要有数组属性,就使用. explosion()。YourPropertyToExclude或. explosion (). property1 . myarrayproperty . explosion (). mystupidproperty。上面的代码帮助我避免像我想要的那样深的自我引用。现在我可以使用GetAll并排除我不想要的属性!

感谢你阅读这篇文章!

其他回答

您也可以将属性应用到属性。 [JsonProperty(ReferenceLoopHandling =…)属性非常适合这一点。

例如:

/// <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...    
}

希望有帮助, 扬

修复方法是忽略循环引用,不序列化它们。此行为在JsonSerializerSettings中指定。

带重载的单个JsonConvert:

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

Global.asax.cs中的Application_Start()代码中的全局设置:

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

参考:https://github.com/JamesNK/Newtonsoft.Json/issues/78

我的问题解决了自定义配置JsonSerializerSettings:

services.AddMvc(
  // ...
               ).AddJsonOptions(opt =>
                 {
                opt.SerializerSettings.ReferenceLoopHandling =
                    Newtonsoft.Json.ReferenceLoopHandling.Serialize;
                opt.SerializerSettings.PreserveReferencesHandling =
                    Newtonsoft.Json.PreserveReferencesHandling.Objects;
                 });

非常类似于这里的其他设置,通过使用序列化器设置解决。然而,我的起源,因为我使用FromObject方面的东西,因为我需要一个JObject来工作。

这意味着只需要创建默认序列化器并对其应用设置。简单的修复就变成了这样

 var deserializerSettings = new JsonSerializerSettings()
        {
            ReferenceLoopHandling = ReferenceLoopHandling.Serialize,
            PreserveReferencesHandling = PreserveReferencesHandling.Objects,
        };

        var serializer = JsonSerializer.CreateDefault(deserializerSettings);
            
        JObject jsonObject = (JObject)JToken.FromObject(mySelfRefernceObject, serializer);

如果你使用的是。net Core 2。x,更新Startup.cs中的ConfigureServices部分

https://learn.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
        );

    ...
    }

如果你使用的是。net Core 3。x - 5.0,没有MVC,它将是:

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

对于。net 6.0,唯一的不同是它现在进入Program.cs。

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

如果你使用实体框架和数据库优先的设计模式,这个引用循环处理几乎是强制性的。