Eu estava enfrentando o mesmo problema e tentei usar o JsonSetting para ignorar o erro de auto-referência. Isso meio que funcionou até que eu recebi uma classe que se auto-referenciava muito profundamente e meu processo dot-net depende do valor de gravação do Json.
Meu 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; }
}
Você pode ver o problema na classe User, que está se referindo à classe CompanyUser, que é uma auto-referência.
Agora, estou chamando o método GetAll, que inclui todas as propriedades relacionais.
cs.GetAll("CompanyUsers", "CompanyUsers.User");
Nesse estágio, meu processo DotNetCore fica travado na execução do JsonResult, escrevendo valor ... e nunca chegando. No meu Startup.cs, eu já configurei o JsonOption. Por alguma razão, o EFCore está incluindo propriedades aninhadas que não estou pedindo ao Ef para fornecer.
options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
comportamento esperado deve ser este
Ei, EfCore, você pode incluir também os dados "CompanyUsers" na minha classe Company, para que eu possa acessar facilmente os dados.
então
Hey EfCore, você também pode incluir os dados "CompanyUsers.User" , para que eu possa acessar facilmente os dados como este
Company.CompanyUsers.First (). User.DisplayName
nesse estágio, eu só deveria obter esse "Company.CompanyUsers.First (). User.DisplayName" e ele não deve me fornecer Company.CompanyUsers.First (). User.CompanyUsers que causam o problema de auto-referência; Tecnicamente, ele não deve me fornecer User.CompanyUsers como CompanyUsers é uma propriedade de navegação. Mas, o EfCore fica muito animado e me oferece User.CompanyUsers .
Então, decidi escrever um método de extensão para a propriedade a ser excluída do objeto (na verdade, não está excluindo, está apenas configurando a propriedade como nula). Não apenas isso também funcionará nas propriedades da matriz. Abaixo está o código. Também vou exportar o pacote nuget para outros usuários (não tenho certeza se isso ajuda alguém). A razão é simples porque tenho preguiça de escrever .Select (n => new {n.p1, n.p2}); Eu só não quero escrever a instrução select para excluir apenas uma propriedade!
Este não é o melhor código (atualizarei em algum momento), como escrevi com pressa e, embora isso possa ajudar alguém que queira excluir (definir nulo) no objeto com matrizes também.
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();
}
}
A classe de extensão acima permitirá que você defina a propriedade como nula para evitar o loop de auto-referência e até as matrizes.
Construtor de Expressões
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:
Classes 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; }
}
Dados fictícios
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 apenas propriedade sem nenhuma matriz
p.Exclude(n => n.AddressDetail.CountryDetail.CountryName);
Caso 2: excluir propriedade com 1 matriz
p1.Exclude(n => n.AddressDetail.CountryDetail2.Explode().CountryName);
Caso 3: excluir propriedade com 2 matrizes aninhadas
p1.Exclude(n => n.AddressDetail.CountryDetail2.Explode().CountryDetail.Explode().Name);
Caso 4: Consulta EF GetAll com Inclui
var query = cs.GetAll("CompanyUsers", "CompanyUsers.User").ToArray();
query.Exclude(n => n.Explode().CompanyUsers.Explode().User.CompanyUsers);
return query;
Você notou que o método Explode () também é um método de extensão apenas para o nosso construtor de expressões obter a propriedade da propriedade da matriz. Sempre que houver uma propriedade de matriz, use .Explode (). YourPropertyToExclude ou .Explode (). Property1.MyArrayProperty.Explode (). MyStupidProperty . O código acima me ajuda a evitar a auto-referência tão profunda quanto eu quero. Agora eu posso usar GetAll e excluir a propriedade que eu não quero!
Obrigado por ler este grande post!