J'ai 2 modèles, dont l'un a une collection enfant de l'autre:
[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; }
}
Ce code fonctionne pour> 99% des widgets:
var parent = _dbContext.Parents.FirstOrDefault(p => p.Id == parentId);
En parent.Widgets
générale, parent.Widgets
est une collection contenant plusieurs éléments. Dans quelques cas, cependant, parent.Widgets
est null (pas une collection sans éléments).
J'ai utilisé l'Analyseur de requêtes pour suivre à la fois la requête pour le parent et la requête pour les widgets appartenant à ce parent. Les deux renvoient exactement les rangées que j'attends. Cependant, le modèle pour un ou deux ID parents génère une valeur null pour la collection de widgets. Qu'est-ce qui pourrait rendre une collection chargée paresseux nulle dans certains cas mais pas dans d'autres?
Cette situation se produit généralement lorsqu'une durée de vie dbContext est laissée ouverte sur une opération Add, saveChanges, puis une récupération.
Par exemple:
var context = new MyDbContext(); // holding Parents.
var testParent = new Parent{Id = "Parent1", Name = "Parent 1"};
context.Parents.Add(testParent);
À ce stade, si vous deviez faire:
var result = context.Parents.FirstOrDefault(x=> x.ParentId == "Parent1");
tu n'aurais pas de parent. La sélection vient d'un état engagé .. Alors ...
context.SaveChanges();
var result = context.Parents.FirstOrDefault(x=> x.ParentId == "Parent1");
Cela vous renverra une référence au parent que vous avez inséré car le contexte connaît cette entité et contient une référence à l'objet que vous avez créé. Il ne va pas à l'état de données. Puisque votre définition de Widgets vient d'être définie avec une propriété auto get / set, la collection de Widgets sera dans ce cas #null.
Si tu fais ça:
context.Dispose();
context = new MyDbContext();
var result = context.Parents.FirstOrDefault(x=> x.ParentId == "Parent1");
Dans ce cas, le nouveau contexte ne connaît pas le parent, il passe donc à l'état de données. EF vous renverra une liste de proxy pour le chargement paresseux des widgets, ce qui n’existe pas, vous récupérant ainsi une liste vide et non #null.
Lorsque vous traitez avec des classes de collection dans EF, il est préférable d'éviter les propriétés automatiques ou de les initialiser dans votre constructeur pour éviter ce problème. vous souhaiterez généralement assigner des widgets après la création d'un parent. Il est préférable d’initialiser un membre par défaut car vous ne voulez pas encourager l’utilisation d’un setter dans la propriété de collection.
Par exemple:
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()"); }
}
Évitez d'exécuter une opération Set sur une propriété de collection, car cela risquerait de provoquer des scénarios de référence d'entité. Par exemple, si vous souhaitez trier votre collection de widgets par année et effectuez les opérations suivantes:
parent.Widgets = parent.Widgets.OrderBy(x=> x.Year).ToList();
Cela semble assez innocent, mais lorsque la référence à Widgets était un proxy EF, vous l'avez simplement épatée. EF ne peut désormais plus effectuer de suivi des modifications sur la collection.
Initialisez votre collection et évitez les surprises avec les références à #null collection. Aussi, je regarderais la durée de vie de votre dbContext. Il est bon d’en garder une initialisée pendant la durée de vie d’une requête ou d’une opération particulière, mais évitez de les maintenir en vie plus longtemps que nécessaire. Le suivi des changements de contexte et l’utilisation de telles ressources consomment des ressources, et vous pouvez trouver un comportement étrange et apparemment intermittent comme celui-ci lorsqu’elles traversent des opérations.