Ich war das gleiche Problem konfrontiert und ich versuchte mit JsonSetting zu ignorieren, die selbst-Referenzierung Fehler seine Art von Arbeit, bis ich eine Klasse, die selbst-Referenzierung sehr tief und meine Dot-Net-Prozess hängt auf Json schreiben Wert.
Mein 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; }
}
Sie können das Problem in der Benutzerklasse sehen, auf die sie verweist UnternehmenBenutzer Klasse, die selbstreferenzierend ist.
Jetzt rufe ich die GetAll-Methode auf, die alle relationalen Eigenschaften enthält.
cs.GetAll("CompanyUsers", "CompanyUsers.User");
In diesem Stadium bleibt mein DotNetCore-Prozess hängen bei Ausführen von JsonResult, Schreiben des Wertes ... und nie kommen. In meiner Startup.cs, habe ich bereits die JsonOption festgelegt. Aus irgendeinem Grund EFCore ist einschließlich verschachtelte Eigenschaft, die ich nicht Ef zu geben, fragen.
options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
Das erwartete Verhalten sollte wie folgt aussehen
Hey EfCore können Sie bitte "CompanyUsers" Daten als auch in meinem Company-Klasse aufnehmen, damit ich einfach auf die Daten zugreifen kann.
dann
Hey EfCore, können Sie bitte auch die "FirmaBenutzer.Benutzer" Daten als auch, so dass ich leicht auf die Daten zugreifen kann wie folgt Firma.CompanyUsers.First().User.DisplayName
In diesem Stadium sollte ich nur Folgendes erhalten "Firma.CompanyUsers.First().User.DisplayName" und es sollte mich nicht Firma.CompanyUsers.First().User.CompanyUsers was das Problem der Selbstreferenzierung verursacht; technisch gesehen sollte es mir keine User.CompanyUsers da CompanyUsers eine Navigationseigenschaft ist. Aber, EfCore bekommen sehr aufgeregt und geben mir User.CompanyUsers .
Also beschloss ich, eine Erweiterungsmethode für die Eigenschaft aus dem Objekt ausgeschlossen werden zu schreiben (es ist eigentlich nicht auszuschließen, es ist nur die Eigenschaft auf null). Nicht nur, dass es auch auf Array-Eigenschaften als gut funktionieren wird. unten ist der Code, den ich auch gehen, um das Nuget-Paket für andere Benutzer zu exportieren (nicht sicher, ob dies sogar jemand hilft). Der Grund ist einfach, weil ich zu faul bin, zu schreiben .Select(n => new { n.p1, n.p2}); Ich möchte einfach keine Select-Anweisung schreiben, um nur 1 Eigenschaft auszuschließen!
Dies ist nicht der beste Code (ich werde zu einem späteren Zeitpunkt aktualisieren), da ich in Eile geschrieben habe und obwohl dies jemandem helfen könnte, der das Objekt mit Arrays auch ausschließen (null setzen) möchte.
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();
}
}
obige Erweiterungsklasse gibt Ihnen die Möglichkeit, die Eigenschaft auf Null zu setzen, um die selbstreferenzierende Schleife auch Arrays zu vermeiden.
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);
}
}
Verwendungen:
Modell-Klassen
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-Daten
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" } } },
}
}
};
Die Fälle:
Fall 1: Nur Eigenschaft ohne Array ausschließen
p.Exclude(n => n.AddressDetail.CountryDetail.CountryName);
Fall 2: Eigenschaft mit 1 Array ausschließen
p1.Exclude(n => n.AddressDetail.CountryDetail2.Explode().CountryName);
Fall 3: Eigenschaft ausschließen mit 2 verschachtelten Arrays
p1.Exclude(n => n.AddressDetail.CountryDetail2.Explode().CountryDetail.Explode().Name);
Fall 4: EF GetAll-Abfrage mit Includes
var query = cs.GetAll("CompanyUsers", "CompanyUsers.User").ToArray();
query.Exclude(n => n.Explode().CompanyUsers.Explode().User.CompanyUsers);
return query;
Sie haben festgestellt, dass Explodieren() Methode ist auch eine Erweiterung Methode nur für unsere Expression Builder, um die Eigenschaft von Array-Eigenschaft zu erhalten. Wann immer es eine Array-Eigenschaft gibt, verwenden Sie die .Explode().YourPropertyToExclude oder .Explode().Property1.MyArrayProperty.Explode().MyStupidProperty . Der obige Code hilft mir, die Selbstreferenzierung so tief zu vermeiden, wie ich will. Jetzt kann ich GetAll verwenden und die Eigenschaft ausschließen, die ich nicht haben möchte!
Vielen Dank, dass Sie diesen Beitrag gelesen haben!