Septembre 2017

Volume 32, numéro 9

Cet article a fait l'objet d'une traduction automatique.

Points de données - DDD-Friendlier EF Core 2.0

Par Julie Lerman

Julie LermanSi vous avez effectué cette colonne pour un certain temps, vous avez peut-être remarqué un certain articles sur l’implémentation d’Entity Framework (EF) lors de la création de solutions se pencher sur la conception orientée modèles et instructions. Même si DDD se concentre sur le domaine, pas sur le mode de conservation des données, à un moment donné, vous devez les données de circuler vers et depuis votre logiciel.

Au-delà des itérations de EF, ses motifs (conventionnelles ou personnalisées) ont les utilisateurs autorisés à mapper les classes de domaine FLUIDE directement à la base de données sans trop de friction. Et Mon guide a été généralement que si la couche de mappage EF prend en charge l’obtention de vos modèles de domaine bien conçue dans et en dehors de la base de données sans avoir à créer un modèle de données supplémentaires, cela est suffisant. Mais au moment où vous vous trouvez un ajustement vos classes de domaine et la logique pour qu’ils fonctionnent mieux avec Entity Framework, qui est un indicateur rouge qu’il est temps de créer un modèle pour la persistance des données et ensuite mapper à partir de classes de modèle de domaine pour les classes de modèle de données.

J’ai un peu surpris de savoir combien de ces articles sur les mappages DDD et EF apparus. Il a été quatre ans depuis la série en trois parties appelée « codage pour la conception de domaine : Conseils pour les développeurs Data-Focused, » qui couvrent les problèmes d’août, septembre et octobre 2013 de MSDN Magazine. Voici un lien vers la première partie, qui inclut des liens vers la série entière : msdn.com/magazine/dn342868.

A deux colonnes spécifiques qui a traité les modèles DDD et expliqué comment EF a ou n’a pas mapper facilement entre vos classes de domaine et de la base de données. À compter de EF6, un des plus grandes difficultés était le fait que vous n’a pas pu encapsuler et ainsi de protéger une collection de « enfant ». Utilisation des modèles connus pour la protection de la collection (généralement cela signifiait em-ploying IEnumerable) n’a pas été aligner avec les exigences de EF et EF ne reconnaît même que le volet de navigation doit être la partie du modèle. Steve Smith et j’ai passé beaucoup de temps sur lors que nous avons créé notre cours Pluralsight, notions de base de la conception (bit.ly/PS-jjj) et finalement Steve fournie avec une bonne solution de contournement (bit.ly/ 2ufw89D).

EF Core enfin de résoudre ce problème avec la version 1.1 et j’ai écrit sur cette nouvelle fonctionnalité dans la colonne janvier 2017 (msdn.com/magazine/mt745093). EF Core 1.0 et 1.1 également résolu quelques autres contraintes DDD, mais reste quelques lacunes, notamment l’impossibilité de mapper les objets de valeur DDD utilisés dans vos types de domaine. La possibilité de faire existait dans EF depuis le début, mais il n’avait pas encore été mis à EF Core. Mais avec EF Core 2.0 à venir, cette restriction est désormais disparu.

Ce que je vais faire dans cet article est de présenter les fonctionnalités de EF Core 2.0 disponibles pour vous alignent avec la plupart des concepts DDD. EF Core 2.0 est beaucoup plus conviviale pour les développeurs qui tirant parti de ces concepts et peut-être qu’il vous présentent les pour la première fois. Même si vous ne prévoyez pas d’adopter DDD, vous pouvez tirer parti de ses nombreux modèles excellentes ! Et maintenant, vous pouvez effectuer ce plus encore avec EF de base.

Obtient un à un plus intelligent

Dans son livre, « La conception, » Eric Evans indique, » une association bidirectionnelle signifie que les deux objets peuvent être derstood-annuler uniquement entre eux. Lors de la configuration requise pour application n’appelez pas pour le parcours dans les deux sens, ajout d’un parcours di-rection réduit interdépendance et simplifie la conception. » Selon les consignes a supprimé en effet les effets de mon code. EF a toujours été en mesure de gérer les relations unidirectionnelle avec un-à-plusieurs et un à un. En fait, lors de l’écriture de cet article, j’ai appris que mon malentendu longtime qu’une relation avec les deux termine force requis vous dans une relation bidirectionnelle est incorrecte. Toutefois, vous avez à configurer explicitement ces flâner-tionships requis, et qui est un élément que vous n’avez à faire maintenant dans EF Core, à l’exception d’avec cases de bord.

Une exigence défavorable dans EF6 pour relations était que la propriété de clé dans le type dépendant devait double en tant que la clé étrangère vers l’entité de sécurité d’entité. Cela forcé vous permet de concevoir des classes de manière irrégulière, même si vous avez obtenu utilisé à ce dernier. Grâce à l’introduction de la prise en charge pour les clés étrangères uniques dans EF Core, vous pouvez maintenant avoir une propriété de clé étrangère explicite dans la terminaison dépendante de la relation. Une clé étrangère explicite est plus naturel. Et, dans la plupart des cas, EF Core doit être en mesure de déduire correctement la terminaison dépendante de la relation en fonction de l’existence de cette propriété de clé étrangère. Si elle ne l’obtenir à droite en raison de certains cas limite, vous devrez ajouter une configuration qui va vous montrer peu de temps quand je renomme la propriété de clé étrangère.

Pour illustrer une relation un à un, je vais utiliser mon domaine EF Core favoris : classes à partir de la séquence « Sept rai Samu » :

public class Samurai {
  public int Id { get; set; }
  public string Name { get; set; }
  public Entrance Entrance { get; set; }
}

public class Entrance {
  public int Id { get; set; }
  public string SceneName { get; set; }
  public int SamuraiId { get; set; }
}

Désormais, avec EF Core, ces deux classes : Samurai et entrée (du caractère première apparition dans la séquence) — correctement identifiée comme une relation unidirectionnelle, avec une entrée qui est le type dépendant. Vous n’avez pas besoin d’inclure une propriété de navigation dans l’entrée et vous n’avez pas besoin tout mappage spécial dans l’API Fluent. La clé étrangère (SamuraiId) suit la convention, EF Core est capable de reconnaître la relation.

EF Core déduit que la base de données, Entrance.SamuraiId est une clé étrangère unique pointant vers Samurai. Gardez à l’esprit que quelque chose que j’ai du mal à car (comme je dois constamment rappeler), EF Core n’est pas EF6 ! Par défaut, .NET et EF Core traitera le Samurai.Entrance comme une propriété facultative au moment de l’exécution à moins d’avoir une logique de domaine pour appliquer cette entrée est requise. À partir de EF4.3, vous avez l’avantage de la validation des API qui réponde à une annotation [obligatoire] dans la classe ou le mappage. Mais il n’existe aucune validation API (encore) ? dans Core EF à observer ce problème particulier. Et autres conditions requises qui sont liés à la base de données. Par exemple, Entrance.SamuraiId sera un type non nullable int. Si vous essayez d’insérer une entrée sans valeur SamuraiId remplie, EF Core ne sera pas intercepter les données non valides, ce qui signifie également que le fournisseur en mémoire actuellement ne se plainte. Mais votre base de données relationnelle doit lever une erreur pour le conflit de contraintes.

À partir d’un point de vue DDD, toutefois, cela n’est pas réellement un problème, car vous ne devez pas reposer sur la couche de persistance de souligner les erreurs dans votre logique de domaine. Si le Samurai requiert une entrée, qui est une règle d’entreprise. Si vous ne pouvez pas les entrées ou phaned, qui est également une règle d’entreprise. Par conséquent, la validation doit être quand même partie de votre logique de domaine.

Ces cas extrêmes suggéré précédemment, Voici un exemple. Si la clé étrangère dans l’entité dépendante (par exemple, entrée) ne suit la convention, vous pouvez utiliser l’API Fluent pour informer EF Core. Si Entrance.SamuraiId était, peut-être Entrance.SamuraiFK, vous pouvez de clarifier que FK via :

modelBuilder.Entity<Samurai>().HasOne(s=>s.Entrance)
  .WithOne().HasForeignKey<Entrance>(e=>e.SamuraiFK);

Si la relation est requise sur les deux extrémités (autrement dit, entrée doit avoir un Samurai) vous pouvez ajouter le paramètre IsRequired après WithOne.

Propriétés peuvent être davantage encapsulées.

DDD vous guide pour créer des agrégats (graphiques d’objets), où la racine d’agrégation (l’objet principal dans le graphique) est dans le contrôle de tous les autres objets dans le graphique. Cela signifie que l’écriture de code qui empêche tout autre code à partir de l’utilisation abusive ou même abusives les règles. En encapsulant des propriétés de sorte qu’ils ne peuvent pas être aléatoirement définissez (et, souvent, de façon aléatoire en lecture) est une méthode de clé de la protection d’un graphique. Dans EF6 et versions antérieures, il a toujours été possible d’élaborer scalaire et propriétés de navigation ont des méthodes setter privé et encore être reconnues par EF quand il lu et mis à jour des données, mais vous n’a pas pu facilement les propriétés privées. Une publication par Rowan Miller montre une manière de le faire dans EF6 et revient à certaines solutions antérieures (bit.ly/2eHTm2t). Et il n’existait aucun moyen true pour protéger une collection de navigation dans une relation un-à-plusieurs. Une grande partie a été écrite sur ce problème de ce dernier. Maintenant, non seulement peuvent aisément être EF travail avec les propriétés privées qui comportent les champs de stockage (ou déduit des champs de stockage), mais vous peut également réellement encapsuler les propriétés de la collection, Merci de prendre en charge pour le mappage IEnumerable < T >. J’ai écrit sur les champs de stockage et un IEnumerable < T > dans mon janvier 2017 mentionnées précédemment, donc je ne rehash les détails ici. Toutefois, cela est très important pour les modèles DDD et par conséquent pertinentes à noter dans cet article.

Pendant que vous pouvez masquer des valeurs scalaires et des regroupements, il existe un autre type de propriété peut très bien à encapsuler : propriétés de navigation. Collections de navigation tirent parti de la prise en charge IEnumerable < T >, mais les propriétés de navigation qui sont privés, tels que Samurai.Entrance, ne peut pas être compréhensible par le modèle. Toutefois, il est possible de configurer le modèle pour comprendre la propriété de navigation qui est masqué dans l’agrégat racine.

Par exemple, dans le code suivant, j’ai déclaré entrée comme une propriété privée de Samurai (et j’utilise même pas un champ de stockage explicite, bien que j’impossible si nécessaire). Vous pouvez créer une nouvelle entrée avec la méthode CreateEntrance (qui appelle une méthode de fabrique d’entrée) et vous ne pouvez lire que la propriété de SceneName d’une entrée. Notez que je suis utilisant l’opérateur de null-condition c# 6 afin d’éviter une exception si je n’ai pas encore chargé l’entrée :

private Entrance Entrance {get;set;}
public void CreateEntrance (string sceneName) {
    Entrance = Entrance.Create (sceneName);
  }
public string EntranceScene => Entrance?.SceneName;

Par convention, EF Core renouvellera présomption sur cette propriété privée. Même si j’avais le champ de stockage, l’entrée privée ne pourraient être détectée automatiquement et vous ne pourriez pas l’utiliser lors de l’interaction avec le magasin de données. Il s’agit d’une conception intentionnelle de l’API pour vous protéger contre les effets secondaires potentiels. Mais vous pouvez le configurer explicitement. N’oubliez pas que lorsque l’entrée est publique, EF Core est à même de comprendre la relation. Toutefois, car il est privé vous devez tout d’abord afin de vous assurer que EF sait à ce sujet.

Dans OnModelCreating, vous devez ajouter le mappage fluent HasOne/WithOne pour rendre EF Core prenant en charge. Étant donné que l’entrée est privée, vous ne pouvez pas utiliser une expression lambda en tant que paramètre de HasOne. Au lieu de cela, vous devez décrire la propriété par son type et son nom. WithOne prend généralement une expression lambda pour spécifier la propriété de navigation à l’autre terminaison de l’association. Mais l’entrée n’a pas une propriété de navigation Samurai, mais uniquement la clé étrangère. C’est parfait ! Vous pouvez laisser le paramètre vide parce que EF Core a maintenant suffisamment d’informations pour déterminer :

modelBuilder.Entity<Samurai> ()
  .HasOne (typeof (Entrance), "Entrance").WithOne();

Que se passe-t-il si vous utilisez une propriété de sauvegarde, telles que _entrance dans la classe Samurai, comme indiqué dans ces modifications :

private Entrance _entrance;
private Entrance Entrance { get{return _entrance;} }
public void CreateEntrance (string sceneName) {
    _entrance = _entrance.Create (sceneName);
  }
public string EntranceScene => _entrance?.SceneName;

EF Core résoudra qu’il doit utiliser le champ de stockage lors de la matérialisation de la propriété d’entrée. C’est pourquoi, comme Arthur Vickers expliqué dans la conversation très longue nous avons eu sur GitHub alors que j’ai apprendre à utiliser, si « il existe un champ de stockage et il n’existe aucun accesseur Set, EF utilise simplement le champ de stockage [car] existe rien d’autre n’il peut utiliser. » Par conséquent, elle fonctionne tout simplement.

Si ce nom de champ de stockage ne suit la convention, si, par exemple, vous avez le nommé _foo, vous avez besoin d’une configuration-métadonnées :

modelBuilder.Entity<Samurai> ()
  .Metadata
  .FindNavigation ("Entrance")
  .SetField("_foo");

Maintenant les mises à jour la base de données et les requêtes seront en mesure de définir cette relation. N’oubliez pas que si vous souhaitez utiliser un chargement hâtif, vous devez utiliser une chaîne pour becaise entrée qu'il ne peut pas être détecté par l’expression lambda ; par exemple :

var samurai = context.Samurais.Include("Entrance").FirstOrDefault();

Vous pouvez utiliser la syntaxe standard pour interagir avec les champs d’éléments tels que les filtres, la sauvegarde comme indiqué en bas de la page de documentation de la sauvegarde des champs à bit.ly/2wJeHQ7.

Charge désormais les objets de valeur

Les objets de valeur sont un concept essentiel pour DDD car ils vous permettent de définir des modèles de domaine en tant que types valeur. Un objet-valeur n’a pas sa propre identité et devient partie intégrante de l’entité qui l’utilise en tant que propriété. Envisagez le type de valeur de chaîne, qui est constitué d’une série de caractères. Étant donné que la modification d’un seul caractère modifie la signification du mot, les chaînes sont immuables. Pour modifier une chaîne, vous devez remplacer l’objet de la chaîne entière. DDD vous guide pour prendre en compte l’à l’aide de la valeur des objets n’importe où que vous avez identifié le type de relation. Pour plus d’informations sur les valeur ob-jets dans le cours Notions de base DDD indiqué précédemment.

EF toujours pris en charge que la possibilité d’inclure des objets de valeur via son type ComplexType. Vous pouvez définir un type sans clé et utiliser ce type en tant que propriété d’une entité. Qui a été suffisant pour le déclencheur EF à reconnaître en tant qu’un type complexe et mappez ses propriétés dans la table à laquelle l’entité est mappée. Le type pour ont également des fonctionnalités de re-quired d’un objet de valeur, telles que de garantir que le type est immuable et d’évaluation de chaque propriété pour déterminer l’égalité et en remplaçant le code de hachage peut ensuite être étendue. Fréquence à laquelle dériver une classe Mes types de classe de base de Jimmy Bogard ValueObject rapidement adopter ces attributs.

Nom d’une personne est un type qui est couramment utilisé comme un objet de valeur. Vous pouvez vous assurer que lorsqu’un utilisateur souhaite avoir le nom d’une personne dans une entité, ils suivent toujours un ensemble de règles communes. Figure 1 montre une classe PersonName simple qui a les propriétés de la première et la dernière, tous deux entièrement encapsulé, ainsi que d’une propriété pour retourner un nom complet. La classe est conçue pour garantir que les deux parties du nom sont toujours fournis.

Figure 1 l’objet de valeur de PersonName

public class PersonName : ValueObject<PersonName> {
  public static PersonName Create (string first, string last) {
    return new PersonName (first, last);
  }
  private PersonName () { } 
  private PersonName (string first, string last) {
    First = first;
    Last = last;
  }
  public string First { get; private set; }
  public string Last { get; private set; }
  public string FullName => First + " " + Last;
}

Je peux utiliser PersonName en tant que propriété dans d’autres types et continuer à développer une logique supplémentaire dans la classe PersonName. L’avantage de l’objet de valeur sur le type de relation ici est que je n’ai pas gérer la relation quand je suis de codage. Il s’agit de standard une programmation orientée objet. Il s’agit simplement d’une autre propriété. Dans la classe Samurai, j’ai ajouter une nouvelle propriété de ce type, définie comme privée sa méthode setter et fourni une autre méthode nommée d’identification à utiliser au lieu de l’accesseur Set :

 

public PersonName SecretIdentity{get;private set;}
public void Identify (string first, string last) {
  SecretIdentity = PersonName.Create (first, last);
}

Jusqu'à ce que EF Core 2.0, il a été aucune fonctionnalité similaire à ComplexTypes, afin de n’a pas pu utiliser facilement les objets de valeur sans ajouter effectue une opération dans un modèle de données distinct. Plutôt que simplement réimplémenter le ComplexType dans EF Core, l’équipe EF créé un concept appelé entités appartenant, qui s’appuie sur une autre fonctionnalité EF Core, propriétés de l’ombre. À présent, les entités appartenant à sont reconnues comme des propriétés supplémentaires des types que propre les et EF Core comprend comment ils résolvent dans le schéma de base de données et comment créer des requêtes et des mises à jour qui respectent ces données.

Convention de noyaux 2.0 EF ne détecte pas automatiquement que cette nouvelle propriété SecretIdentity est un type à incorporer dans les données persistantes. Vous devez explicitement pour demander le DbContext que la propriété Samurai.SecretIdentity est une entité détenue dans DbContext.OnModelCreating à l’aide de la méthode OwnsOne :

protected override void OnModelCreating (ModelBuilder modelBuilder) {
  modelBuilder.Entity<Samurai>().OwnsOne(s => s.SecretIdentity);
}

Cette opération force les propriétés de PersonName à résoudre en tant que propriétés de Samurai. Bien que votre code sera utilisent le type Samurai.SecretIdentity et parcourir que pour le premier et le derniers propriétés, ces deux propriétés seront résout en tant que colonnes dans la table de base de données Samurais. Convention de noyaux EF sera nommez-les avec le nom de la propriété dans Samurai (SecretIdentity) et le nom de la propriété d’entité détenue, comme indiqué dans Figure 2.

Le schéma de la Table Samurais, y compris les propriétés de la valeur
Figure 2 le schéma de la Table Samurais, y compris les propriétés de la valeur

Maintenant, je peux identifier le nom de secret principal d’un Samurai et l’enregistrer avec un code similaire à celui-ci :

using (var context = new SamuraiContext()) {
  var samurai = new Samurai { Name = "HubbieSan" 
  samurai.Identify ("Late", "Todinner");
  context.Samurais.Add (samurai);
  context.SaveChanges ();
}

Dans le magasin de données « Au plus tard » obtient conservé dans les champs de SecretIdentity_First « Todinner » dans le champ SecretIdentity_Last.

Puis je peux simplement interroger une Samurai :

var samurai=context.Samurais .FirstOrDefaultAsync (s => s.Name == "HubbieSan")

EF Core s’assurer que propriété de SecretIdentity de la Samurai résultant est remplie et vous afficherez ensuite l’identité en demandant :

samurai.SecretIdentity.FullName

EF Core nécessite que les propriétés qui sont des entités appartenant sont remplies. Dans notre exemple, comment puis-je conçu le type PersonName pour prendre en charge qui s’affiche.

Classes simples de leçons Simple

Ce que j’ai illustré que vous ici sont des classes simples qui tirent parti de certains des principaux concepts d’une implémentation DDD de la façon minimale qui vous permet de voir comment EF Core répond à ces constructions. Vous avez vu que EF Core 2.0 est en mesure de comprendre les relations unidirectionnel. Il peut rendre persistantes des données à partir des entités où les propriétés scalaires, de navigation et de collection sont entièrement encapsulées. Aussi, il vous permet d’utiliser des objets de valeur dans votre modèle de domaine et est en mesure de conserver les, également.

Pour cet article, j’ai conservé simples de classes et le manque de logique supplémentaire qui contraint les entités et les objets de valeur à l’aide de modèles de DDD les plus correctement. Cette simplicité est répercutée dans l’exemple à télécharger, qui est également sur GitHub à l’adresse bit.ly/2tDRXwi. Il vous trouverez la version simple et une branche avancée où j’ai renforcées de ce modèle de domaine et appliquer certaines pratiques DDD supplémentaires à la racine d’agrégation (Samurai), de son entité connexe (entrée) et de l’objet de valeur (PersonName) afin de voir comment EF Core 2.0 gère une expression plus réaliste d’un agrégat DDD. Dans une colonne à venir, je vais étudier les modèles avancés appliquées dans cette branche.

Gardez à l’esprit que j’utilise une version de EF Core 2.0 peu de temps avant sa version finale. Alors que la plupart des comportements que j’ai présentés est pleine, il existe encore la possibilité de modifications mineures avant la publication 2.0.0.


Julie Lerman est directeur régional Microsoft, Microsoft MVP, responsable d’équipe de logiciel et consultant qui réside dans le creux du Vermont. Vous trouverez sa présentation sur l’accès aux données et d’autres rubriques dans les groupes d’utilisateurs et des conférences dans le monde entier. Blogs she à adresse thedatafarm.com/blog et est l’auteur de « Programming Entity Framework », ainsi que d’un Code First et une édition DbContext, à partir d’o ' Reilly Media. Suivre sur Twitter : @julielerman et voir son cours Pluralsight à juliel.me/PS-vidéos.


Discussion sur cet article sur le forum MSDN Magazine