ฉันกำลังเผชิญปัญหาเดียวกันและฉันพยายามใช้ JsonSetting เพื่อละเว้นข้อผิดพลาดในการอ้างอิงตัวเองมันทำงานได้จนกว่าฉันจะได้เรียนซึ่งการอ้างอิงตัวเองอย่างลึกซึ้งมากและกระบวนการ dot-net ของฉันค้างอยู่กับค่าการเขียน 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 ()นี้ได้อย่างง่ายดาย
User.DisplayName
ในขั้นตอนนี้ฉันควรได้รับ"Company.CompanyUsers.First () นี้ User.DisplayName" เท่านั้นและไม่ควรให้Company.CompanyUsers.First ()แก่ฉันUser.CompanyUsersซึ่งเป็นสาเหตุของปัญหาการอ้างอิงตนเอง ในทางเทคนิคแล้วมันไม่ควรให้User.CompanyUsersกับฉันเพราะCompanyUsersเป็นคุณสมบัติการนำทาง แต่ EfCore รับตื่นเต้นมากและให้ฉันUser.CompanyUsers
ดังนั้นฉันตัดสินใจที่จะเขียนวิธีการขยายสำหรับคุณสมบัติที่จะถูกแยกออกจากวัตถุ ไม่เพียงแค่ว่ามันจะทำงานในคุณสมบัติอาร์เรย์เช่นกัน ด้านล่างเป็นรหัสที่ฉันจะส่งออกแพ็คเกจ nuget สำหรับผู้ใช้รายอื่น (ไม่แน่ใจว่าสิ่งนี้จะช่วยคนอื่นได้หรือไม่) เหตุผลง่ายเพราะฉันขี้เกียจเขียนเลือก (n => ใหม่ {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();
}
}
ชั้นส่วนขยายข้างต้นจะช่วยให้คุณสามารถตั้งค่าคุณสมบัติเป็นโมฆะเพื่อหลีกเลี่ยงการวนรอบการอ้างอิงตัวเองแม้กระทั่งอาร์เรย์
ตัวสร้างนิพจน์
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: คำถามเกี่ยวกับการรับ GetAll ของ EF
var query = cs.GetAll("CompanyUsers", "CompanyUsers.User").ToArray();
query.Exclude(n => n.Explode().CompanyUsers.Explode().User.CompanyUsers);
return query;
คุณสังเกตเห็นว่าExplode ()วิธีการมันยังเป็นวิธีการขยายเพียงสำหรับการสร้างการแสดงออกของเราที่จะได้รับทรัพย์สินจากคุณสมบัติอาร์เรย์ เมื่อใดก็ตามที่มีการใช้งานคุณสมบัติอาร์เรย์.Explode (). YourPropertyToExclude หรือ .Explode โค้ดด้านบนช่วยให้ฉันสามารถหลีกเลี่ยงการอ้างอิงตนเองได้อย่างลึกล้ำฉันต้องการ ตอนนี้ฉันสามารถใช้ GetAll และยกเว้นคุณสมบัติที่ฉันไม่ต้องการได้!
ขอบคุณสำหรับการอ่านโพสต์ใหญ่นี้!