Remarque
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de modifier des répertoires.
Conseil / Astuce
Ce contenu est un extrait du livre électronique 'Architecture des microservices .NET pour les applications .NET conteneurisées', disponible sur .NET Docs ou en tant que PDF téléchargeable gratuitement, lisible hors ligne.
Lorsque vous utilisez des bases de données relationnelles telles que SQL Server, Oracle ou PostgreSQL, une approche recommandée consiste à implémenter la couche de persistance basée sur Entity Framework (EF). EF prend en charge LINQ et fournit des objets fortement typés pour votre modèle, ainsi qu’une persistance simplifiée dans votre base de données.
Entity Framework a un historique long dans le cadre du .NET Framework. Lorsque vous utilisez .NET, vous devez également utiliser Entity Framework Core, qui s’exécute sur Windows ou Linux de la même façon que .NET. EF Core est une réécriture complète d’Entity Framework implémentée avec une empreinte beaucoup plus petite et des améliorations importantes des performances.
Présentation d’Entity Framework Core
Entity Framework (EF) Core est une version légère, extensible et multiplateforme de la technologie d’accès aux données Entity Framework populaire. Il a été introduit avec .NET Core à la mi-2016.
Étant donné qu’une introduction à EF Core est déjà disponible dans la documentation Microsoft, nous fournissons simplement des liens vers ces informations.
Ressources supplémentaires
Entity Framework Core
https://learn.microsoft.com/ef/core/Bien démarrer avec ASP.NET Core et Entity Framework Core à l’aide de Visual Studio
https://learn.microsoft.com/aspnet/core/data/ef-mvc/DbContext, classe
https://learn.microsoft.com/dotnet/api/microsoft.entityframeworkcore.dbcontextComparer EF Core & EF6.x
https://learn.microsoft.com/ef/efcore-and-ef6/index
Infrastructure dans Entity Framework Core du point de vue DDD
D’un point de vue DDD, une fonctionnalité importante d’EF est la possibilité d’utiliser les entités de domaine OCT, également désignées dans la terminologie EF sous le nom d’entités Code First OCT. Si vous utilisez des entités de domaine OCT, vos classes de modèle de domaine ignorent la persistance, selon les principes d’ignorance de la persistance et d’ignorance de l’infrastructure.
Par modèle DDD, vous devez encapsuler le comportement et les règles de domaine au sein de la classe d’entité elle-même, afin qu’elle puisse contrôler les invariants, les validations et les règles lors de l’accès à n’importe quelle collection. Par conséquent, il n’est pas recommandé dans DDD d’autoriser l’accès public aux collections d’entités enfants ou d’objets de valeur. Au lieu de cela, vous souhaitez exposer des méthodes qui contrôlent comment et quand vos champs et collections de propriétés peuvent être mis à jour, et quel comportement et actions doivent se produire quand cela se produit.
Depuis EF Core 1.1, pour répondre à ces exigences DDD, vous pouvez avoir des champs simples dans vos entités au lieu de propriétés publiques. Si vous ne souhaitez pas qu’un champ d’entité soit accessible en externe, vous pouvez simplement créer l’attribut ou le champ au lieu d’une propriété. Vous pouvez également utiliser des méthodes setter de propriétés privées.
De la même façon, vous disposez désormais d'un accès en lecture seule aux collections en utilisant une propriété publique typée comme IReadOnlyCollection<T>, qui est soutenue par un membre de champ privé pour la collection (comme un List<T>) dans votre entité qui s’appuie sur EF pour la persistance. Les versions précédentes d’Entity Framework nécessitaient des propriétés de collection pour prendre en charge ICollection<T>, ce qui signifiait que tout développeur utilisant la classe d’entité parente pouvait ajouter ou supprimer des éléments via ses propriétés de collection. Cette possibilité serait contraire aux modèles recommandés dans DDD.
Vous pouvez utiliser une collection privée lors de l’exposition d’un objet en lecture seule IReadOnlyCollection<T> , comme illustré dans l’exemple de code suivant :
public class Order : Entity
{
// Using private fields, allowed since EF Core 1.1
private DateTime _orderDate;
// Other fields ...
private readonly List<OrderItem> _orderItems;
public IReadOnlyCollection<OrderItem> OrderItems => _orderItems;
protected Order() { }
public Order(int buyerId, int paymentMethodId, Address address)
{
// Initializations ...
}
public void AddOrderItem(int productId, string productName,
decimal unitPrice, decimal discount,
string pictureUrl, int units = 1)
{
// Validation logic...
var orderItem = new OrderItem(productId, productName,
unitPrice, discount,
pictureUrl, units);
_orderItems.Add(orderItem);
}
}
La propriété OrderItems est accessible uniquement en lecture seule à l’aide de IReadOnlyCollection<OrderItem>. Ce type est en lecture seule. Il est donc protégé contre les mises à jour externes régulières.
EF Core permet de mapper le modèle de domaine à la base de données physique sans « contaminer » le modèle de domaine. Il s’agit d’un code POCO .NET pur, car l’action de mappage est implémentée dans la couche de persistance. Dans cette action de mappage, vous devez configurer le mappage de champs à base de données. Dans l'exemple suivant de la méthode OnModelCreating de OrderingContext et de la classe OrderEntityTypeConfiguration, l'appel à SetPropertyAccessMode indique à EF Core d'accéder à la propriété OrderItems par son champ.
// At OrderingContext.cs from eShopOnContainers
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// ...
modelBuilder.ApplyConfiguration(new OrderEntityTypeConfiguration());
// Other entities' configuration ...
}
// At OrderEntityTypeConfiguration.cs from eShopOnContainers
class OrderEntityTypeConfiguration : IEntityTypeConfiguration<Order>
{
public void Configure(EntityTypeBuilder<Order> orderConfiguration)
{
orderConfiguration.ToTable("orders", OrderingContext.DEFAULT_SCHEMA);
// Other configuration
var navigation =
orderConfiguration.Metadata.FindNavigation(nameof(Order.OrderItems));
//EF access the OrderItem collection property through its backing field
navigation.SetPropertyAccessMode(PropertyAccessMode.Field);
// Other configuration
}
}
Lorsque vous utilisez des champs plutôt que des propriétés, l’entité OrderItem est conservée comme si elle avait une List<OrderItem> propriété. Toutefois, il expose un seul accesseur, la AddOrderItem méthode, pour ajouter de nouveaux éléments à l’ordre. Par conséquent, le comportement et les données sont liés et seront cohérents dans tout code d’application qui utilise le modèle de domaine.
Implémenter des référentiels personnalisés avec Entity Framework Core
Au niveau de l’implémentation, un référentiel est simplement une classe avec du code de persistance des données coordonné par une unité de travail (DBContext dans EF Core) lors de l’exécution de mises à jour, comme indiqué dans la classe suivante :
// using directives...
namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Repositories
{
public class BuyerRepository : IBuyerRepository
{
private readonly OrderingContext _context;
public IUnitOfWork UnitOfWork
{
get
{
return _context;
}
}
public BuyerRepository(OrderingContext context)
{
_context = context ?? throw new ArgumentNullException(nameof(context));
}
public Buyer Add(Buyer buyer)
{
return _context.Buyers.Add(buyer).Entity;
}
public async Task<Buyer> FindAsync(string buyerIdentityGuid)
{
var buyer = await _context.Buyers
.Include(b => b.Payments)
.Where(b => b.FullName == buyerIdentityGuid)
.SingleOrDefaultAsync();
return buyer;
}
}
}
L’interface IBuyerRepository provient de la couche de modèle de domaine comme contrat. Toutefois, l’implémentation du référentiel est effectuée au niveau de la persistance et de la couche d’infrastructure.
DBContext EF passe par le constructeur via une injection de dépendances. Il est partagé entre plusieurs référentiels dans la même étendue de requête HTTP, grâce à sa durée de vie par défaut (ServiceLifetime.Scoped) dans le conteneur IoC (qui peut également être défini explicitement avec services.AddDbContext<>).
Méthodes à implémenter dans un référentiel (mises à jour ou transactions par rapport aux requêtes)
Dans chaque classe de référentiel, vous devez placer les méthodes de persistance qui mettent à jour l’état des entités contenues par son agrégat associé. N’oubliez pas qu’il existe une relation un-à-un entre un agrégat et son référentiel associé. Considérez qu’un objet d’entité racine d’agrégation peut avoir des entités enfants incorporées dans son graphe EF. Par exemple, un acheteur peut avoir plusieurs modes de paiement en tant qu’entités enfants associées.
Étant donné que l’approche du microservice de commande dans eShopOnContainers est également basée sur CQS/CQRS, la plupart des requêtes ne sont pas implémentées dans des référentiels personnalisés. Les développeurs ont la liberté de créer les requêtes et les jointures dont ils ont besoin pour la couche de présentation sans les restrictions imposées par les agrégats, les référentiels personnalisés par agrégat et DDD en général. La plupart des référentiels personnalisés suggérés par ce guide ont plusieurs méthodes de mise à jour ou transactionnelles, mais uniquement les méthodes de requête nécessaires pour obtenir des données à mettre à jour. Par exemple, le référentiel BuyerRepository implémente une méthode FindAsync, car l’application doit savoir si un acheteur particulier existe avant de créer un acheteur lié à la commande.
Toutefois, les méthodes de requête réelles permettant d’obtenir des données à envoyer à la couche de présentation ou aux applications clientes sont implémentées, comme indiqué, dans les requêtes CQRS basées sur des requêtes flexibles à l’aide de Dapper.
Utilisation d’un référentiel personnalisé par rapport à l’utilisation directe d’EF DbContext
La classe Entity Framework DbContext est basée sur les modèles Unit of Work and Repository et peut être utilisé directement à partir de votre code, comme à partir d’un contrôleur MVC ASP.NET Core. Les modèles d’unité de travail et de référentiel entraînent le code le plus simple, comme dans le microservice du catalogue CRUD dans eShopOnContainers. Dans les cas où vous souhaitez que le code le plus simple soit possible, vous pouvez utiliser directement la classe DbContext, comme le font de nombreux développeurs.
Toutefois, l’implémentation de référentiels personnalisés offre plusieurs avantages lors de l’implémentation de microservices ou d’applications plus complexes. Les modèles d’unité de travail et de référentiel sont destinés à encapsuler la couche de persistance de l’infrastructure afin qu’elle soit découplée des couches application et modèle de domaine. L’implémentation de ces modèles peut faciliter l’utilisation de référentiels fictifs simulant l’accès à la base de données.
Dans la figure 7-18, vous pouvez voir les différences entre ne pas utiliser de référentiels (utilisation directe d'EF DbContext) et utiliser des référentiels, ce qui facilite la simulation de ceux-ci.

Figure 7-18. Utilisation de référentiels personnalisés par rapport à un DbContext simple
La figure 7-18 montre que l’utilisation d’un référentiel personnalisé ajoute une couche d’abstraction qui peut être utilisée pour faciliter les tests en moquant le référentiel. Il existe plusieurs alternatives lors de la simulation. Vous pourriez simuler simplement des dépôts ou vous pourriez simuler une unité entière de travail. Généralement, la simulation des dépôts uniquement est suffisante, et la tâche complexe d’abstraction et de simulation de l’intégralité d’une unité de travail n’est pas nécessaire.
Plus tard, lorsque nous nous concentrons sur la couche Application, vous verrez comment l’injection de dépendances fonctionne dans ASP.NET Core et comment elle est implémentée lors de l’utilisation de référentiels.
En bref, les référentiels personnalisés vous permettent de tester plus facilement le code avec des tests unitaires qui ne sont pas affectés par l’état du niveau données. Si vous exécutez des tests qui accèdent également à la base de données réelle via Entity Framework, ils ne sont pas des tests unitaires, mais des tests d’intégration, qui sont beaucoup plus lents.
Si vous utilisiez dbContext directement, vous devrez le simuler ou exécuter des tests unitaires à l’aide d’un serveur SQL Server en mémoire avec des données prévisibles pour les tests unitaires. Mais la simulation de DbContext ou le contrôle des données fictives nécessite plus de travail que la simulation au niveau du référentiel. Bien sûr, vous pouvez toujours tester les contrôleurs MVC.
Durée de vie de l’instance EF DbContext et IUnitOfWork dans votre conteneur IoC
L’objet DbContext (exposé en tant qu’objet IUnitOfWork ) doit être partagé entre plusieurs référentiels dans la même étendue de requête HTTP. Par exemple, cela est vrai lorsque l’opération en cours d’exécution doit traiter plusieurs agrégats, ou simplement parce que vous utilisez plusieurs instances de référentiel. Il est également important de mentionner que l’interface IUnitOfWork fait partie de votre couche de domaine, et non d’un type EF Core.
Pour ce faire, l’instance de l’objet DbContext doit avoir sa durée de vie de service définie sur ServiceLifetime.Scoped. Il s'agit de la durée de vie par défaut lors de l'enregistrement d'un DbContext avec builder.Services.AddDbContext dans votre conteneur IoC à partir du fichier Program.cs dans votre projet d'API Web ASP.NET Core. Le code suivant illustre cela.
// Add framework services.
builder.Services.AddMvc(options =>
{
options.Filters.Add(typeof(HttpGlobalExceptionFilter));
}).AddControllersAsServices();
builder.Services.AddEntityFrameworkSqlServer()
.AddDbContext<OrderingContext>(options =>
{
options.UseSqlServer(Configuration["ConnectionString"],
sqlOptions => sqlOptions.MigrationsAssembly(typeof(Startup).GetTypeInfo().
Assembly.GetName().Name));
},
ServiceLifetime.Scoped // Note that Scoped is the default choice
// in AddDbContext. It is shown here only for
// pedagogic purposes.
);
Le mode d’instanciation DbContext ne doit pas être configuré en tant que ServiceLifetime.Transient ou ServiceLifetime.Singleton.
Durée de vie de l’instance de référentiel dans votre conteneur IoC
De la même façon, la durée de vie du dépôt doit généralement être définie comme délimitée (InstancePerLifetimeScope dans Autofac). Il peut également être temporaire (InstancePerDependency dans Autofac), mais votre service sera plus efficace en ce qui concerne la mémoire lors de l’utilisation de la durée de vie limitée.
// Registering a Repository in Autofac IoC container
builder.RegisterType<OrderRepository>()
.As<IOrderRepository>()
.InstancePerLifetimeScope();
L’utilisation de la durée de vie singleton pour le dépôt peut vous causer des problèmes d’accès concurrentiel graves quand votre DbContext a une durée de vie délimitée (InstancePerLifetimeScope), durée de vie par défaut d’un DBContext. Tant que vos durées de vie de service pour vos référentiels et votre DbContext sont toutes les deux délimitées, vous éviterez ces problèmes.
Ressources supplémentaires
Implémentation du référentiel et des modèles d’unité de travail dans une application MVC ASP.NET
https://www.asp.net/mvc/overview/older-versions/getting-started-with-ef-5-using-mvc-4/implementing-the-repository-and-unit-of-work-patterns-in-an-asp-net-mvc-applicationJonathan Allen. Stratégies d’implémentation pour le modèle de référentiel avec Entity Framework, Dapper et Chain
https://www.infoq.com/articles/repository-implementation-strategiesCesar de la Torre. Comparaison des durées de vie des services de conteneur IoC d'ASP.NET Core avec les portées d'instance du conteneur IoC Autofac
https://devblogs.microsoft.com/cesardelatorre/comparing-asp-net-core-ioc-service-life-times-and-autofac-ioc-instance-scopes/
Mappage de table
Le mappage de table identifie les données de table à interroger et à enregistrer dans la base de données. Auparavant, vous avez vu comment les entités de domaine (par exemple, un domaine de produit ou de commande) peuvent être utilisées pour générer un schéma de base de données associé. EF est fortement conçu autour du concept de conventions. Les conventions répondent à des questions telles que « Quel est le nom d’une table ? » ou « Quelle propriété est la clé primaire ? » Les conventions sont généralement basées sur des noms conventionnels. Par exemple, il est courant que la clé primaire soit une propriété qui se termine par Id.
Par convention, chaque entité sera configurée pour être associée à une table portant le même nom que la propriété DbSet<TEntity> qui expose l’entité dans le contexte dérivé. Si aucune valeur n’est DbSet<TEntity> fournie pour l’entité donnée, le nom de la classe est utilisé.
Annotations de données et API Fluent
Il existe de nombreuses conventions EF Core supplémentaires, et la plupart d’entre elles peuvent être modifiées à l’aide d’annotations de données ou de l’API Fluent, implémentées dans la méthode OnModelCreating.
Les annotations de données doivent être utilisées sur les classes de modèle d’entité elles-mêmes, ce qui est un moyen plus intrusif d’un point de vue DDD. Cela est dû au fait que vous contaminez votre modèle avec des annotations de données liées à la base de données d’infrastructure. En revanche, l’API Fluent est un moyen pratique de modifier la plupart des conventions et mappages au sein de votre couche d’infrastructure de persistance des données. Par conséquent, le modèle d’entité sera propre et découplé de l’infrastructure de persistance.
API Fluent et méthode OnModelCreating
Comme mentionné, pour modifier les conventions et les mappages, vous pouvez utiliser la méthode OnModelCreating dans la classe DbContext.
Le microservice de commande dans eShopOnContainers implémente le mappage et la configuration explicite, si nécessaire, comme indiqué dans le code suivant.
// At OrderingContext.cs from eShopOnContainers
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// ...
modelBuilder.ApplyConfiguration(new OrderEntityTypeConfiguration());
// Other entities' configuration ...
}
// At OrderEntityTypeConfiguration.cs from eShopOnContainers
class OrderEntityTypeConfiguration : IEntityTypeConfiguration<Order>
{
public void Configure(EntityTypeBuilder<Order> orderConfiguration)
{
orderConfiguration.ToTable("orders", OrderingContext.DEFAULT_SCHEMA);
orderConfiguration.HasKey(o => o.Id);
orderConfiguration.Ignore(b => b.DomainEvents);
orderConfiguration.Property(o => o.Id)
.UseHiLo("orderseq", OrderingContext.DEFAULT_SCHEMA);
//Address value object persisted as owned entity type supported since EF Core 2.0
orderConfiguration
.OwnsOne(o => o.Address, a =>
{
a.WithOwner();
});
orderConfiguration
.Property<int?>("_buyerId")
.UsePropertyAccessMode(PropertyAccessMode.Field)
.HasColumnName("BuyerId")
.IsRequired(false);
orderConfiguration
.Property<DateTime>("_orderDate")
.UsePropertyAccessMode(PropertyAccessMode.Field)
.HasColumnName("OrderDate")
.IsRequired();
orderConfiguration
.Property<int>("_orderStatusId")
.UsePropertyAccessMode(PropertyAccessMode.Field)
.HasColumnName("OrderStatusId")
.IsRequired();
orderConfiguration
.Property<int?>("_paymentMethodId")
.UsePropertyAccessMode(PropertyAccessMode.Field)
.HasColumnName("PaymentMethodId")
.IsRequired(false);
orderConfiguration.Property<string>("Description").IsRequired(false);
var navigation = orderConfiguration.Metadata.FindNavigation(nameof(Order.OrderItems));
// DDD Patterns comment:
//Set as field (New since EF 1.1) to access the OrderItem collection property through its field
navigation.SetPropertyAccessMode(PropertyAccessMode.Field);
orderConfiguration.HasOne<PaymentMethod>()
.WithMany()
.HasForeignKey("_paymentMethodId")
.IsRequired(false)
.OnDelete(DeleteBehavior.Restrict);
orderConfiguration.HasOne<Buyer>()
.WithMany()
.IsRequired(false)
.HasForeignKey("_buyerId");
orderConfiguration.HasOne(o => o.OrderStatus)
.WithMany()
.HasForeignKey("_orderStatusId");
}
}
Vous pouvez définir tous les mappages d’API Fluent au sein de la même OnModelCreating méthode, mais il est recommandé de partitionner ce code et d’avoir plusieurs classes de configuration, une par entité, comme illustré dans l’exemple. En particulier pour les grands modèles, il est recommandé d’avoir des classes de configuration distinctes pour la configuration de différents types d’entités.
Le code de l’exemple montre quelques déclarations et mappages explicites. Toutefois, les conventions EF Core effectuent automatiquement la plupart de ces mappages. Par conséquent, le code réel dont vous avez besoin dans votre cas peut être plus petit.
Algorithme Hi/Lo dans EF Core
Un aspect intéressant du code dans l’exemple précédent est qu’il utilise l’algorithme Hi/Lo comme stratégie de génération de clés.
L’algorithme Hi/Lo est utile lorsque vous avez besoin de clés uniques avant de valider les modifications. En résumé, l’algorithme Hi-Lo affecte des identificateurs uniques aux lignes de la table, tout en n’étant pas en fonction du stockage de la ligne dans la base de données immédiatement. Cela vous permet de commencer à utiliser immédiatement les identificateurs, comme cela se produit avec des ID de base de données séquentielles standard.
L’algorithme Hi/Lo décrit un mécanisme permettant d’obtenir un lot d’ID uniques à partir d’une séquence de base de données associée. Ces ID sont sûrs à utiliser, car la base de données garantit l’unicité, de sorte qu’il n’y aura pas de collisions entre les utilisateurs. Cet algorithme est intéressant pour ces raisons :
Il n’interrompt pas le modèle Unité de travail.
Il obtient des ID séquentiels par lots pour réduire les allers-retours vers la base de données.
Il génère un identificateur lisible par l’homme, contrairement aux techniques qui utilisent des GUID.
EF Core prend en charge HiLo avec la UseHiLo méthode, comme indiqué dans l’exemple précédent.
Mapper des champs au lieu de propriétés
Avec cette fonctionnalité, disponible depuis EF Core 1.1, vous pouvez mapper directement des colonnes aux champs. Il est possible de ne pas utiliser de propriétés dans la classe d’entité et simplement de mapper des colonnes d’une table à des champs. Cette fonctionnalité est couramment utilisée dans le cadre des champs privés pour n’importe quel état interne qui ne doit pas être accessible depuis l’extérieur de l’entité.
Pour ce faire, vous pouvez utiliser des champs uniques ou des collections, comme un List<> champ. Ce point a été mentionné précédemment lorsque nous avons abordé la modélisation des classes de modèle de domaine, mais ici, vous pouvez voir comment ce mappage est effectué avec la PropertyAccessMode.Field configuration mise en surbrillance dans le code précédent.
Utiliser les propriétés d'ombre dans EF Core, cachées au niveau de l'infrastructure
Les propriétés d’ombre dans EF Core sont des propriétés qui n’existent pas dans votre modèle de classe d’entité. Les valeurs et les états de ces propriétés sont conservés uniquement dans la classe ChangeTracker au niveau de l’infrastructure.
Implémenter le modèle de spécification de requête
Comme indiqué précédemment dans la section conception, le modèle spécification de requête est un modèle de conception Domain-Driven conçu comme emplacement où vous pouvez placer la définition d’une requête avec une logique de tri et de pagination facultative.
Le modèle spécification de requête définit une requête dans un objet. Par exemple, pour encapsuler une requête paginée qui recherche certains produits, vous pouvez créer une spécification PagedProduct qui accepte les paramètres d’entrée nécessaires (pageNumber, pageSize, filter, etc.). Ensuite, dans n’importe quelle méthode de référentiel (généralement une surcharge List() elle accepterait une IQuerySpecification et exécuterait la requête attendue en fonction de cette spécification.
Un exemple d’interface de spécification générique est le code suivant, qui est similaire au code utilisé dans l’application de référence eShopOnWeb .
// GENERIC SPECIFICATION INTERFACE
// https://github.com/dotnet-architecture/eShopOnWeb
public interface ISpecification<T>
{
Expression<Func<T, bool>> Criteria { get; }
List<Expression<Func<T, object>>> Includes { get; }
List<string> IncludeStrings { get; }
}
Ensuite, l’implémentation d’une classe de base de spécification générique est la suivante.
// GENERIC SPECIFICATION IMPLEMENTATION (BASE CLASS)
// https://github.com/dotnet-architecture/eShopOnWeb
public abstract class BaseSpecification<T> : ISpecification<T>
{
public BaseSpecification(Expression<Func<T, bool>> criteria)
{
Criteria = criteria;
}
public Expression<Func<T, bool>> Criteria { get; }
public List<Expression<Func<T, object>>> Includes { get; } =
new List<Expression<Func<T, object>>>();
public List<string> IncludeStrings { get; } = new List<string>();
protected virtual void AddInclude(Expression<Func<T, object>> includeExpression)
{
Includes.Add(includeExpression);
}
// string-based includes allow for including children of children
// for example, Basket.Items.Product
protected virtual void AddInclude(string includeString)
{
IncludeStrings.Add(includeString);
}
}
La spécification suivante charge une entité de panier unique en fonction de l’ID du panier ou de l’ID de l’acheteur auquel appartient le panier. Elle effectue un chargement immédiat de la collection Items du panier.
// SAMPLE QUERY SPECIFICATION IMPLEMENTATION
public class BasketWithItemsSpecification : BaseSpecification<Basket>
{
public BasketWithItemsSpecification(int basketId)
: base(b => b.Id == basketId)
{
AddInclude(b => b.Items);
}
public BasketWithItemsSpecification(string buyerId)
: base(b => b.BuyerId == buyerId)
{
AddInclude(b => b.Items);
}
}
Enfin, vous pouvez voir ci-dessous comment un référentiel EF générique peut utiliser une telle spécification pour filtrer et charger avec impatience les données liées à un type d’entité T donné.
// GENERIC EF REPOSITORY WITH SPECIFICATION
// https://github.com/dotnet-architecture/eShopOnWeb
public IEnumerable<T> List(ISpecification<T> spec)
{
// fetch a Queryable that includes all expression-based includes
var queryableResultWithIncludes = spec.Includes
.Aggregate(_dbContext.Set<T>().AsQueryable(),
(current, include) => current.Include(include));
// modify the IQueryable to include any string-based include statements
var secondaryResult = spec.IncludeStrings
.Aggregate(queryableResultWithIncludes,
(current, include) => current.Include(include));
// return the result of the query using the specification's criteria expression
return secondaryResult
.Where(spec.Criteria)
.AsEnumerable();
}
Outre la logique de filtrage encapsulante, la spécification peut spécifier la forme des données à retourner, y compris les propriétés à remplir.
Même si nous vous déconseillons de retourner IQueryable à partir d’un référentiel, il est parfaitement recommandé de les utiliser dans le référentiel pour créer un ensemble de résultats. Vous pouvez voir cette approche utilisée dans la méthode List ci-dessus, qui utilise des expressions intermédiaires IQueryable pour créer la liste des éléments inclus de la requête avant d’exécuter la requête avec les critères de la spécification sur la dernière ligne.
Découvrez comment le modèle de spécification est appliqué dans l’exemple eShopOnWeb.
Ressources supplémentaires
Mappage de tables
https://learn.microsoft.com/ef/core/modeling/relational/tablesUtiliser HiLo pour générer des clés avec Entity Framework Core
https://www.talkingdotnet.com/use-hilo-to-generate-keys-with-entity-framework-core/Champs de stockage
https://learn.microsoft.com/ef/core/modeling/backing-fieldSteve Smith. Collections encapsulées dans Entity Framework Core
https://ardalis.com/encapsulated-collections-in-entity-framework-corePropriétés de l’ombre
https://learn.microsoft.com/ef/core/modeling/shadow-propertiesModèle de spécification
https://deviq.com/specification-pattern/Package NuGet Ardalis.Specification Utilisé par eShopOnWeb. \ https://www.nuget.org/packages/Ardalis.Specification