Stavo affrontando lo stesso problema e ho provato a usare JsonSetting per ignorare l'errore di autoreferenziazione che ha funzionato fino a quando non ho ottenuto una classe che si auto-referenzia molto profondamente e il mio processo dot-net si blocca sul valore di scrittura di Json.
Il mio 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; }
}
Puoi vedere il problema nella classe Utente che fa riferimento alla classe CompanyUser che è un auto-riferimento.
Ora sto chiamando il metodo GetAll che include tutte le proprietà relazionali.
cs.GetAll("CompanyUsers", "CompanyUsers.User");
In questa fase il mio processo DotNetCore si blocca sull'esecuzione di JsonResult, scrivendo valore ... e non arriva mai. Nel mio Startup.cs, ho già impostato JsonOption. Per qualche motivo EFCore include la proprietà nidificata che non sto chiedendo a Ef di dare.
options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
il comportamento previsto dovrebbe essere questo
Ehi EfCore, per favore, puoi includere anche i dati di "CompanyUsers" nella mia classe aziendale in modo che io possa accedere facilmente ai dati.
poi
Ehi EfCore puoi anche includere i dati "CompanyUsers.User" in modo da poter accedere facilmente ai dati come questo
Company.CompanyUsers.First (). User.DisplayName
a questo punto dovrei ottenere questo "Company.CompanyUsers.First (). User.DisplayName" e non dovrei darmi Company.CompanyUsers.First (). User.CompanyUsers che causano il problema di autoreferenziazione; Tecnicamente non dovrebbe darmi User.CompanyUsers come CompanyUsers è una proprietà di navigazione. Ma EfCore si emoziona molto e mi dà User.CompanyUsers .
Quindi, ho deciso di scrivere un metodo di estensione per escludere la proprietà dall'oggetto (in realtà non si esclude, è solo impostare la proprietà su null). Non solo funzionerà anche con le proprietà dell'array. sotto è il codice che vado anche ad esportare il pacchetto nuget per altri utenti (non sono sicuro che ciò aiuti anche qualcuno). Il motivo è semplice perché sono troppo pigro per scrivere .Seleziona (n => nuovo {n.p1, n.p2}); Non voglio solo scrivere un'istruzione select per escludere solo 1 proprietà!
Questo non è il codice migliore (lo aggiornerò a un certo punto) come ho scritto in fretta e anche se questo potrebbe aiutare qualcuno che vuole escludere (impostare null) anche nell'oggetto con array.
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();
}
}
sopra la classe di estensione ti darà la possibilità di impostare la proprietà su null per evitare persino il ciclo di autoreferenziazione.
Generatore di espressioni
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);
}
}
usi:
Classi di modello
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; }
}
Dati fittizi
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" } } },
}
}
};
casi:
Caso 1: escludere solo la proprietà senza alcun array
p.Exclude(n => n.AddressDetail.CountryDetail.CountryName);
Caso 2: Escludere la proprietà con 1 array
p1.Exclude(n => n.AddressDetail.CountryDetail2.Explode().CountryName);
Caso 3: Escludere la proprietà con 2 array nidificati
p1.Exclude(n => n.AddressDetail.CountryDetail2.Explode().CountryDetail.Explode().Name);
Caso 4: EF GetAll Query con include
var query = cs.GetAll("CompanyUsers", "CompanyUsers.User").ToArray();
query.Exclude(n => n.Explode().CompanyUsers.Explode().User.CompanyUsers);
return query;
Si è notato che il metodo Explode () è anche un metodo di estensione solo per il nostro generatore di espressioni per ottenere la proprietà dalla proprietà array. Ogni volta che esiste una proprietà array, utilizzare .Explode (). YourPropertyToExclude o .Explode (). Property1.MyArrayProperty.Explode (). MyStupidProperty . Il codice sopra mi aiuta a evitare l'auto-riferimento tanto profondo quanto profondo voglio. Ora posso usare GetAll ed escludere la proprietà che non desidero!
Grazie per aver letto questo grande post!