Navigations de relation

Les relations EF Core sont définies par des clés étrangères. Les navigations sont superposées sur des clés étrangères pour fournir une vue naturelle orientée objet pour la lecture et la manipulation des relations. En utilisant des navigations, les applications peuvent utiliser des graphiques d’entités sans être préoccupés par ce qui se passe aux valeurs de clé étrangère.

Important

Plusieurs relations ne peuvent pas partager de navigations. Toute clé étrangère peut être associée au plus une navigation du principal à dépendante, et au plus une navigation de dépendant au principal.

Conseil

Il n’est pas nécessaire de rendre les navigations virtuelles, sauf si elles sont utilisées par des proxys de chargement différé ou de suivi des modifications.

Navigations de référence

Les navigations sont fournies dans deux formulaires - référence et collection. Les navigations de référence sont des références d’objet simples à une autre entité. Ils représentent le ou les côtés « un » des relations un-à-plusieurs et un-à-un. Par exemple :

public Blog TheBlog { get; set; }

Les navigations de référence doivent avoir un setter, même s’il n’a pas besoin d’être publics. Les navigations de référence ne doivent pas être initialisées automatiquement en valeur par défaut non null ; cela équivaut à affirmer qu’une entité existe quand elle n’existe pas.

Lorsque vous utilisez des types de référence pouvant accepter la valeur Null (nullable) C#, les navigations de référence doivent être nullables pour les relations facultatives :

public Blog? TheBlog { get; set; }

Les navigations de référence pour les relations requises peuvent être nullables ou non nullables.

Navigations de collection

Les navigations de collection sont des instances d’un type de collection .NET ; autrement dit, tout type implémentant ICollection<T>. La collection contient des instances du type d’entité associé, dont il peut y avoir n’importe quel nombre. Ils représentent le ou les « nombreux » côtés de des relations un-à-plusieurs et des relations plusieurs-à-plusieurs. Par exemple :

public ICollection<Post> ThePosts { get; set; }

Les navigations de collection n’ont pas besoin d’avoir un setter. Il est courant d’initialiser la collection inline, supprimant ainsi la nécessité de vérifier si la propriété est null. Par exemple :

public ICollection<Post> ThePosts { get; } = new List<Post>();

Conseil

Ne créez pas accidentellement une propriété corporelle d’expression, telle que public ICollection<Post> ThePosts => new List<Post>();. Cela crée une instance de collection vide chaque fois que la propriété est accessible et sera donc inutile en tant que navigation.

Types de collections

L’instance de collection sous-jacente doit implémenter ICollection<T> et doit avoir une méthode de travail Add. Il est courant d’utiliser List<T> ou HashSet<T>. List<T> est efficace pour un petit nombre d’entités associées et gère un classement stable. HashSet<T> a des recherches plus efficaces pour un grand nombre d’entités, mais n’a pas de classement stable. Vous pouvez également utiliser votre propre implémentation de collection personnalisée.

Important

La collection doit utiliser l’égalité de référence. Lors de la création d’une HashSet<T> navigation pour une collection, veillez à utiliser ReferenceEqualityComparer.

Les tableaux ne peuvent pas être utilisés pour les navigations de collection, car, même s’ils implémentent ICollection<T>, la Add méthode lève une exception lorsqu’elle est appelée.

Même si l’instance de collection doit être un ICollection<T>, la collection n’a pas besoin d’être exposée en tant que telle. Par exemple, il est courant d’exposer la navigation sous la forme IEnumerable<T>d’un affichage en lecture seule qui ne peut pas être modifié de manière aléatoire par le code de l’application. Par exemple :

public class Blog
{
    public int Id { get; set; }
    public IEnumerable<Post> ThePosts { get; } = new List<Post>();
}

Une variante de ce modèle inclut des méthodes de manipulation de la collection si nécessaire. Par exemple :

public class Blog
{
    private readonly List<Post> _posts = new();

    public int Id { get; set; }

    public IEnumerable<Post> Posts => _posts;

    public void AddPost(Post post) => _posts.Add(post);
}

Le code de l’application peut toujours convertir la collection exposée en un ICollection<T>, puis le manipuler. S’il s’agit d’une préoccupation, l’entité peut retourner une copie défensive de la collection. Par exemple :

public class Blog
{
    private readonly List<Post> _posts = new();

    public int Id { get; set; }

    public IEnumerable<Post> Posts => _posts.ToList();

    public void AddPost(Post post) => _posts.Add(post);
}

Déterminez soigneusement si la valeur acquise à partir de cette valeur est suffisamment élevée pour qu’elle l’emporte sur la surcharge de création d’une copie de la collection chaque fois que la navigation est accessible.

Conseil

Ce modèle final fonctionne car, par défaut, EF accède à la collection via son champ de stockage. Cela signifie qu’EF lui-même ajoute et supprime des entités de la collection réelle, tandis que les applications interagissent uniquement avec une copie défensive de la collection.

Initialisation des navigations de collection

Les navigations de collection peuvent être initialisées par le type d’entité, soit avec impatience :

public class Blog
{
    public ICollection<Post> Posts { get; } = new List<Post>();
}

Ou paresseux :

public class Blog
{
    private ICollection<Post>? _posts;

    public ICollection<Post> Posts => _posts ??= new List<Post>();
}

Si EF doit ajouter une entité à une navigation de collection, par exemple, lors de l’exécution d’une requête, elle initialise la collection si elle est actuellement null. L’instance créée dépend du type exposé de la navigation.

  • Si la navigation est exposée en tant que HashSet<T>, une instance d’utilisation HashSet<T>est créée.ReferenceEqualityComparer
  • Sinon, si la navigation est exposée en tant que type concret avec un constructeur sans paramètre, une instance de ce type concret est créée. Cela s’applique à List<T>, mais également à d’autres types de collection, y compris les types de collection personnalisés.
  • Sinon, si la navigation est exposée sous la forme d’un IEnumerable<T>, d’un ICollection<T>ou d’un ISet<T>, une instance d’utilisation HashSet<T>ReferenceEqualityComparer est créée.
  • Sinon, si la navigation est exposée en tant qu’instance IList<T>, une instance de celle-ci List<T> est créée.
  • Sinon, une exception est levée.

Remarque

Si les entitésde notification, y compris les proxys de suivi des modifications, sont utilisées, puis ObservableCollection<T>et ObservableHashSet<T> sont utilisées à la place List<T>et HashSet<T>.

Important

Comme décrit dans la documentation de suivi des modifications, EF effectue uniquement le suivi d’une seule instance d’une entité avec une valeur de clé donnée. Cela signifie que les collections utilisées comme navigations doivent utiliser la sémantique d’égalité de référence. Les types d’entités qui ne remplacent pas l’égalité des objets obtiennent cela par défaut. Veillez à l’utiliser ReferenceEqualityComparer lors de la création d’une HashSet<T> fonctionnalité de navigation pour vous assurer qu’elle fonctionne pour tous les types d’entités.

Configuration des navigations

Les navigations sont incluses dans le modèle dans le cadre de la configuration d’une relation. Autrement dit, par convention ou à l’aide de HasOne,HasMany etc. dans l’API de génération de modèles. La plupart des configurations associées aux navigations sont effectuées en configurant la relation elle-même.

Toutefois, il existe certains types de configuration spécifiques aux propriétés de navigation elles-mêmes, plutôt que de faire partie de la configuration globale de la relation. Ce type de configuration est effectué avec la méthode Navigation. Par exemple, pour forcer EF à accéder à la navigation via sa propriété, plutôt que d’utiliser le champ de stockage :

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .Navigation(e => e.Posts)
        .UsePropertyAccessMode(PropertyAccessMode.Property);

    modelBuilder.Entity<Post>()
        .Navigation(e => e.Blog)
        .UsePropertyAccessMode(PropertyAccessMode.Property);
}

Remarque

L’appel Navigation ne peut pas être utilisé pour créer une propriété de navigation. Il est utilisé uniquement pour configurer une propriété de navigation qui a été créée précédemment en définissant une relation ou à partir d’une convention.

Navigations requises

Une navigation de dépendant au principal est requise si la relation est requise, ce qui signifie à son tour que la propriété de clé étrangère n’est pas nullable. À l’inverse, la navigation est facultative si la clé étrangère est nullable et que la relation est donc facultative.

Les navigations de référence du principal vers dépendantes sont différentes. Dans la plupart des cas, une entité principale peut toujours exister sans entités dépendantes. Autrement dit, une relation requise n' pas indique qu’il y aura toujours au moins une entité dépendante. Il n’existe aucun moyen dans le modèle EF, et pas non plus de façon standard dans une base de données relationnelle, pour s’assurer qu’un principal est associé à un certain nombre de dépendants. Si nécessaire, elle doit être implémentée dans la logique d’application (métier).

Il existe une exception à cette règle : lorsque le principal et les types dépendants partagent la même table dans une base de données relationnelle ou contenus dans un document. Cela peut se produire avec des types détenus ou des types non détenus partageant la même table. Dans ce cas, la propriété de navigation du principal au dépendant peut être marquée comme nécessaire, indiquant que la dépendance doit exister.

La configuration de la propriété de navigation comme nécessaire est effectuée à l’aide de la Navigation méthode. Par exemple :

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .Navigation(e => e.BlogHeader)
        .IsRequired();
}