Tôi đã phải đối mặt với cùng một vấn đề và tôi đã thử sử dụng JsonSetting để bỏ qua lỗi tự tham chiếu của nó cho đến khi tôi có một lớp tự tham chiếu rất sâu và quá trình dot-net của tôi bị treo trên giá trị viết Json.
Vấn đề của tôi
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; }
}
Bạn có thể thấy vấn đề trong lớp Người dùng đang đề cập đến lớp CompanyUser là tự tham khảo.
Bây giờ, tôi đang gọi Phương thức GetAll bao gồm tất cả các thuộc tính quan hệ.
cs.GetAll("CompanyUsers", "CompanyUsers.User");
Trong giai đoạn này, quy trình DotNetCore của tôi bị treo trên Thi hành JsonResult, viết giá trị ... và không bao giờ đến. Trong Startup.cs của tôi, tôi đã thiết lập JsonOption. Vì một số lý do, EFCore bao gồm cả tài sản lồng nhau mà tôi không yêu cầu Ef đưa ra.
options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
hành vi dự kiến nên thế này
Xin chào EfCore, bạn có thể vui lòng bao gồm dữ liệu "CompanyUsers" trong lớp Công ty của tôi để tôi có thể dễ dàng truy cập dữ liệu.
sau đó
Xin chào EfCore, bạn cũng có thể vui lòng bao gồm dữ liệu "CompanyUsers.User" để tôi có thể dễ dàng truy cập dữ liệu như
Company.CompanyUsers.First (). User.DisplayName
ở giai đoạn này tôi chỉ nên lấy "Company.CompanyUsers.First (). User.DisplayName" này và nó không nên cung cấp cho tôi Company.CompanyUsers.First (). User.CompanyUsers gây ra vấn đề tự tham chiếu; Về mặt kỹ thuật, nó không nên cung cấp cho tôi User.CompanyUsers vì CompanyUsers là một tài sản điều hướng. Nhưng, EfCore rất phấn khích và mang đến cho tôi User.CompanyUsers .
Vì vậy, tôi quyết định viết một phương thức mở rộng để loại trừ thuộc tính khỏi đối tượng (thực tế không loại trừ nó chỉ đặt thuộc tính thành null). Không chỉ vậy nó cũng sẽ làm việc trên các thuộc tính mảng. bên dưới là mã tôi cũng sẽ xuất gói nuget cho những người dùng khác (không chắc điều này có giúp được ai đó không). Lý do rất đơn giản vì tôi quá lười viết .Select (n => new {n.p1, n.p2}); Tôi chỉ không muốn viết câu lệnh chọn để loại trừ chỉ 1 thuộc tính!
Đây không phải là mã tốt nhất (tôi sẽ cập nhật ở một số giai đoạn) vì tôi đã viết vội vàng và mặc dù điều này có thể giúp ai đó muốn loại trừ (đặt null) trong đối tượng bằng các mảng.
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();
}
}
lớp mở rộng ở trên sẽ cung cấp cho bạn khả năng đặt thuộc tính thành null để tránh vòng lặp tự tham chiếu ngay cả các mảng.
Trình tạo biểu thức
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);
}
}
Tập quán:
Lớp học mẫu
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; }
}
Dữ liệu giả
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" } } },
}
}
};
Các trường hợp:
Trường hợp 1: Chỉ loại trừ tài sản mà không có bất kỳ mảng nào
p.Exclude(n => n.AddressDetail.CountryDetail.CountryName);
Trường hợp 2: Loại trừ tài sản với 1 mảng
p1.Exclude(n => n.AddressDetail.CountryDetail2.Explode().CountryName);
Trường hợp 3: Loại trừ tài sản với 2 mảng lồng nhau
p1.Exclude(n => n.AddressDetail.CountryDetail2.Explode().CountryDetail.Explode().Name);
Trường hợp 4: Truy vấn Get Get của EF với Bao gồm
var query = cs.GetAll("CompanyUsers", "CompanyUsers.User").ToArray();
query.Exclude(n => n.Explode().CompanyUsers.Explode().User.CompanyUsers);
return query;
Bạn có lưu ý rằng phương thức Explode () cũng là một phương thức mở rộng chỉ dành cho trình xây dựng biểu thức của chúng ta để lấy thuộc tính từ thuộc tính mảng. Bất cứ khi nào có thuộc tính mảng, hãy sử dụng .Explode (). YourPropertyToExclude hoặc .Explode (). Property1.MyArrayProperty.Explode (). MyStoolProperty . Mã trên giúp tôi tránh được việc tự giới thiệu quá sâu như tôi muốn. Bây giờ tôi có thể sử dụng GetAll và loại trừ tài sản mà tôi không muốn!
Cảm ơn bạn đã đọc bài viết lớn này!