Я зіткнувся з тією самою проблемою, і я спробував за допомогою 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; }
}
Проблему ви можете бачити в класі користувача, посилаючись на 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 ().
на цьому етапі я повинен отримати лише це "Company.CompanyUsers.First (). User.DisplayName", і він не повинен давати мені Company.CompanyUsers.First (). User.CompanyUsers, що викликає проблему самопосилання; Технічно це не повинно давати мені User.CompanyUsers як CompanyUsers - навігаційне властивість. Але EfCore дуже схвильований і дарує мені User.CompanyUsers .
Отже, я вирішив написати метод розширення для того, щоб властивість було виключено з об'єкта (це фактично не виключає, що просто встановити властивість на нуль). Мало того, що він також буде працювати і над властивостями масиву. нижче - код, який я також хочу експортувати пакунок nuget для інших користувачів (не впевнений, чи це комусь навіть допомагає). Причина проста, тому що я лінивий писати .Select (n => new {n.p1, n.p2}); Я просто не хочу писати заяву select, щоб виключити лише 1 властивість!
Це не найкращий код (я його оновлюю на деякому етапі), як я писав поспіхом, і хоча це може допомогти тому, хто хоче виключити (встановити нуль) в об'єкті також з масивами.
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();
}
}
вище клас розширення дасть вам можливість встановити властивість на нуль, щоб уникнути навіть масивів циклу самопосилань.
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);
}
}
Використання:
Класи моделей
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 With Includes
var query = cs.GetAll("CompanyUsers", "CompanyUsers.User").ToArray();
query.Exclude(n => n.Explode().CompanyUsers.Explode().User.CompanyUsers);
return query;
Ви помітили, що метод Explode () - це також метод розширення лише для того, щоб наш конструктор виразів міг отримати властивість з властивості масиву. Щоразу, коли є властивість масиву, використовуйте .Explode (). YourPropertyToExclude або .Explode (). Properties1.MyArrayProperty.Explode (). MyStupidProperty . Наведений вище код допомагає мені уникнути самовідсилення настільки глибоко, наскільки я хочу. Тепер я можу використовувати GetAll та виключити властивість, яку я не хочу; не хочу!
Дякую, що прочитали цей великий пост!