Share via


Configurazione in blocco del modello

Quando un aspetto deve essere configurato nello stesso modo in più tipi di entità, le tecniche seguenti consentono di ridurre la duplicazione del codice e consolidare la logica.

Vedere il progetto di esempio completo contenente i frammenti di codice presentati di seguito.

Configurazione in blocco in OnModelCreating

Ogni oggetto generatore restituito da ModelBuilder espone una Model proprietà o Metadata che fornisce un accesso di basso livello agli oggetti che costituiscono il modello. In particolare, esistono metodi che consentono di eseguire l'iterazione su oggetti specifici nel modello e di applicare una configurazione comune.

Nell'esempio seguente il modello contiene un tipo di Currencyvalore personalizzato:

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

    public decimal Amount { get; }

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

Le proprietà di questo tipo non vengono individuate per impostazione predefinita perché il provider EF corrente non sa come eseguirne il mapping a un tipo di database. Questo frammento di OnModelCreating aggiunge tutte le proprietà del tipo Currency e configura un convertitore di valori a un tipo supportato - 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))
    {
    }
}

Svantaggi dell'API metadati

  • A differenza dell'API Fluent, ogni modifica al modello deve essere eseguita in modo esplicito. Ad esempio, se alcune delle Currency proprietà sono state configurate come spostamenti in base a una convenzione, è necessario prima rimuovere la struttura di spostamento che fa riferimento alla proprietà CLR prima di aggiungere una proprietà del tipo di entità. #9117 migliorerà questo problema.
  • Le convenzioni vengono eseguite dopo ogni modifica. Se si rimuove una navigazione individuata da una convenzione, la convenzione verrà eseguita nuovamente e potrebbe aggiungerla nuovamente. Per evitare che ciò accada, è necessario ritardare le convenzioni fino a quando la proprietà viene aggiunta chiamando DelayConventions() e successivamente eliminando l'oggetto restituito o per contrassegnare la proprietà CLR come ignorata tramite AddIgnored.
  • I tipi di entità possono essere aggiunti dopo l'esecuzione di questa iterazione e la configurazione non verrà applicata. Ciò può in genere essere impedito inserendo questo codice alla fine di OnModelCreating, ma se si dispone di due set di configurazioni interdipendenti potrebbe non esserci un ordine che consentirà di applicarlo in modo coerente.

Configurazione pre-convenzione

EF Core consente di specificare la configurazione di mapping una sola volta per un determinato tipo CLR; tale configurazione viene quindi applicata a tutte le proprietà di quel tipo nel modello man mano che vengono individuate. Questa operazione è denominata "configurazione del modello pre-convenzione", poiché configura gli aspetti del modello prima che le convenzioni di compilazione del modello siano consentite per l'esecuzione. Tale configurazione viene applicata eseguendo l'override ConfigureConventions del tipo derivato da DbContext.

In questo esempio viene illustrato come configurare tutte le proprietà di tipo Currency per avere un convertitore di valori:

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

In questo esempio viene illustrato come configurare alcuni facet in tutte le proprietà di tipo string:

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

Nota

Il tipo specificato in una chiamata da ConfigureConventions può essere un tipo di base, un'interfaccia o una definizione di tipo generico. Tutte le configurazioni corrispondenti verranno applicate in ordine dal meno specifico:

  1. Interfaccia
  2. Tipo base
  3. Definizione di tipo generico
  4. Tipo valore non-nullable
  5. Tipo esatto

Importante

La configurazione pre-convenzione equivale alla configurazione esplicita applicata non appena viene aggiunto un oggetto corrispondente al modello. Eseguirà l'override di tutte le convenzioni e delle annotazioni dei dati. Ad esempio, con la configurazione precedente, tutte le proprietà di chiave esterna stringa verranno create come non Unicode con MaxLength 1024, anche quando questa non corrisponde alla chiave principale.

Ignorare i tipi

La configurazione pre-convenzione consente anche di ignorare un tipo e impedire che venga individuato dalle convenzioni come tipo di entità o come proprietà in un tipo di entità:

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

Mapping dei tipi predefinito

Ef è in genere in grado di convertire le query con costanti di un tipo non supportato dal provider, purché sia stato specificato un convertitore di valori per una proprietà di questo tipo. Tuttavia, nelle query che non comportano alcuna proprietà di questo tipo, non esiste alcun modo per EF di trovare il convertitore di valori corretto. In questo caso, è possibile chiamare DefaultTypeMapping per aggiungere o eseguire l'override di un mapping dei tipi di provider:

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

Limitazioni della configurazione pre-convenzione

  • Molti aspetti non possono essere configurati con questo approccio. #6787 si espanderà fino a più tipi.
  • Attualmente la configurazione è determinata solo dal tipo CLR. #20418 consente predicati personalizzati.
  • Questa configurazione viene eseguita prima della creazione di un modello. Se si verificano conflitti durante l'applicazione, l'analisi dello stack di eccezioni non conterrà il ConfigureConventions metodo , quindi potrebbe essere più difficile trovare la causa.

Convenzioni

Nota

Le convenzioni di compilazione di modelli personalizzati sono state introdotte in EF Core 7.0.

Le convenzioni di compilazione dei modelli di EF Core sono classi che contengono logica attivata in base alle modifiche apportate al modello durante la compilazione. In questo modo il modello viene aggiornato man mano che viene eseguita la configurazione esplicita, vengono applicati gli attributi di mapping e vengono eseguite altre convenzioni. Per partecipare a questa operazione, ogni convenzione implementa una o più interfacce che determinano quando verrà attivato il metodo corrispondente. Ad esempio, una convenzione che implementa IEntityTypeAddedConvention verrà attivata ogni volta che viene aggiunto un nuovo tipo di entità al modello. Analogamente, una convenzione che implementa e IForeignKeyAddedConventionIKeyAddedConvention verrà attivata ogni volta che viene aggiunta una chiave o una chiave esterna al modello.

Le convenzioni di compilazione dei modelli sono un modo efficace per controllare la configurazione del modello, ma può essere complesso e difficile da ottenere correttamente. In molti casi, è possibile usare la configurazione del modello pre-convenzione per specificare facilmente la configurazione comune per proprietà e tipi.

Aggiunta di una nuova convenzione

Esempio: Vincolare la lunghezza delle proprietà discriminatorie

La strategia di mapping dell'ereditarietà per ogni gerarchia richiede una colonna discriminatoria per specificare il tipo rappresentato in una determinata riga. Per impostazione predefinita, EF usa una colonna stringa non associato per il discriminante, che garantisce che funzioni per qualsiasi lunghezza discriminatoria. Tuttavia, vincolare la lunghezza massima delle stringhe discriminatorie può rendere più efficiente l'archiviazione e le query. Si creerà una nuova convenzione che lo farà.

Le convenzioni di compilazione dei modelli di EF Core vengono attivate in base alle modifiche apportate al modello durante la compilazione. In questo modo il modello viene aggiornato man mano che viene eseguita la configurazione esplicita, vengono applicati gli attributi di mapping e vengono eseguite altre convenzioni. Per partecipare a questa operazione, ogni convenzione implementa una o più interfacce che determinano quando verrà attivata la convenzione. Ad esempio, una convenzione che implementa IEntityTypeAddedConvention verrà attivata ogni volta che viene aggiunto un nuovo tipo di entità al modello. Analogamente, una convenzione che implementa e IForeignKeyAddedConventionIKeyAddedConvention verrà attivata ogni volta che viene aggiunta una chiave o una chiave esterna al modello.

Conoscere le interfacce da implementare può essere difficile, poiché la configurazione eseguita al modello in un determinato momento può essere modificata o rimossa in un secondo momento. Ad esempio, una chiave può essere creata per convenzione, ma successivamente sostituita quando viene configurata in modo esplicito una chiave diversa.

Facciamo questo un po ' più concreto facendo un primo tentativo di implementazione della convenzione di lunghezza discriminatoria:

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);
        }
    }
}

Questa convenzione implementa IEntityTypeBaseTypeChangedConvention, il che significa che verrà attivato ogni volta che viene modificata la gerarchia di ereditarietà mappata per un tipo di entità. La convenzione trova e configura quindi la proprietà discriminatoria della stringa per la gerarchia.

Questa convenzione viene quindi usata chiamando Add in ConfigureConventions:

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

Nota

Anziché aggiungere direttamente un'istanza della convenzione, il Add metodo accetta una factory per la creazione di istanze della convenzione. In questo modo è possibile usare le dipendenze dal provider di servizi interni di EF Core. Poiché questa convenzione non ha dipendenze, il parametro del provider di servizi è denominato _, a indicare che non viene mai usato.

La compilazione del modello e l'analisi del Post tipo di entità mostra che questa operazione ha funzionato: la proprietà discriminatoria è ora configurata in con una lunghezza massima di 24:

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

Ma cosa succede se ora configuriamo in modo esplicito una proprietà discriminatoria diversa? Ad esempio:

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

Esaminando la visualizzazione di debug del modello, si scopre che la lunghezza del discriminatorio non è più configurata.

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

Ciò è dovuto al fatto che la proprietà discriminatoria configurata nella convenzione è stata successivamente rimossa quando è stato aggiunto il discriminatorio personalizzato. Potremmo tentare di risolvere questo problema implementando un'altra interfaccia sulla nostra convenzione per reagire alle modifiche discriminatorie, ma capire quale interfaccia implementare non è facile.

Fortunatamente, esiste un approccio più semplice. Molto tempo, non importa l'aspetto del modello mentre è in fase di compilazione, purché il modello finale sia corretto. Inoltre, la configurazione da applicare spesso non deve attivare altre convenzioni per reagire. Pertanto, la convenzione può implementare IModelFinalizingConvention. Le convenzioni di finalizzazione del modello vengono eseguite dopo il completamento di tutti gli altri edifici del modello e quindi hanno accesso allo stato quasi finale del modello. Ciò è contrario alle convenzioni interattive che reagiscono a ogni modifica del modello e assicurano che il modello sia aggiornato in qualsiasi momento dell'esecuzione del OnModelCreating metodo. Una convenzione di finalizzazione del modello in genere eseguirà l'iterazione dell'intero modello configurando gli elementi del modello man mano che passa. In questo caso, quindi, si troverà ogni discriminare nel modello e la si 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);
            }
        }
    }
}

Dopo aver compilato il modello con questa nuova convenzione, si scopre che la lunghezza del discriminatorio è ora configurata correttamente anche se è stata personalizzata:

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

È possibile procedere ulteriormente e configurare la lunghezza massima in modo che sia la lunghezza del valore discriminatorio più lungo:

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);
            }
        }
    }
}

Ora la lunghezza massima della colonna discriminatoria è 8, ovvero la lunghezza di "In primo piano", il valore discriminatorio più lungo in uso.

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

Esempio: lunghezza predefinita per tutte le proprietà stringa

Verrà ora esaminato un altro esempio in cui è possibile usare una convenzione di finalizzazione, impostando una lunghezza massima predefinita per qualsiasi proprietà stringa. La convenzione è simile all'esempio precedente:

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);
        }
    }
}

Questa convenzione è piuttosto semplice. Trova ogni proprietà stringa nel modello e ne imposta la lunghezza massima su 512. Esaminando la visualizzazione di debug nelle proprietà per Post, si noterà che tutte le proprietà stringa hanno ora una lunghezza massima di 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)

Nota

La stessa operazione può essere eseguita tramite la configurazione pre-convenzione, ma l'uso di una convenzione consente di filtrare ulteriormente le proprietà applicabili e per le annotazioni dei dati per eseguire l'override della configurazione.

Infine, prima di lasciare questo esempio, cosa accade se si usano entrambi MaxStringLengthConvention e DiscriminatorLengthConvention3 contemporaneamente? La risposta è che dipende dall'ordine in cui vengono aggiunte, dal momento che le convenzioni di finalizzazione del modello vengono eseguite nell'ordine in cui vengono aggiunte. Quindi, se MaxStringLengthConvention viene aggiunto per ultimo, verrà eseguito per ultimo e la lunghezza massima della proprietà discriminatoria verrà impostata su 512. Pertanto, in questo caso, è preferibile aggiungere DiscriminatorLengthConvention3 per ultimo in modo che possa sostituire la lunghezza massima predefinita solo per le proprietà discriminatorie, lasciando tutte le altre proprietà stringa come 512.

Sostituzione di una convenzione esistente

In alcuni casi, anziché rimuovere completamente una convenzione esistente, si vuole sostituirla con una convenzione che esegue fondamentalmente la stessa operazione, ma con il comportamento modificato. Ciò è utile perché la convenzione esistente implementerà già le interfacce che deve essere attivata in modo appropriato.

Esempio: Mapping delle proprietà di consenso esplicito

EF Core esegue il mapping di tutte le proprietà pubbliche di lettura/scrittura per convenzione. Questo potrebbe non essere appropriato per il modo in cui vengono definiti i tipi di entità. Per modificare questo problema, è possibile sostituire con la propria implementazione che non esegue il PropertyDiscoveryConvention mapping di alcuna proprietà, a meno che non sia mappata in modo esplicito in OnModelCreating o contrassegnata con un nuovo attributo denominato Persist:

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

Ecco la nuova convenzione:

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;
            }
        }
    }
}

Suggerimento

Quando si sostituisce una convenzione predefinita, la nuova implementazione della convenzione deve ereditare dalla classe convenzione esistente. Si noti che alcune convenzioni hanno implementazioni relazionali o specifiche del provider, nel qual caso la nuova implementazione della convenzione deve ereditare dalla classe di convenzioni esistente più specifica per il provider di database in uso.

La convenzione viene quindi registrata usando il Replace metodo in ConfigureConventions:

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

Suggerimento

Si tratta di un caso in cui la convenzione esistente presenta dipendenze, rappresentate dall'oggetto ProviderConventionSetBuilderDependencies dipendenza. Questi vengono ottenuti dal provider di servizi interno usando GetRequiredService e passati al costruttore della convenzione.

Si noti che questa convenzione consente di eseguire il mapping dei campi (oltre alle proprietà) purché siano contrassegnati con [Persist]. Ciò significa che è possibile usare campi privati come chiavi nascoste nel modello.

Si considerino ad esempio i tipi di entità seguenti:

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; }
}

Il modello compilato da questi tipi di entità è:

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

In genere, IsClean sarebbe stato eseguito il mapping, ma poiché non è contrassegnato con [Persist], viene ora considerato come una proprietà non mappata.

Suggerimento

Questa convenzione non può essere implementata come convenzione di finalizzazione del modello perché esistono convenzioni di finalizzazione del modello esistenti che devono essere eseguite dopo il mapping della proprietà per configurarla ulteriormente.

Considerazioni sull'implementazione delle convenzioni

EF Core tiene traccia del modo in cui è stata eseguita ogni parte di configurazione. Questa proprietà è rappresentata dall'enumerazione ConfigurationSource . I diversi tipi di configurazione sono:

  • Explicit: l'elemento del modello è stato configurato in modo esplicito in OnModelCreating
  • DataAnnotation: l'elemento del modello è stato configurato usando un attributo di mapping (noto anche come annotazione dei dati) nel tipo CLR
  • Convention: l'elemento del modello è stato configurato da una convenzione di compilazione del modello

Le convenzioni non devono mai sostituire la configurazione contrassegnata come DataAnnotation o Explicit. Questo risultato viene ottenuto usando un generatore di convenzioni, ad esempio , IConventionPropertyBuilderottenuto dalla Builder proprietà . Ad esempio:

property.Builder.HasMaxLength(512);

La chiamata HasMaxLength al generatore di convenzioni imposta la lunghezza massima solo se non è già stata configurata da un attributo di mapping o in OnModelCreating.

I metodi del generatore come questo hanno anche un secondo parametro: fromDataAnnotation. Impostare questa proprietà su true se la convenzione esegue la configurazione per conto di un attributo di mapping. Ad esempio:

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

In questo modo l'oggetto ConfigurationSource viene DataAnnotationimpostato su , il che significa che il valore può ora essere sottoposto a override tramite mapping esplicito in OnModelCreating, ma non da convenzioni di attributi non di mapping.

Se non è possibile eseguire l'override della configurazione corrente, il metodo restituirà null, questo deve essere tenuto in considerazione se è necessario eseguire un'ulteriore configurazione:

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

Si noti che se non è possibile eseguire l'override della configurazione Unicode, la lunghezza massima verrà comunque impostata. Nel caso in cui sia necessario configurare i facet solo quando entrambe le chiamate hanno esito positivo, è possibile controllarlo in modo preliminare chiamando CanSetMaxLength e 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);
            }
        }
    }
}

In questo caso è possibile assicurarsi che la chiamata a HasMaxLength non restituisca null. È comunque consigliabile usare l'istanza del generatore restituita da HasMaxLength perché potrebbe essere diversa da propertyBuilder.

Nota

Le altre convenzioni non vengono attivate immediatamente dopo che una convenzione apporta una modifica, vengono posticipate fino a quando tutte le convenzioni non hanno completato l'elaborazione della modifica corrente.

IConventionContext

Tutti i metodi della convenzione hanno anche un IConventionContext<TMetadata> parametro . Fornisce metodi che potrebbero essere utili in alcuni casi specifici.

Esempio: Convenzione NotMappedAttribute

Questa convenzione cerca NotMappedAttribute un tipo aggiunto al modello e tenta di rimuovere tale tipo di entità dal modello. Tuttavia, se il tipo di entità viene rimosso dal modello, tutte le altre convenzioni che implementano ProcessEntityTypeAdded non devono più essere eseguite. A tale scopo, è possibile chiamare 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

Ogni oggetto generatore passato alla convenzione espone una Metadata proprietà che fornisce un accesso di basso livello agli oggetti che costituiscono il modello. In particolare, esistono metodi che consentono di eseguire l'iterazione su oggetti specifici nel modello e applicare la configurazione comune a tali oggetti, come illustrato in Esempio: Lunghezza predefinita per tutte le proprietà stringa. Questa API è simile a IMutableModel quella illustrata nella configurazione in blocco.

Attenzione

È consigliabile eseguire sempre la configurazione chiamando metodi sul generatore esposti come Builder proprietà, perché i generatori controllano se la configurazione specificata sostituirebbe un elemento già specificato usando l'API Fluent o le annotazioni dati.

Quando usare ogni approccio per la configurazione in blocco

Usare l'API metadati quando:

  • La configurazione deve essere applicata in un determinato momento e non reagire alle modifiche successive nel modello.
  • La velocità di compilazione del modello è molto importante. L'API dei metadati ha meno controlli di sicurezza e pertanto può essere leggermente più veloce rispetto ad altri approcci, tuttavia l'uso di un modello compilato produrrebbe tempi di avvio ancora migliori.

Usare la configurazione del modello pre-convenzione quando:

  • La condizione di applicabilità è semplice perché dipende solo dal tipo.
  • La configurazione deve essere applicata in qualsiasi punto in cui una proprietà del tipo specificato viene aggiunta nel modello ed esegue l'override di annotazioni e convenzioni di dati

Usare le convenzioni di finalizzazione quando:

  • La condizione di applicabilità è complessa.
  • La configurazione non deve eseguire l'override di quanto specificato dalle annotazioni dei dati.

Usare convenzioni interattive quando:

  • Più convenzioni dipendono l'una dall'altra. Le convenzioni di finalizzazione vengono eseguite nell'ordine in cui sono state aggiunte e pertanto non possono reagire alle modifiche apportate successivamente finalizzando le convenzioni.
  • La logica viene condivisa tra diversi contesti. Le convenzioni interattive sono più sicure rispetto ad altri approcci.