Ich habe 2 Modelle, von denen eines eine Kindersammlung des anderen hat:
[Table("ParentTable")]
public class Parent
{
[Key, Column("Parent")]
public string Id { get; set; }
[Column("ParentName")]
public string Name { get; set; }
public virtual ICollection<Widget> Widgets { get; set; }
}
[Table("WidgetTable")]
public class Widget
{
public string Year { get; set; }
[Column("Parent")]
public string ParentId { get; set; }
public string Comments { get; set; }
[Key, Column("ID_Widget")]
public int Id { get; set; }
[ForeignKey("ParentId"), JsonIgnore]
public virtual Parent Parent { get; set; }
}
Dieser Code funktioniert für> 99% der Widgets:
var parent = _dbContext.Parents.FirstOrDefault(p => p.Id == parentId);
Normalerweise ist parent.Widgets
eine Sammlung mit mehr als einem Element. In einigen Fällen ist parent.Widgets
jedoch null (keine Auflistung ohne Elemente).
Ich habe Query Analyzer verwendet, um sowohl die Abfrage für das übergeordnete Element als auch die Abfrage für Widgets, die zu diesem übergeordneten Element gehören, zu verfolgen. Beide geben genau die Zeilen zurück, die ich erwarte. Das Modell für eine oder zwei übergeordnete IDs führt jedoch zu einem Nullwert für die Widgets-Auflistung. Was könnte dazu führen, dass eine lazy-loaded-Auflistung in einigen Fällen null ist, aber nicht in anderen?
Diese Situation tritt häufig auf, wenn eine dbContext-Lebensdauer über einen Add-, saveChanges- und dann Retrieval-Modus offen gelassen wird.
Beispielsweise:
var context = new MyDbContext(); // holding Parents.
var testParent = new Parent{Id = "Parent1", Name = "Parent 1"};
context.Parents.Add(testParent);
An dieser Stelle, wenn Sie tun würden:
var result = context.Parents.FirstOrDefault(x=> x.ParentId == "Parent1");
Du würdest keinen Elternteil bekommen. Auswahl kommt aus engagierten Zustand .. Also ...
context.SaveChanges();
var result = context.Parents.FirstOrDefault(x=> x.ParentId == "Parent1");
Dadurch erhalten Sie einen Verweis auf das übergeordnete Element, das Sie eingefügt haben, da der Kontext über diese Entität bekannt ist und eine Referenz auf das von Ihnen erstellte Objekt besteht. Es geht nicht in den Datenzustand. Da Ihre Definition für Widgets gerade mit einer get / set-Eigenschaft definiert wurde, ist die Widgets-Sammlung in diesem Fall #null.
wenn du das tust:
context.Dispose();
context = new MyDbContext();
var result = context.Parents.FirstOrDefault(x=> x.ParentId == "Parent1");
In diesem Fall ist das übergeordnete Element im neuen Kontext nicht bekannt, sodass es in den Datenzustand übergeht. EF wird Ihnen eine Proxy-Liste für das verzögerte Laden der Widgets zurückgeben, die es nicht gibt, also erhalten Sie eine leere Liste zurück, nicht #null.
Wenn Sie mit Auflistungsklassen in EF arbeiten, vermeiden Sie am besten automatische Eigenschaften oder initialisieren Sie sie in Ihrem Konstruktor, um dieses Verhalten zu vermeiden. In der Regel möchten Sie Widgets nach dem Erstellen eines Parents zuweisen. Das Initialisieren eines Standardmembers ist besser, da Sie nie einen Setter für die Sammlungseigenschaft verwenden möchten.
Beispielsweise:
private readonly List<Widget> _widgets = new List<Widget>();
public virtual ICollection<Widget> Widgets
{
get { return _widgets; }
protected set { throw new InvalidOperationException("Do not set the Widget collection. Use Clear() and Add()"); }
}
Vermeiden Sie die Ausführung einer Set-Operation für eine Auflistungseigenschaft, da dies in Entitätsreferenzszenarios versagt. Zum Beispiel, wenn Sie Ihre Widget-Sammlung nach Jahr sortieren wollten und so etwas wie
parent.Widgets = parent.Widgets.OrderBy(x=> x.Year).ToList();
Scheint unschuldig genug, aber als die Widgets-Referenz ein EF-Proxy war, haben Sie es einfach weggeblasen. EF kann jetzt keine Änderungsverfolgung für die Sammlung durchführen.
Initialisieren Sie Ihre Sammlung und Sie sollten Überraschungen mit #null-Auflistungsreferenzen vermeiden. Auch ich würde auf die Lebensdauer Ihres dbContext schauen. Es ist gut, einen über die Lebensdauer einer Anfrage oder einer bestimmten Operation initialisiert zu halten, aber vermeiden, sie länger als nötig am Leben zu halten. Context-Change-Tracking und solche verbrauchen Ressourcen und Sie können scheinbar intermittierend ungerade Verhalten wie diese finden, wenn sie Operationen überschreiten.