J'étais confronté au même problème et j'ai essayé d'utiliser JsonSetting pour ignorer l'erreur d'auto-référencement, son travail un peu jusqu'à ce que j'obtienne une classe qui s'auto-référençant très profondément et mon processus dot-net se bloque sur la valeur d'écriture Json.
Mon problème
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; }
}
Vous pouvez voir le problème dans la classe Utilisateur qui fait référence à CompanyUser classe qui est une auto-référence.
Maintenant, j'appelle la méthode GetAll qui inclut toutes les propriétés relationnelles.
cs.GetAll("CompanyUsers", "CompanyUsers.User");
À ce stade, mon processus DotNetCore dépend de l' exécution de JsonResult, de l'écriture de valeur ... et ne vient jamais. Dans mon Startup.cs, j'ai déjà défini le JsonOption. Pour une raison quelconque, EFCore inclut une propriété imbriquée que je ne demande pas à Ef de donner.
options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
le comportement attendu devrait être ceci
Hey EfCore, pouvez-vous également inclure les données "CompanyUsers" dans ma classe Company afin que je puisse facilement accéder aux données.
puis
Hey EfCore pouvez-vous également inclure les données "CompanyUsers.User" afin que je puisse facilement accéder aux données comme celle-ci
Company.CompanyUsers.First (). User.DisplayName
à ce stade, je ne devrais obtenir que "Company.CompanyUsers.First (). User.DisplayName" et il ne devrait pas me donner Company.CompanyUsers.First (). User.CompanyUsers qui est à l'origine du problème d'auto-référencement; Techniquement, cela ne devrait pas me donner User.CompanyUsers car CompanyUsers est une propriété de navigation. Mais, EfCore devient très excité et me donne User.CompanyUsers .
J'ai donc décidé d'écrire une méthode d'extension pour que la propriété soit exclue de l'objet (elle n'exclut pas en fait, elle définit simplement la propriété sur null). Non seulement cela fonctionnera également sur les propriétés du tableau. ci-dessous est le code que je vais également exporter le paquet nuget pour les autres utilisateurs (je ne sais pas si cela aide même quelqu'un). La raison est simple car je suis trop paresseux pour écrire .Select (n => new {n.p1, n.p2});Je ne veux tout simplement pas écrire l'instruction select pour exclure seulement 1 propriété!
Ce n'est pas le meilleur code (je mettrai à jour à un moment donné) comme je l'ai écrit à la hâte et bien que cela puisse aussi aider quelqu'un qui veut exclure (définir null) dans l'objet avec des tableaux.
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();
}
}
La classe d'extension ci-dessus vous donnera la possibilité de définir la propriété sur null pour éviter la boucle d'auto-référencement, même les tableaux.
Générateur d'expression
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);
}
}
Coutumes:
Classes de modèles
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; }
}
Données factices
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" } } },
}
}
};
Cas:
Cas 1: exclure uniquement la propriété sans tableau
p.Exclude(n => n.AddressDetail.CountryDetail.CountryName);
Cas 2: exclure une propriété avec 1 tableau
p1.Exclude(n => n.AddressDetail.CountryDetail2.Explode().CountryName);
Cas 3: exclure une propriété avec 2 tableaux imbriqués
p1.Exclude(n => n.AddressDetail.CountryDetail2.Explode().CountryDetail.Explode().Name);
Cas 4: EF GetAll Query avec comprend
var query = cs.GetAll("CompanyUsers", "CompanyUsers.User").ToArray();
query.Exclude(n => n.Explode().CompanyUsers.Explode().User.CompanyUsers);
return query;
Vous avez remarqué que la méthode Explode () est également une méthode d'extension juste pour notre générateur d'expression pour obtenir la propriété de la propriété du tableau. Chaque fois qu'il existe une propriété de tableau, utilisez .Explode (). YourPropertyToExclude ou .Explode (). Property1.MyArrayProperty.Explode (). MyStupidProperty . Le code ci-dessus m'aide à éviter l'auto-référencement aussi profond que profond que je veux. Maintenant, je peux utiliser GetAll et exclure la propriété dont je ne veux pas!
Merci d'avoir lu ce gros post!