Estaba enfrentando el mismo problema e intenté usar JsonSetting para ignorar el error de autorreferencia que funciona un poco hasta que obtuve una clase que hace una autorreferencia muy profunda y mi proceso dot-net se cuelga del valor de escritura de Json.
Mi problema
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; }
}
Puede ver el problema en la clase de usuario que hace referencia a la clase CompanyUser , que es una referencia automática.
Ahora, estoy llamando al Método GetAll que incluye todas las propiedades relacionales.
cs.GetAll("CompanyUsers", "CompanyUsers.User");
En esta etapa, mi proceso DotNetCore se cuelga en Ejecutar JsonResult, escribiendo valor ... y nunca llega. En mi Startup.cs, ya configuré JsonOption. Por alguna razón, EFCore incluye propiedades anidadas que no le estoy pidiendo a Ef.
options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
el comportamiento esperado debería ser este
Hola, EfCore, ¿podría incluir los datos de "CompanyUsers" también en mi clase de empresa para que pueda acceder fácilmente a los datos?
entonces
Hola, EfCore, ¿puedes incluir también los datos de "CompanyUsers.User" para que pueda acceder fácilmente a los datos como esta
Company.CompanyUsers.First (). User.DisplayName
en esta etapa solo debería obtener este "Company.CompanyUsers.First (). User.DisplayName" y no debería darme Company.CompanyUsers.First (). User.CompanyUsers que causa el problema de autorreferencia; Técnicamente no debería darme User.CompanyUsers como CompanyUsers es una propiedad de navegación. Pero, EfCore se emociona mucho y me da User.CompanyUsers .
Entonces, decidí escribir un método de extensión para que la propiedad se excluya del objeto (en realidad no es excluyente, solo establece la propiedad en nulo). No solo eso también funcionará en las propiedades de la matriz. a continuación se muestra el código que también voy a exportar el paquete nuget para otros usuarios (no estoy seguro si esto incluso ayuda a alguien). La razón es simple porque soy demasiado vago para escribir .Seleccione (n => new {n.p1, n.p2}); ¡No quiero escribir una declaración select para excluir solo 1 propiedad!
Este no es el mejor código (lo actualizaré en algún momento) como lo he escrito rápidamente y aunque esto podría ayudar a alguien que quiera excluir (establecer nulo) en el objeto con matrices también.
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();
}
}
la clase de extensión anterior le dará la capacidad de establecer la propiedad en nulo para evitar el bucle de autorreferencia incluso en matrices.
Generador de expresiones
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);
}
}
Usos:
Clases de modelo
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; }
}
Datos ficticios
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" } } },
}
}
};
Casos:
Caso 1: excluir solo propiedades sin ninguna matriz
p.Exclude(n => n.AddressDetail.CountryDetail.CountryName);
Caso 2: excluir propiedad con 1 matriz
p1.Exclude(n => n.AddressDetail.CountryDetail2.Explode().CountryName);
Caso 3: excluir propiedad con 2 matrices anidadas
p1.Exclude(n => n.AddressDetail.CountryDetail2.Explode().CountryDetail.Explode().Name);
Caso 4: EF GetAll Query con incluye
var query = cs.GetAll("CompanyUsers", "CompanyUsers.User").ToArray();
query.Exclude(n => n.Explode().CompanyUsers.Explode().User.CompanyUsers);
return query;
Ha notado que el método Explode () también es un método de extensión solo para que nuestro generador de expresiones obtenga la propiedad de la propiedad de matriz. Siempre que haya una propiedad de matriz, use .Explode (). YourPropertyToExclude o .Explode (). Property1.MyArrayProperty.Explode (). MyStupidProperty . El código anterior me ayuda a evitar la autorreferencia tan profunda como quiero. ¡Ahora puedo usar GetAll y excluir la propiedad que no deseo!
¡Gracias por leer esta gran publicación!