Я столкнулся с той же проблемой, и я пытался использовать JsonSetting, чтобы игнорировать ошибку самоссылки, это своего рода работа, пока я не получил класс, который ссылается очень глубоко, и мой процесс точка-сеть зависает от значения записи 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, не могли бы вы включить данные «CompanyUsers» в мой класс компании, чтобы я мог легко получить доступ к данным.
тогда
Эй, EfCore, можете ли вы также включить данные «CompanyUsers.User», чтобы я мог легко получить доступ к таким данным, как эта
Company.CompanyUsers.First (). User.DisplayName
на этом этапе я должен получить только этот «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);
}
}
Обычаи:
Модельные классы
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;
Вы заметили, что метод Explode () также является методом расширения только для нашего построителя выражений, чтобы получить свойство из свойства массива. Когда есть свойство массива, используйте .Explode (). YourPropertyToExclude или .Explode (). Property1.MyArrayProperty.Explode (). MyStupidProperty . Приведенный выше код помогает мне избежать самореференции настолько глубоко, насколько я хочу. Теперь я могу использовать GetAll и исключить свойство, которое мне не нужно!
Спасибо за чтение этого большого поста!