Miałem do czynienia z tym samym problemem i próbowałem użyć JsonSetting, aby zignorować błąd samoreferencji, który działał, dopóki nie otrzymałem klasy, która sam się tak bardzo odwołuje, a mój proces dot-net opiera się na wartości zapisu Jsona.
Mój 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; }
}
Problem można zobaczyć w klasie użytkownika, która odnosi się do CompanyUser klasy która jest samodzielna.
Teraz wywołuję metodę GetAll, która zawiera wszystkie właściwości relacyjne.
cs.GetAll("CompanyUsers", "CompanyUsers.User");
Na tym etapie mój proces DotNetCore opiera się na wykonywaniu JsonResult, zapisywaniu wartości ... i nigdy nie nadchodzi. W moim Startup.cs już ustawiłem JsonOption. Z jakiegoś powodu EFCore zawiera zagnieżdżoną właściwość, której nie proszę Ef o podanie.
options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
takie powinno być oczekiwane zachowanie
Hej, EfCore, czy możesz dołączyć również dane „CompanyUsers” do mojej klasy firmowej, aby móc łatwo uzyskać dostęp do danych.
następnie
Hej EfCore, czy możesz również dołączyć dane „CompanyUsers.User”, aby móc łatwo uzyskać dostęp do danych takich jak
Company.CompanyUsers.First (). User.DisplayName
na tym etapie powinienem otrzymać tylko „Company.CompanyUsers.First (). User.DisplayName” i nie powinien dać mi Company.CompanyUsers.First (). User.CompanyUsers, który powoduje problem z samodzielnym odwoływaniem się; Technicznie nie powinno dać mi User.CompanyUsers, ponieważ CompanyUsers jest własnością nawigacyjną. Ale EfCore jest bardzo podekscytowany i daje mi User.CompanyUsers .
Postanowiłem więc napisać metodę rozszerzenia właściwości, która ma zostać wykluczona z obiektu (tak naprawdę to nie wyklucza, po prostu ustawia właściwość na null). Nie tylko zadziała również na właściwości tablicy. poniżej znajduje się kod Wyeksportuję również pakiet nuget dla innych użytkowników (nie jestem pewien, czy to komuś pomoże). Powód jest prosty, ponieważ jestem zbyt leniwy, aby pisać. Wybierz (n => new {n.p1, n.p2}); Po prostu nie chcę pisać instrukcji select, aby wykluczyć tylko 1 właściwość!
To nie jest najlepszy kod (na pewnym etapie zaktualizuję), ponieważ napisałem w pośpiechu i chociaż może to pomóc komuś, kto chce wykluczyć (ustawić null) również w obiekcie z tablicami.
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();
}
}
powyżej klasy rozszerzenia daje możliwość ustawienia właściwości na null, aby uniknąć pętli odwołujących się nawet do tablic.
Konstruktor wyrażeń
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);
}
}
Zastosowania:
Klasy modeli
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" } } },
}
}
};
Skrzynie:
Przypadek 1: Wyklucz tylko właściwość bez tablicy
p.Exclude(n => n.AddressDetail.CountryDetail.CountryName);
Przypadek 2: Wyklucz właściwość z 1 tablicą
p1.Exclude(n => n.AddressDetail.CountryDetail2.Explode().CountryName);
Przypadek 3: Wyklucz właściwość z 2 zagnieżdżonymi tablicami
p1.Exclude(n => n.AddressDetail.CountryDetail2.Explode().CountryDetail.Explode().Name);
Przypadek 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;
Zauważyłeś, że metoda Explode () jest również metodą rozszerzenia tylko dla naszego konstruktora wyrażeń, aby uzyskać właściwość z właściwości tablicy. Ilekroć istnieje właściwość tablicowa, użyj .Explode (). YourPropertyToExclude lub .Explode (). Property1.MyArrayProperty.Explode (). MyStupidProperty . Powyższy kod pomaga mi uniknąć odwoływania się do siebie tak głęboko, jak chcę. Teraz mogę korzystać z GetAll i wykluczyć właściwość, której nie chcę!
Dziękujemy za przeczytanie tego dużego posta!