web-dev-qa-db-fra.com

Pourquoi ma collection de proxy Entity Framework Code First est-elle nulle et pourquoi ne puis-je pas la définir?

J'utilise DBContext et j'ai deux classes dont les propriétés sont toutes virtuelles. Je peux voir dans le débogueur que j'obtiens un objet proxy lorsque j'interroge le contexte. Cependant, une propriété de collection est toujours nulle lorsque j'essaye d'y ajouter. Je pensais que le proxy garantirait l'initialisation de la collecte.

Parce que mon objet Poco peut être utilisé en dehors de son contexte de données, j'ai ajouté une vérification pour que la collection soit nulle dans le constructeur et la crée si nécessaire:

public class DanceStyle
{
    public DanceStyle()
    {
        if (DanceEvents == null)
        {
            DanceEvents = new Collection<DanceEvent>();
        }
    }
    ...
    public virtual ICollection<DanceEvent> DanceEvents { get; set; }
}

Cela fonctionne en dehors du contexte de données mais si je récupère un objet à l'aide d'une requête, bien que le test soit vrai, lorsque j'essaie de le définir, j'obtiens l'exception suivante: 'La propriété' DanceEvents 'sur le type' DanceStyle_B6089AE40D178593955F1328A70EAA3D8F0F01DDE9F9FBD615F60A34F9178 collection est déjà définie sur une EntityCollection. '

Je peux voir qu'il est nul et je ne peux pas y ajouter, mais je ne peux pas non plus le définir sur une collection car le proxy dit qu'il est déjà défini. Je ne peux donc pas l'utiliser. Je suis confus.

Voici la classe DanceEvent:

public class DanceEvent
{
    public DanceEvent()
    {
        if (DanceStyles == null)
        {
            DanceStyles = new Collection<DanceStyle>();
        }
    }
    ...
    public virtual ICollection<DanceStyle> DanceStyles { get; set; }
}

J'ai omis les autres propriétés de type valeur du code ci-dessus. Je n'ai aucun autre mappage pour ces classes dans la classe de contexte.

41
Rob Kent

J'ai trouvé la solution à ce problème ici: Code First ajoutant aux collections? Comment utiliser Code First avec les référentiels?

J'ai supprimé "virtuel" de toutes les propriétés sauf les collections et les objets chargés paresseux, c'est-à-dire tous les types natifs.

Mais je ne comprends toujours pas comment vous pouvez vous retrouver dans la situation où vous avez une collection nulle que vous ne pouvez pas utiliser et n'avez aucun moyen de la définir sur une collection valide.

J'ai aussi trouvé cette réponse de Rowan Miller sur un forum MSDN

Salut,

Si vous rendez toutes vos propriétés virtuelles, EF générera des classes de proxy lors de l'exécution qui dérivent de votre classe POCO, ces proxys permettent à EF de connaître les modifications en temps réel plutôt que d'avoir à capturer les valeurs d'origine de votre objet, puis à rechercher les modifications lorsque vous enregistrez (cela a évidemment des avantages en termes de performances et d'utilisation de la mémoire, mais la différence sera négligeable à moins que vous ayez un grand nombre d'entités chargées en mémoire). Ceux-ci sont connus sous le nom de `` proxys de suivi des modifications '', si vous rendez vos propriétés de navigation virtuelles, un proxy est toujours généré, mais il est beaucoup plus simple et inclut simplement une logique pour effectuer un chargement paresseux lorsque vous accédez à une propriété de navigation.

Étant donné que votre code d'origine générait des proxys de suivi des modifications, EF remplaçait votre propriété de collection par un type de collection spécial pour l'aider à découvrir les modifications. Parce que vous essayez de redéfinir la collection sur une simple liste dans le constructeur, vous obtenez l'exception.

À moins que vous ne constatiez des problèmes de performances, je suivrais la suggestion de Terrence et je supprimerais simplement "virtuel" de vos propriétés de non-navigation.

~ Rowan

Il semble donc que je n'ai le problème avec un "proxy de suivi des modifications" complet que si toutes mes propriétés sont virtuelles. Mais étant donné cela, pourquoi ne puis-je toujours pas utiliser la propriété virtuelle sur le proxy de suivi des modifications? Ce code explose sur la ligne trois car ds2.DanceEvents est null et ne peut pas être défini dans le constructeur:

DanceStyle ds2 = ctx.DanceStyles.Where(ds => ds.DanceStyleId == 1).Single();
DanceEvent evt = CreateDanceEvent();
ds2.DanceEvents.Add(evt);

Je suis toujours confus, même si mon code fonctionne maintenant à cause du correctif ci-dessus.

13
Rob Kent

Comme vous l'avez correctement observé dans la réponse à votre propre question, la suppression du mot clé "virtuel" des propriétés de la collection contourne le problème, en empêchant Entity Framework de créer un proxy de suivi des modifications. Cependant, ce n'est pas une solution pour de nombreuses personnes, car les proxys de suivi des modifications peuvent être très pratiques et peuvent aider à prévenir les problèmes lorsque vous oubliez de détecter les modifications aux bons endroits dans votre code.

Une meilleure approche serait de modifier vos classes POCO, afin qu'elles instancient les propriétés de la collection dans leur accesseur get, plutôt que dans le constructeur. Voici votre classe POCO, modifiée pour permettre la création d'un proxy de suivi des modifications:

public class DanceEvent
{
    private ICollection<DanceStyle> _danceStyles;
    public virtual ICollection<DanceStyle> DanceStyles
    {
        get { return _danceStyles ?? (_danceStyles = new Collection<DanceStyle>()); }
        protected set { _danceStyles = value; }
    }
}

Dans le code ci-dessus, la propriété de collection n'est plus automatique, mais possède plutôt un champ de sauvegarde. Il est préférable de laisser le setter protégé, empêchant tout code (autre que le proxy) de modifier ultérieurement ces propriétés. Vous remarquerez que le constructeur n'était plus nécessaire et a été supprimé.

48
Pando

Vieille question ...

Classe Poco:

public partial class MyPOCO
{
    public MyPOCO()
    {
        this.MyPocoSub = new HashSet<MyPocoSub>();
    }

    //VIRTUAL
    public virtual ICollection<MyPocoSub> MyPocoSub { get; set; }
}

et code proxy:

    public override ICollection<MyPocoSubSet> MyPocoSubSets
    {
        get
        {
            ICollection<MyPocoSubSet> myPocoSubSets = base.MyPocoSubSets;
            if (!this.ef_proxy_interceptorForMyPocoSubSets(this, myPocoSubSets))
            {
                return base.MyPocoSubSets;
            }
            return myPocoSubSets;
        }
        set
        {
            if (value != this.RelationshipManager.GetRelatedEnd("WindowsFormsApplication.Models.MyPocoSubSet_MyPOCO", "MyPocoSubSet_MyPOCO_Source"))
            {
                // EXCEPTION 
                throw new InvalidOperationException("The property 'MyPocoSubSets' on type 'MyPOCO_A78FCE6C6A890855C68B368B750864E3136B589F9023C7B1D90BF7C83FD291AC' cannot be set because the collection is already set to an EntityCollection.");
            }
            base.MyPocoSubSets = value;
        }
    }

Comme vous pouvez le voir, l'exception levée dans la classe proxy dans ExtityFramework 5. Cela signifie que le comportement existe toujours.

3
Ayhan