web-dev-qa-db-fra.com

Code EF d'abord: dois-je initialiser les propriétés de navigation?

J'avais vu quelques livres (par exemple code de cadre d'entité de programmation d'abord Julia Lerman ) définir leurs classes de domaine (POCO) sans initialisation des propriétés de navigation comme:

public class User
{
    public int Id { get; set; }
    public string UserName { get; set; }

    public virtual ICollection<Address> Address { get; set; }
    public virtual License License { get; set; }
}

d'autres livres ou outils (par exemple Entity Framework Power Tools ) lors de la génération de POCO initialise les propriétés de navigation de la classe, comme:

public class User
{
    public User()
    {
        this.Addresses = new IList<Address>();
        this.License = new License();
    }
    public int Id { get; set; }
    public string UserName { get; set; }

    public virtual ICollection<Address> Addresses { get; set; }
    public virtual License License { get; set; }
}

Q1: Lequel est le meilleur? Pourquoi? Avantages et inconvénients?

Modifier:

public class License
{
    public License()
    {
        this.User = new User();
    }
    public int Id { get; set; }
    public string Key { get; set; }
    public DateTime Expirtion { get; set; }

    public virtual User User { get; set; }
}

Q2: Dans la deuxième approche, il y aurait débordement de pile si la classe `License` a également une référence à la classe` User`. Cela signifie que nous devrions avoir une référence à sens unique. (?) Comment devons-nous décider laquelle des propriétés de navigation doit être supprimée?

57
Iman Mahmoudinasab

Collections: peu importe.

Il existe une différence nette entre les collections et les références en tant que propriétés de navigation. Une référence est une entité. Une collection contient des entités. Cela signifie que l'initialisation d'une collection est sans signification en termes de logique métier: elle ne définit pas d'association entre entités. La définition d'une référence le fait.

C'est donc une question de préférence que vous initialisiez ou non, ou comment, vous initialisez des listes intégrées.

Quant au "comment", certains préfèrent l'initialisation paresseuse:

private ICollection<Address> _addresses;

public virtual ICollection<Address> Addresses
{ 
    get { return this._addresses ?? (this._addresses = new HashSet<Address>());
}

Il empêche les exceptions de référence nulles, ce qui facilite les tests unitaires et la manipulation de la collection, mais il empêche également l'initialisation inutile. Ce dernier peut faire la différence lorsqu'une classe possède relativement de nombreuses collections. L'inconvénient est qu'il faut relativement beaucoup de plomberie, en particulier. par rapport aux propriétés automatiques sans initialisation. De plus, l'arrivée de l'opérateur de propagation nulle en C # a rendu moins urgente l'initialisation des propriétés de collection.

... sauf si un chargement explicite est appliqué

La seule chose est que l'initialisation des collections rend difficile de vérifier si une collection a été chargée par Entity Framework ou non. Si une collection est initialisée, une déclaration comme ...

var users = context.Users.ToList();

... créera User objets ayant des collections Addresses vides et non nulles (chargement paresseux de côté). Vérifier si la collection est chargée nécessite un code comme ...

var user = users.First();
var isLoaded = context.Entry(user).Collection(c => c.Addresses).IsLoaded;

Si la collection n'est pas initialisée, une simple vérification de null fera l'affaire. Ainsi, lorsque le chargement explicite sélectif est une partie importante de votre pratique de codage, c'est-à-dire ...

if (/*check collection isn't loaded*/)
    context.Entry(user).Collection(c => c.Addresses).Load();

... il peut être plus pratique de ne pas initialiser les propriétés de la collection.

Propriétés de référence: Ne pas

Les propriétés de référence sont des entités, donc leur affecter un objet vide est significatif.

Pire, si vous les lancez dans le constructeur, EF ne les remplacera pas lors de la matérialisation de votre objet ou par chargement paresseux. Ils auront toujours leurs valeurs initiales jusqu'à ce que activement les remplaciez. Pire encore, vous pouvez même finir par enregistrer des entités vides dans la base de données!

Et il y a un autre effet: la correction de la relation ne se produira pas. La correction des relations est le processus par lequel EF connecte toutes les entités du contexte par leurs propriétés de navigation. Lorsqu'un User et un Licence sont chargés séparément, toujours User.License sera rempli et vice versa. Sauf bien sûr, si License a été initialisé dans le constructeur. Cela est également vrai pour les associations 1: n. Si Address initialise un User dans son constructeur, User.Addresses ne serait pas rempli!

noyau Entity Framework

La correction des relations dans le noyau Entity Framework (2.1 au moment de la rédaction) n'est pas affectée par les propriétés de navigation de référence initialisées dans les constructeurs. En d'autres termes, lorsque les utilisateurs et les adresses sont extraits séparément de la base de données, les propriétés de navigation sont renseignées.
Cependant, le chargement différé ne pas écrase les propriétés de navigation de référence initialisées. Donc, en conclusion, également dans le noyau EF, l'initialisation des propriétés de navigation de référence dans les constructeurs peut causer des problèmes. Ne le fais pas. Cela n'a pas de sens de toute façon,

70
Gert Arnold

Dans tous mes projets je respecte la règle - "Les collections ne doivent pas être nulles. Elles sont vides ou ont des valeurs."

Le premier exemple est possible lorsque la création de ces entités est la responsabilité du code tiers (par exemple ORM) et que vous travaillez sur un projet à court terme.

Le deuxième exemple est meilleur, car

  • vous êtes sûr que l'entité a toutes les propriétés définies
  • vous évitez les idiots NullReferenceException
  • vous rendez les consommateurs de votre code plus heureux

Les personnes qui pratiquent la conception pilotée par domaine exposent les collections en lecture seule et évitent les paramétreurs sur elles. (voir Quelle est la meilleure pratique pour les listes en lecture seule dans NHibernate )

Q1: Lequel est le meilleur? Pourquoi? Avantages et inconvénients?

Il est préférable d'exposer les collectes non nulles car vous évitez les vérifications supplémentaires dans votre code (par exemple Addresses). C'est un bon contrat à avoir dans votre base de code. Mais il est OK pour moi d'exposer une référence nullable à une seule entité (par exemple License)

Q2: Dans la seconde approche, il y aurait débordement de pile si la classe License a également une référence à la classe User. Cela signifie que nous devrions avoir une référence à sens unique. (?) Comment devons-nous décider laquelle des propriétés de navigation doit être supprimée?

Quand j'ai développé modèle de mappeur de données par moi-même, j'ai essayé d'éviter les références bidirectionnelles et j'avais très rarement des références de l'enfant au parent.

Lorsque j'utilise des ORM, il est facile d'avoir des références bidirectionnelles.

Lorsqu'il est nécessaire de créer une entité de test pour mes tests unitaires avec un ensemble de références bidirectionnelles, je suis les étapes suivantes:

  1. Je construis parent entity avec emty children collection.
  2. Ensuite, j'ajoute evey child en référence à parent entity en children collection.

Instancé d'avoir un constructeur sans paramètre dans le type License je ferais la propriété user requise.

public class License
{
    public License(User user)
    {
        this.User = user;
    }

    public int Id { get; set; }
    public string Key { get; set; }
    public DateTime Expirtion { get; set; }

    public virtual User User { get; set; }
}
4
Ilya Palkin

Il est redondant pour new la liste, car votre POCO dépend du chargement différé.

Le chargement différé est le processus par lequel une entité ou une collection d'entités est automatiquement chargée à partir de la base de données lors du premier accès à une propriété faisant référence à l'entité/aux entités. Lorsque vous utilisez des types d'entités POCO, le chargement différé est obtenu en créant des instances de types de proxy dérivés, puis en remplaçant les propriétés virtuelles pour ajouter le crochet de chargement.

Si vous supprimez le modificateur virtuel, vous désactivez le chargement paresseux et, dans ce cas, votre code ne fonctionne plus (car rien n'initialise la liste).

Notez que le chargement différé est une fonctionnalité prise en charge par le framework d'entité, si vous créez la classe en dehors du contexte d'un DbContext, le code dépendant souffrirait évidemment d'un NullReferenceException

HTH

3
bas

Q1: Lequel est le meilleur? Pourquoi? Avantages et inconvénients?

La deuxième variante lorsque les propriétés virtuelles sont définies à l'intérieur d'un constructeur d'entité a un problème défini qui est appelé " appel de membre virtuel dans un constructeur ".

Quant à la première variante sans initialisation des propriétés de navigation, il existe 2 situations selon qui/quoi crée un objet:

  1. Le framework d'entité crée un objet
  2. Le consommateur de code crée un objet

La première variante est parfaitement valide lorsque Entity Framework crée un objet, mais peut échouer lorsqu'un consommateur de code crée un objet.

La solution pour garantir qu'un consommateur de code crée toujours un objet valide consiste à utiliser un méthode d'usine statique :

  1. Rendre le constructeur par défaut protégé. Entity Framework convient parfaitement pour travailler avec des constructeurs protégés.

  2. Ajoutez une méthode d'usine statique qui crée un objet vide, par exemple un objet User, définit toutes les propriétés, par exemple Addresses et License, après création et retourne un objet User entièrement construit

De cette façon, Entity Framework utilise un constructeur par défaut protégé pour créer un objet valide à partir de données obtenues à partir d'une source de données et le consommateur de code utilise une méthode d'usine statique pour créer un objet valide.

3
Lightman

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

A eu des problèmes avec l'initialisation du constructeur. La seule raison pour laquelle je fais cela est de rendre le code de test plus facile. Faire en sorte que la collection ne soit jamais nulle me sauve constamment en initialisant les tests, etc.

2
GraemeMiller

Les autres réponses répondent pleinement à la question, mais je voudrais ajouter quelque chose, car cette question est toujours pertinente et apparaît dans les recherches Google.

Lorsque vous utilisez l'assistant "code premier modèle à partir de la base de données" dans Visual Studio, toutes les collections sont initialisées comme suit:

public partial class SomeEntity
{
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
    public SomeEntity()
    {
        OtherEntities = new HashSet<OtherEntity>();
    }

    public int Id { get; set; }

    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
    public virtual ICollection<OtherEntity> OtherEntities { get; set; }
}

J'ai tendance à considérer la sortie de l'assistant comme une recommandation officielle de Microsoft, c'est pourquoi j'ajoute à cette question vieille de cinq ans. Par conséquent, j'initialiserais toutes les collections comme HashSets.

Et personnellement, je pense qu'il serait assez astucieux de modifier ce qui précède pour tirer parti des initialiseurs de propriété automatique de C # 6.0:

    public virtual ICollection<OtherEntity> OtherEntities { get; set; } = new HashSet<OtherEntity>();
1
Jesse Hufstetler