Configuration en bloc du modèle

Lorsqu’un aspect doit être configuré de la même façon sur plusieurs types d’entités, les techniques suivantes permettent de réduire la duplication du code et de consolider la logique.

Consultez l’exemple de projet complet contenant les extraits de code présentés ci-dessous.

Configuration en bloc dans OnModelCreating

Chaque objet générateur retourné par ModelBuilder expose une propriété Model ou Metadata qui fournit un accès de bas niveau aux objets qui composent le modèle. En particulier, il existe des méthodes qui vous permettent d’itérer sur des objets spécifiques dans le modèle et d’appliquer une configuration commune à celles-ci.

Dans l’exemple suivant, le modèle contient un type de valeur personnalisé Currency:

public readonly struct Currency
{
    public Currency(decimal amount)
        => Amount = amount;

    public decimal Amount { get; }

    public override string ToString()
        => $"${Amount}";
}

Les propriétés de ce type ne sont pas découvertes par défaut, car le fournisseur EF actuel ne sait pas comment le mapper à un type de base de données. Cet extrait de code de OnModelCreating ajoute toutes les propriétés du type Currency et configure un convertisseur de valeurs en un type pris en charge - decimal:

foreach (var entityType in modelBuilder.Model.GetEntityTypes())
{
    foreach (var propertyInfo in entityType.ClrType.GetProperties())
    {
        if (propertyInfo.PropertyType == typeof(Currency))
        {
            entityType.AddProperty(propertyInfo)
                .SetValueConverter(typeof(CurrencyConverter));
        }
    }
}
public class CurrencyConverter : ValueConverter<Currency, decimal>
{
    public CurrencyConverter()
        : base(
            v => v.Amount,
            v => new Currency(v))
    {
    }
}

Inconvénients de l’API de métadonnées

  • Contrairement à API Fluent, chaque modification du modèle doit être effectuée explicitement. Par exemple, si certaines des propriétés Currency ont été configurées comme navigations par une convention, vous devez d’abord supprimer la navigation référençant la propriété CLR avant d’ajouter une propriété de type d’entité pour celle-ci. #9117 améliorera cela.
  • Les conventions s’exécutent après chaque modification. Si vous supprimez une navigation découverte par une convention, la convention s’exécute à nouveau et peut l’ajouter. Pour empêcher ce problème, vous devrez retarder les conventions jusqu’à ce que la propriété soit ajoutée en appelant DelayConventions() et en supprimant ultérieurement l’objet retourné ou pour marquer la propriété CLR comme ignorée à l’aide de AddIgnored.
  • Les types d’entités peuvent être ajoutés une fois cette itération effectuée et la configuration ne sera pas appliquée. Cela peut généralement être empêché en plaçant ce code à la fin de OnModelCreating, mais si vous avez deux ensembles de configurations interdépendants, il se peut qu’il n’y ait pas d’ordre qui leur permettra d’être appliqués de manière cohérente.

Configuration de pré-convention

EF Core permet à la configuration de mappage d’être spécifiée une fois pour un type CLR donné ; cette configuration est ensuite appliquée à toutes les propriétés de ce type dans le modèle à mesure qu’elles sont découvertes. Il s’agit de la « configuration du modèle de pré-convention », car elle configure les aspects du modèle avant que les conventions de génération de modèle soient autorisées à s’exécuter. Cette configuration est appliquée en substituant ConfigureConventions sur le type dérivé de DbContext.

Cet exemple montre comment configurer toutes les propriétés de type Currency avoir un convertisseur de valeur :

protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
    configurationBuilder
        .Properties<Currency>()
        .HaveConversion<CurrencyConverter>();
}

Cet exemple montre comment configurer certaines facettes sur toutes les propriétés de type string:

configurationBuilder
    .Properties<string>()
    .AreUnicode(false)
    .HaveMaxLength(1024);

Remarque

Le type spécifié dans un appel de ConfigureConventions peut être un type de base, une interface ou une définition de type générique. Toutes les configurations correspondantes sont appliquées dans l’ordre à partir du moins spécifique :

  1. Interface
  2. Type de base
  3. Définition de type générique
  4. Type de valeur n’acceptant pas Null
  5. Type exact

Important

La configuration de pré-convention équivaut à une configuration explicite appliquée dès qu’un objet correspondant est ajouté au modèle. Elle remplace toutes les conventions et annotations de données. Par exemple, avec la configuration ci-dessus, toutes les propriétés de clé étrangère de chaîne sont créées en tant que non-unicode avec MaxLength de 1024, même si cela ne correspond pas à la clé principale.

Ignorer les types

La configuration de pré-convention permet également d’ignorer un type et d’empêcher sa découverte par des conventions en tant que type d’entité ou en tant que propriété sur un type d’entité :

configurationBuilder
    .IgnoreAny(typeof(IList<>));

Mappage de type par défaut

En règle générale, EF est en mesure de traduire des requêtes avec des constantes d’un type qui n’est pas pris en charge par le fournisseur, tant que vous avez spécifié un convertisseur de valeur pour une propriété de ce type. Toutefois, dans les requêtes qui n’impliquent aucune propriété de ce type, il n’existe aucun moyen pour EF de trouver le convertisseur de valeur correct. Dans ce cas, il est possible d’appeler DefaultTypeMapping pour ajouter ou remplacer un mappage de type de fournisseur :

configurationBuilder
    .DefaultTypeMapping<Currency>()
    .HasConversion<CurrencyConverter>();

Limitations de la configuration de la pré-convention

  • De nombreux aspects ne peuvent pas être configurés avec cette approche. #6787 étend cette option à d’autres types.
  • Actuellement, la configuration est déterminée uniquement par le type CLR. #20418 autoriserait les prédicats personnalisés.
  • Cette configuration est effectuée avant la création d’un modèle. S’il existe des conflits qui se produisent lors de son application, la trace de la pile d’exceptions ne contient pas la méthode ConfigureConventions, de sorte qu’il peut être plus difficile de trouver la cause.

Conventions

Remarque

Les conventions de génération de modèles personnalisées ont été introduites dans EF Core 7.0.

Les conventions de génération de modèles EF Core sont des classes qui contiennent une logique déclenchée en fonction des modifications apportées au modèle au fur et à mesure qu’il est généré. Cela maintient le modèle à jour à mesure que la configuration explicite est effectuée, les attributs de mappage sont appliqués et d’autres conventions s’exécutent. Pour y participer, chaque convention implémente une ou plusieurs interfaces qui déterminent quand la méthode correspondante sera déclenchée. Par exemple, une convention qui implémente IEntityTypeAddedConvention est déclenchée chaque fois qu’un nouveau type d’entité est ajouté au modèle. De même, une convention qui implémente à la fois IForeignKeyAddedConvention et IKeyAddedConvention sera déclenchée chaque fois qu’une clé ou une clé étrangère est ajoutée au modèle.

Les conventions de création de modèles constituent un moyen puissant de contrôler la configuration du modèle, mais peuvent être complexes et difficiles à obtenir correctement. Dans de nombreux cas, la configuration du modèle pré-convention peut être utilisée à la place pour spécifier facilement la configuration commune pour les propriétés et les types.

Ajout d’une nouvelle convention

Exemple : Limiter la longueur des propriétés de discriminateur

La stratégie de mappage d’héritage table par hiérarchie nécessite une colonne de discrimination pour spécifier le type représenté dans une ligne donnée. Par défaut, EF utilise une colonne de chaîne non délimitée pour le discriminateur, ce qui garantit qu’elle fonctionnera pour toute longueur de discriminateur. Toutefois, la limitation de la longueur maximale des chaînes de discrimination peut rendre plus efficace le stockage et les requêtes. Créons une convention qui le fera.

Les conventions de génération de modèles EF Core sont déclenchées en fonction des modifications apportées au modèle au fur et à mesure qu’il est généré. Cela maintient le modèle à jour à mesure que la configuration explicite est effectuée, les attributs de mappage sont appliqués et d’autres conventions s’exécutent. Pour y participer, chaque convention implémente une ou plusieurs interfaces qui déterminent quand la convention sera déclenchée. Par exemple, une convention qui implémente IEntityTypeAddedConvention est déclenchée chaque fois qu’un nouveau type d’entité est ajouté au modèle. De même, une convention qui implémente à la fois IForeignKeyAddedConvention et IKeyAddedConvention sera déclenchée chaque fois qu’une clé ou une clé étrangère est ajoutée au modèle.

Connaître les interfaces à implémenter peut être difficile, car la configuration apportée au modèle à un moment donné peut être modifiée ou supprimée ultérieurement. Par exemple, une clé peut être créée par convention, mais ensuite remplacée ultérieurement lorsqu’une autre clé est configurée explicitement.

Faisons en sorte que cela soit un peu plus concret en effectuant une première tentative d’implémentation de la convention de longueur de discrimination :

public class DiscriminatorLengthConvention1 : IEntityTypeBaseTypeChangedConvention
{
    public void ProcessEntityTypeBaseTypeChanged(
        IConventionEntityTypeBuilder entityTypeBuilder,
        IConventionEntityType? newBaseType,
        IConventionEntityType? oldBaseType,
        IConventionContext<IConventionEntityType> context)
    {
        var discriminatorProperty = entityTypeBuilder.Metadata.FindDiscriminatorProperty();
        if (discriminatorProperty != null
            && discriminatorProperty.ClrType == typeof(string))
        {
            discriminatorProperty.Builder.HasMaxLength(24);
        }
    }
}

Cette convention implémente IEntityTypeBaseTypeChangedConvention, ce qui signifie qu’elle sera déclenchée chaque fois que la hiérarchie d’héritage mappée pour un type d’entité est modifiée. La convention recherche et configure ensuite la propriété de discrimination de chaîne pour la hiérarchie.

Cette convention est ensuite utilisée en appelant Add dans ConfigureConventions:

protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
    configurationBuilder.Conventions.Add(_ =>  new DiscriminatorLengthConvention1());
}

Remarque

Au lieu d’ajouter directement une instance de la convention, la méthode Add accepte une fabrique pour créer des instances de la convention. Cela permet à la convention d’utiliser des dépendances du fournisseur de services interne EF Core. Étant donné que cette convention n’a pas de dépendances, le paramètre du fournisseur de services est nommé _, ce qui indique qu’il n’est jamais utilisé.

La création du modèle et l’analyse du type d’entité Post montre que cela a fonctionné : la propriété de discrimination est désormais configurée avec une longueur maximale de 24 :

 Discriminator (no field, string) Shadow Required AfterSave:Throw MaxLength(24)

Mais que se passe-t-il si nous configurons maintenant explicitement une autre propriété de discriminateur ? Par exemple :

modelBuilder.Entity<Post>()
    .HasDiscriminator<string>("PostTypeDiscriminator")
    .HasValue<Post>("Post")
    .HasValue<FeaturedPost>("Featured");

En examinant la vue de débogage du modèle, nous constatons que la longueur du discriminateur n’est plus configurée.

 PostTypeDiscriminator (no field, string) Shadow Required AfterSave:Throw

Cela est dû au fait que la propriété de discrimination que nous avons configurée dans notre convention a été supprimée ultérieurement lorsque le discriminateur personnalisé a été ajouté. Nous pourrions tenter de résoudre ce problème en implémentant une autre interface sur notre convention pour réagir aux modifications du discriminateur, mais déterminer l’interface à implémenter n’est pas facile.

Heureusement, il y a une approche plus facile. Beaucoup de temps, il n’importe pas ce que le modèle ressemble pendant qu’il est généré, tant que le modèle final est correct. En outre, la configuration que nous voulons appliquer n’a souvent pas besoin de déclencher d’autres conventions pour réagir. Par conséquent, notre convention peut implémenter IModelFinalizingConvention. Conventions de finalisation du modèle s’exécuter une fois que toute autre génération de modèle est terminée, et ainsi avoir accès à l’état quasi final du modèle. Cela s’oppose à conventions interactives qui réagissent à chaque modification de modèle et assurez-vous que le modèle est à jour à n’importe quel point de l’exécution de la méthode OnModelCreating. Une convention de finalisation de modèle effectue généralement une itération sur l’ensemble du modèle configurant des éléments de modèle au fur et à mesure. Ainsi, dans ce cas, nous allons trouver chaque discriminateur dans le modèle et le configurer :

public class DiscriminatorLengthConvention2 : IModelFinalizingConvention
{
    public void ProcessModelFinalizing(IConventionModelBuilder modelBuilder, IConventionContext<IConventionModelBuilder> context)
    {
        foreach (var entityType in modelBuilder.Metadata.GetEntityTypes()
                     .Where(entityType => entityType.BaseType == null))
        {
            var discriminatorProperty = entityType.FindDiscriminatorProperty();
            if (discriminatorProperty != null
                && discriminatorProperty.ClrType == typeof(string))
            {
                discriminatorProperty.Builder.HasMaxLength(24);
            }
        }
    }
}

Après avoir généré le modèle avec cette nouvelle convention, nous constatons que la longueur du discriminateur est maintenant configurée correctement même si elle a été personnalisée :

PostTypeDiscriminator (no field, string) Shadow Required AfterSave:Throw MaxLength(24)

Nous pouvons aller plus loin et configurer la longueur maximale pour être la longueur de la valeur de discrimination la plus longue :

public class DiscriminatorLengthConvention3 : IModelFinalizingConvention
{
    public void ProcessModelFinalizing(IConventionModelBuilder modelBuilder, IConventionContext<IConventionModelBuilder> context)
    {
        foreach (var entityType in modelBuilder.Metadata.GetEntityTypes()
                     .Where(entityType => entityType.BaseType == null))
        {
            var discriminatorProperty = entityType.FindDiscriminatorProperty();
            if (discriminatorProperty != null
                && discriminatorProperty.ClrType == typeof(string))
            {
                var maxDiscriminatorValueLength =
                    entityType.GetDerivedTypesInclusive().Select(e => ((string)e.GetDiscriminatorValue()!).Length).Max();

                discriminatorProperty.Builder.HasMaxLength(maxDiscriminatorValueLength);
            }
        }
    }
}

À présent, la longueur maximale de la colonne de discrimination est 8, qui est la longueur de « Featured », la valeur de discriminateur la plus longue utilisée.

PostTypeDiscriminator (no field, string) Shadow Required AfterSave:Throw MaxLength(8)

Exemple : Longueur par défaut de toutes les propriétés de chaîne

Examinons un autre exemple dans lequel une convention de finalisation peut être utilisée : définition d’une longueur maximale par défaut pour toute propriété de chaîne. La convention ressemble assez à l’exemple précédent :

public class MaxStringLengthConvention : IModelFinalizingConvention
{
    public void ProcessModelFinalizing(IConventionModelBuilder modelBuilder, IConventionContext<IConventionModelBuilder> context)
    {
        foreach (var property in modelBuilder.Metadata.GetEntityTypes()
                     .SelectMany(
                         entityType => entityType.GetDeclaredProperties()
                             .Where(
                                 property => property.ClrType == typeof(string))))
        {
            property.Builder.HasMaxLength(512);
        }
    }
}

Cette convention est assez simple. Elle recherche chaque propriété de chaîne dans le modèle et définit sa longueur maximale sur 512. En examinant la vue de débogage sur les propriétés de Post, nous voyons que toutes les propriétés de chaîne ont maintenant une longueur maximale de 512.

EntityType: Post
  Properties:
    Id (int) Required PK AfterSave:Throw ValueGenerated.OnAdd
    AuthorId (no field, int?) Shadow FK Index
    BlogId (no field, int) Shadow Required FK Index
    Content (string) Required MaxLength(512)
    Discriminator (no field, string) Shadow Required AfterSave:Throw MaxLength(512)
    PublishedOn (DateTime) Required
    Title (string) Required MaxLength(512)

Remarque

La même chose peut être effectuée par la configuration de pré-convention, mais l’utilisation d’une convention permet de filtrer davantage les propriétés applicables et pour annotations de données pour remplacer la configuration.

Enfin, avant de quitter cet exemple, que se passe-t-il si nous utilisons les MaxStringLengthConvention et les DiscriminatorLengthConvention3 en même temps ? La réponse est qu’elle dépend de l’ordre dans lequel elles sont ajoutées, car les conventions de finalisation du modèle s’exécutent dans l’ordre dans lequel elles sont ajoutées. Par conséquent, si MaxStringLengthConvention est ajoutée en dernier, elle s’exécute en dernier, et elle définit la longueur maximale de la propriété de discriminateur sur 512. Par conséquent, dans ce cas, il est préférable d’ajouter DiscriminatorLengthConvention3 en dernier afin qu’elle puisse remplacer la longueur maximale par défaut pour les propriétés de juste discrimination, tout en laissant toutes les autres propriétés de chaîne comme 512.

Remplacement d’une convention existante

Parfois, plutôt que de supprimer complètement une convention existante, nous voulons le remplacer par une convention qui fait essentiellement la même chose, mais avec un comportement modifié. Cela est utile, car la convention existante implémentera déjà les interfaces qu’il doit déclencher de manière appropriée.

Exemple : Mappage de propriétés d’inscription

EF Core mappe toutes les propriétés en lecture-écriture publique par convention. Cela peut ne pas convenir à la façon dont vos types d’entités sont définis. Pour changer cela, nous pouvons remplacer le PropertyDiscoveryConvention par notre propre implémentation qui ne mappe aucune propriété, sauf si elle est explicitement mappée dans OnModelCreating ou marquée par un nouvel attribut appelé Persist:

[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
public sealed class PersistAttribute : Attribute
{
}

Voici la nouvelle convention :

public class AttributeBasedPropertyDiscoveryConvention : PropertyDiscoveryConvention
{
    public AttributeBasedPropertyDiscoveryConvention(ProviderConventionSetBuilderDependencies dependencies)
        : base(dependencies)
    {
    }

    public override void ProcessEntityTypeAdded(
        IConventionEntityTypeBuilder entityTypeBuilder,
        IConventionContext<IConventionEntityTypeBuilder> context)
        => Process(entityTypeBuilder);

    public override void ProcessEntityTypeBaseTypeChanged(
        IConventionEntityTypeBuilder entityTypeBuilder,
        IConventionEntityType? newBaseType,
        IConventionEntityType? oldBaseType,
        IConventionContext<IConventionEntityType> context)
    {
        if ((newBaseType == null
             || oldBaseType != null)
            && entityTypeBuilder.Metadata.BaseType == newBaseType)
        {
            Process(entityTypeBuilder);
        }
    }

    private void Process(IConventionEntityTypeBuilder entityTypeBuilder)
    {
        foreach (var memberInfo in GetRuntimeMembers())
        {
            if (Attribute.IsDefined(memberInfo, typeof(PersistAttribute), inherit: true))
            {
                entityTypeBuilder.Property(memberInfo);
            }
            else if (memberInfo is PropertyInfo propertyInfo
                     && Dependencies.TypeMappingSource.FindMapping(propertyInfo) != null)
            {
                entityTypeBuilder.Ignore(propertyInfo.Name);
            }
        }

        IEnumerable<MemberInfo> GetRuntimeMembers()
        {
            var clrType = entityTypeBuilder.Metadata.ClrType;

            foreach (var property in clrType.GetRuntimeProperties()
                         .Where(p => p.GetMethod != null && !p.GetMethod.IsStatic))
            {
                yield return property;
            }

            foreach (var property in clrType.GetRuntimeFields())
            {
                yield return property;
            }
        }
    }
}

Conseil

Lors du remplacement d’une convention intégrée, la nouvelle implémentation de convention doit hériter de la classe de convention existante. Notez que certaines conventions ont des implémentations relationnelles ou spécifiques au fournisseur, auquel cas la nouvelle implémentation de convention doit hériter de la classe de convention existante la plus spécifique pour le fournisseur de base de données en cours d’utilisation.

La convention est ensuite inscrite à l’aide de la méthode Replace dans ConfigureConventions:

protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
    configurationBuilder.Conventions.Replace<PropertyDiscoveryConvention>(
        serviceProvider => new AttributeBasedPropertyDiscoveryConvention(
            serviceProvider.GetRequiredService<ProviderConventionSetBuilderDependencies>()));
}

Conseil

Il s’agit d’un cas où la convention existante a des dépendances, représentées par l’objet de dépendance ProviderConventionSetBuilderDependencies. Ceux-ci sont obtenus auprès du fournisseur de services interne à l’aide de GetRequiredService et transmis au constructeur de convention.

Notez que cette convention permet aux champs d’être mappés (en plus des propriétés) tant qu’ils sont marqués avec [Persist]. Cela signifie que nous pouvons utiliser des champs privés comme clés masquées dans le modèle.

Par exemple, tenez compte des types d’entités suivants :

public class LaundryBasket
{
    [Persist]
    [Key]
    private readonly int _id;

    [Persist]
    public int TenantId { get; init; }

    public bool IsClean { get; set; }

    public List<Garment> Garments { get; } = new();
}

public class Garment
{
    public Garment(string name, string color)
    {
        Name = name;
        Color = color;
    }

    [Persist]
    [Key]
    private readonly int _id;

    [Persist]
    public int TenantId { get; init; }

    [Persist]
    public string Name { get; }

    [Persist]
    public string Color { get; }

    public bool IsClean { get; set; }

    public LaundryBasket? Basket { get; set; }
}

Le modèle créé à partir de ces types d’entités est :

Model:
  EntityType: Garment
    Properties:
      _id (_id, int) Required PK AfterSave:Throw ValueGenerated.OnAdd
      Basket_id (no field, int?) Shadow FK Index
      Color (string) Required
      Name (string) Required
      TenantId (int) Required
    Navigations:
      Basket (LaundryBasket) ToPrincipal LaundryBasket Inverse: Garments
    Keys:
      _id PK
    Foreign keys:
      Garment {'Basket_id'} -> LaundryBasket {'_id'} ToDependent: Garments ToPrincipal: Basket ClientSetNull
    Indexes:
      Basket_id
  EntityType: LaundryBasket
    Properties:
      _id (_id, int) Required PK AfterSave:Throw ValueGenerated.OnAdd
      TenantId (int) Required
    Navigations:
      Garments (List<Garment>) Collection ToDependent Garment Inverse: Basket
    Keys:
      _id PK

Normalement, IsClean aurait été mappé, mais comme elle n’est pas marquée avec [Persist], elle est désormais traitée comme une propriété non mappée.

Conseil

Cette convention n’a pas pu être implémentée en tant que convention de finalisation de modèle, car il existe des conventions de finalisation de modèle existantes qui doivent s’exécuter une fois que la propriété est mappée pour la configurer davantage.

Considérations relatives à l’implémentation des conventions

EF Core effectue le suivi de la façon dont chaque partie de la configuration a été effectuée. Il s’agit de l’énumération ConfigurationSource. Les différents types de configuration sont les suivants :

  • Explicit: l’élément de modèle a été configuré explicitement dans OnModelCreating
  • DataAnnotation: l’élément de modèle a été configuré à l’aide d’un attribut de mappage (alias annotation de données) sur le type CLR
  • Convention: l’élément de modèle a été configuré par une convention de création de modèle

Les conventions ne doivent jamais remplacer la configuration marquée comme DataAnnotation ou Explicit. Cela est obtenu à l’aide d’un générateur de conventions , par exemple, le IConventionPropertyBuilder, qui est obtenu à partir de la propriété Builder . Par exemple :

property.Builder.HasMaxLength(512);

L’appel de HasMaxLength sur le générateur de conventions ne définit que la longueur maximale si elle n’a pas déjà été configurée par un attribut de mappage ou dans OnModelCreating.

Les méthodes de générateur telles que celles-ci ont également un deuxième paramètre : fromDataAnnotation. Définissez cette valeur sur true si la convention effectue la configuration pour le compte d’un attribut de mappage. Par exemple :

property.Builder.HasMaxLength(512, fromDataAnnotation: true);

Cela définit la ConfigurationSource sur DataAnnotation, ce qui signifie que la valeur peut désormais être substituée par un mappage explicite sur OnModelCreating, mais pas par des conventions d’attribut non-mappage.

Si la configuration actuelle ne peut pas être remplacée, la méthode retourne null, cela doit être pris en compte si vous devez effectuer une configuration supplémentaire :

property.Builder.HasMaxLength(512)?.IsUnicode(false);

Notez que si la configuration Unicode ne peut pas être remplacée, la longueur maximale est toujours définie. Dans le cas où vous devez configurer les facettes uniquement lorsque les deux appels réussissent, vous pouvez vérifier cela de manière préemptive en appelant CanSetMaxLength et CanSetIsUnicode:

public class MaxStringLengthNonUnicodeConvention : IModelFinalizingConvention
{
    public void ProcessModelFinalizing(IConventionModelBuilder modelBuilder, IConventionContext<IConventionModelBuilder> context)
    {
        foreach (var property in modelBuilder.Metadata.GetEntityTypes()
                     .SelectMany(
                         entityType => entityType.GetDeclaredProperties()
                             .Where(
                                 property => property.ClrType == typeof(string))))
        {
            var propertyBuilder = property.Builder;
            if (propertyBuilder.CanSetMaxLength(512)
                && propertyBuilder.CanSetIsUnicode(false))
            {
                propertyBuilder.HasMaxLength(512)!.IsUnicode(false);
            }
        }
    }
}

Ici, nous pouvons être sûrs que l’appel à HasMaxLength ne retournera pas null. Il est toujours recommandé d’utiliser l’instance du générateur retournée par HasMaxLength, car elle peut être différente de propertyBuilder.

Remarque

Les autres conventions ne sont pas déclenchées immédiatement après une modification d’une convention, elles sont retardées jusqu’à ce que toutes les conventions aient terminé le traitement de la modification actuelle.

IConventionContext

Toutes les méthodes de convention ont également un paramètre IConventionContext<TMetadata>. Il fournit des méthodes qui pourraient être utiles dans certains cas spécifiques.

Exemple : Convention NotMappedAttribute

Cette convention recherche NotMappedAttribute sur un type ajouté au modèle et tente de supprimer ce type d’entité du modèle. Toutefois, si le type d’entité est supprimé du modèle, toutes les autres conventions qui implémentent ProcessEntityTypeAdded n’ont plus besoin d’être exécutées. Pour ce faire, appelez StopProcessing():

public virtual void ProcessEntityTypeAdded(
    IConventionEntityTypeBuilder entityTypeBuilder,
    IConventionContext<IConventionEntityTypeBuilder> context)
{
    var type = entityTypeBuilder.Metadata.ClrType;
    if (!Attribute.IsDefined(type, typeof(NotMappedAttribute), inherit: true))
    {
        return;
    }

    if (entityTypeBuilder.ModelBuilder.Ignore(entityTypeBuilder.Metadata.Name, fromDataAnnotation: true) != null)
    {
        context.StopProcessing();
    }
}

IConventionModel

Chaque objet générateur passé à la convention expose une propriété Metadata qui fournit un accès de bas niveau aux objets qui composent le modèle. En particulier, il existe des méthodes qui vous permettent d’itérer sur des objets spécifiques dans le modèle et d’appliquer une configuration commune, comme indiqué dans Exemple : Longueur par défaut pour toutes les propriétés de chaîne. Cette API est similaire à IMutableModel affichée dans configuration en bloc.

Avertissement

Il est recommandé d’effectuer toujours une configuration en appelant des méthodes sur le générateur exposé en tant que propriété Builder, car les générateurs vérifient si la configuration donnée remplacerait un élément déjà spécifié à l’aide de l’API Fluent ou des annotations de données.

Quand utiliser chaque approche pour la configuration en bloc

Utilisez API de métadonnées quand :

  • La configuration doit être appliquée à un certain moment et ne pas réagir aux modifications ultérieures dans le modèle.
  • La vitesse de construction du modèle est très importante. L’API de métadonnées a moins de vérifications de sécurité et peut donc être légèrement plus rapide que d’autres approches, mais l’utilisation d’un modèle compilé générerait encore plus de temps de démarrage.

Utilisez configuration du modèle pré-convention quand :

  • La condition d’applicabilité est simple, car elle dépend uniquement du type.
  • La configuration doit être appliquée à tout moment, une propriété du type donné est ajoutée dans le modèle et remplace les annotations et conventions de données

Utilisez conventions de finalisation quand :

  • La condition d’applicabilité est complexe.
  • La configuration ne doit pas remplacer ce qui est spécifié par les annotations de données.

Utilisez conventions interactives quand :

  • Plusieurs conventions dépendent les unes des autres. La finalisation des conventions s’exécute dans l’ordre dans lequel elles ont été ajoutées et ne peut donc pas réagir aux modifications apportées par la finalisation ultérieure des conventions.
  • La logique est partagée entre plusieurs contextes. Les conventions interactives sont plus sûres que d’autres approches.