Dela via


Masskonfiguration av modell

När en aspekt måste konfigureras på samma sätt för flera entitetstyper kan följande tekniker minska koddupliceringen och konsolidera logiken.

Se det fullständiga exempelprojektet som innehåller kodfragmenten som visas nedan.

Bulkkonfiguration i OnModelCreating

Varje builder-objekt som returneras från ModelBuilder exponerar en Model eller Metadata -egenskap som ger åtkomst på låg nivå till de objekt som utgör modellen. I synnerhet finns det metoder som gör att du kan iterera över specifika objekt i modellen och tillämpa gemensam konfiguration på dem.

I följande exempel innehåller modellen en anpassad värdetyp Currency:

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

    public decimal Amount { get; }

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

Egenskaper av den här typen identifieras inte som standard eftersom den aktuella EF-providern inte vet hur den ska mappas till en databastyp. Det här kodfragmentet OnModelCreating lägger till alla egenskaper för typen Currency och konfigurerar en värdekonverterare till en typ som stöds – : 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))
    {
    }
}

Nackdelar med metadata-API:et

  • Till skillnad från Fluent API måste varje ändring av modellen göras explicit. Om vissa av Currency egenskaperna till exempel har konfigurerats som navigering av en konvention måste du först ta bort navigeringen som refererar till CLR-egenskapen innan du lägger till en egenskap av entitetstyp för den. #9117 förbättrar detta.
  • Konventionerna körs efter varje ändring. Om du tar bort en navigering som identifieras av en konvention körs konventionen igen och kan lägga till den igen. För att förhindra att detta inträffar måste du antingen fördröja konventionerna tills efter att egenskapen har lagts till genom att anropa DelayConventions() och senare ta bort det returnerade objektet eller markera CLR-egenskapen som ignorerad med hjälp av AddIgnored.
  • Entitetstyper kan läggas till när den här iterationen inträffar och konfigurationen tillämpas inte på dem. Detta kan vanligtvis förhindras genom att den här koden placeras i slutet av OnModelCreating, men om du har två beroende konfigurationsuppsättningar kanske det inte finns någon ordning som gör att de kan tillämpas konsekvent.

Förkonventionskonfiguration

MED EF Core kan mappningskonfigurationen anges en gång för en viss CLR-typ. konfigurationen tillämpas sedan på alla egenskaper av den typen i modellen när de identifieras. Detta kallas "förkonventionell modellkonfiguration" eftersom det konfigurerar aspekter av modellen innan modellbyggnadskonventionerna tillåts att köras. En sådan konfiguration tillämpas genom att åsidosätta ConfigureConventions på den typ som härleds från DbContext.

Det här exemplet visar hur du konfigurerar alla egenskaper av typen Currency för att ha en värdekonverterare:

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

Och det här exemplet visar hur du konfigurerar vissa fasetter för alla egenskaper av typen string:

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

Anmärkning

Den typ som anges i ett anrop från ConfigureConventions kan vara en bastyp, ett gränssnitt eller en allmän typdefinition. Alla matchande konfigurationer tillämpas i ordning från de minst specifika:

  1. Gränssnitt
  2. Bastyp
  3. Allmän typdefinition
  4. Ej nullbar värdetyp
  5. Exakt typ

Viktigt!

Förkonvent konfiguration motsvarar explicit konfiguration som tillämpas så snart ett matchande objekt läggs till i modellen. Den åsidosätter alla konventioner och dataanteckningar. Med den ovanstående konfigurationen kommer till exempel alla utländska nyckelegenskaper för strängar att skapas som icke-unicode med MaxLength 1024, även om detta inte matchar huvudnyckeln.

Ignorerande av typer

Konfiguration före konventionen gör det också möjligt att ignorera en typ och förhindra att den identifieras av konventioner antingen som en entitetstyp eller som en egenskap för en entitetstyp:

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

Förinställd typmappning

I allmänhet kan EF översätta frågor med konstanter av en typ som inte stöds av providern, så länge du har angett en värdekonverterare för en egenskap av den här typen. I frågor som inte innehåller några egenskaper av den här typen finns det dock inget sätt för EF att hitta rätt värdekonverterare. I det här fallet är det möjligt att anropa DefaultTypeMapping för att lägga till eller åsidosätta en mappning av providertyp:

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

Begränsningar i konfiguration före konventionen

  • Många aspekter kan inte konfigureras med den här metoden. #6787 expanderar detta till fler typer.
  • För närvarande bestäms konfigurationen endast av CLR-typen. #20418 skulle tillåta anpassade predikat.
  • Den här konfigurationen utförs innan en modell skapas. Om det uppstår konflikter när du tillämpar den, kommer undantagsstackspårningen inte att innehålla ConfigureConventions metoden, vilket kan göra det svårare att hitta orsaken.

Konventioner

EF Core-modellbyggkonventioner är klasser som innehåller logik som utlöses baserat på ändringar som görs i modellen när den skapas. Detta behåller modellen up-to-date när explicit konfiguration görs, mappningsattribut tillämpas och andra konventioner körs. För att delta i detta implementerar varje konvention ett eller flera gränssnitt som avgör när motsvarande metod ska utlösas. En konvention som implementerar IEntityTypeAddedConvention utlöses till exempel när en ny entitetstyp läggs till i modellen. På samma sätt utlöses en konvention som implementerar båda IForeignKeyAddedConvention och IKeyAddedConvention när antingen en nyckel eller en sekundärnyckel läggs till i modellen.

Modellbyggkonventioner är ett kraftfullt sätt att styra modellkonfigurationen, men kan vara komplext och svårt att få rätt. I många fall kan den förkonventa modellkonfigurationen användas i stället för att enkelt ange gemensam konfiguration för egenskaper och typer.

Lägga till en ny konvention

Exempel: Begränsa längden på diskriminerande egenskaper

Strategin för arvsmappning i tabell-per-hierarki kräver en diskriminerande kolumn för att ange vilken typ som representeras på en viss rad. Som standardinställning använder EF en obegränsad strängkolumn för diskriminatorn, vilket säkerställer att den fungerar för alla diskriminatorlängder. Om du begränsar den maximala längden på diskriminerande strängar kan det dock ge effektivare lagring och frågor. Nu ska vi skapa en ny konvention som gör det.

EF Core-modellbyggkonventioner utlöses baserat på ändringar som görs i modellen när den byggs. Detta behåller modellen up-to-date när explicit konfiguration görs, mappningsattribut tillämpas och andra konventioner körs. För att delta i detta implementerar varje konvention ett eller flera gränssnitt som avgör när konventionen ska utlösas. En konvention som implementerar IEntityTypeAddedConvention utlöses till exempel när en ny entitetstyp läggs till i modellen. På samma sätt utlöses en konvention som implementerar båda IForeignKeyAddedConvention och IKeyAddedConvention när antingen en nyckel eller en sekundärnyckel läggs till i modellen.

Det kan vara svårt att veta vilka gränssnitt som ska implementeras, eftersom konfigurationen av modellen vid ett tillfälle kan ändras eller tas bort vid ett senare tillfälle. En nyckel kan till exempel skapas av konventionen, men sedan ersättas senare när en annan nyckel har konfigurerats explicit.

Låt oss göra detta lite mer konkret genom att göra ett första försök att implementera konventionen om diskriminerande längd:

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

Den här konventionen implementerar IEntityTypeBaseTypeChangedConvention, vilket innebär att den utlöses när den mappade arvshierarkin för en entitetstyp ändras. Konventionen hittar och konfigurerar sedan strängdiskrimineringsegenskapen för hierarkin.

Den här konventionen används sedan genom att anropa Add i ConfigureConventions:

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

Anmärkning

I stället för att lägga till en instans av konventionen direkt accepterar Add metoden en fabrik för att skapa instanser av konventionen. Detta gör att konventionen kan använda beroenden från den interna EF Core-tjänstleverantören. Eftersom den här konventionen inte har några beroenden heter _parametern för tjänstprovidern , vilket indikerar att den aldrig används.

Att skapa modellen och titta på Post entitetstypen visar att detta har fungerat – den diskriminerande egenskapen är nu konfigurerad till med en maximal längd på 24:

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

Men vad händer om vi nu uttryckligen konfigurerar en annan diskriminerande egenskap? Till exempel:

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

När vi tittar på felsökningsvyn för modellen upptäcker vi att den diskriminerande längden inte längre är konfigurerad.

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

Detta beror på att diskrimineringsegenskapen som vi konfigurerade i vår konvention senare togs bort när den anpassade diskriminatorn lades till. Vi skulle kunna försöka åtgärda detta genom att implementera ett annat gränssnitt i vår konvention för att reagera på de diskriminerande förändringarna, men det är inte lätt att ta reda på vilket gränssnitt som ska implementeras.

Lyckligtvis finns det ett enklare tillvägagångssätt. Mycket av tiden spelar det ingen roll hur modellen ser ut när den byggs, så länge den slutliga modellen är korrekt. Dessutom behöver den konfiguration som vi vill tillämpa ofta inte utlösa andra konventioner för att reagera. Därför kan vår konvention implementera IModelFinalizingConvention. Modellslutkonventioner körs när alla andra modellbyggen har slutförts och har därför åtkomst till modellens nästan slutgiltiga tillstånd. Detta motsätter sig interaktiva konventioner som reagerar på varje modelländring och ser till att modellen är up-to-date när som helst i metodkörningen OnModelCreating . En modell som slutför konventionen itererar vanligtvis över hela modellen och konfigurerar modellelement allt eftersom. Så i det här fallet hittar vi alla diskriminerande i modellen och konfigurerar den:

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

När vi har skapat modellen med den här nya konventionen konstaterar vi att diskriminatorns längd nu är korrekt konfigurerad även om den har anpassats.

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

Vi kan gå ett steg längre och konfigurera maxlängden så att den är längden på det längsta diskriminerande värdet:

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

Nu är den maximala längden för diskriminator-kolumnen 8, vilket är längden på "Utvald", det längsta diskriminatorvärdet som används.

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

Exempel: Standardlängd för alla strängegenskaper

Nu ska vi titta på ett annat exempel där en avslutande konvention kan användas – ange en maximal standardlängd för alla strängegenskaper. Konventionen ser ut ungefär som i föregående exempel:

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

Den här konventionen är ganska enkel. Den hittar varje strängegenskap i modellen och anger maxlängden till 512. När vi tittar i felsökningsvyn på egenskaperna för Postser vi att alla strängegenskaper nu har en maxlängd på 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)

Anmärkning

Samma sak kan åstadkommas med förkonvent konfiguration, men med hjälp av en konvention kan ytterligare filtrera tillämpliga egenskaper och för dataanteckningar att åsidosätta konfigurationen.

Slutligen, innan vi lämnar det här exemplet, vad händer om vi använder både MaxStringLengthConvention och DiscriminatorLengthConvention3 samtidigt? Svaret är att det beror på vilken ordning de läggs till, eftersom modellslutkonventioner körs i den ordning de läggs till. Så om MaxStringLengthConvention läggs till sist kommer den att köras sist, och den kommer att ange den maximala längden på den diskriminerande egenskapen till 512. I det här fallet är det därför bättre att lägga DiscriminatorLengthConvention3 till sist så att den kan åsidosätta den maximala standardlängden för bara diskriminerande egenskaper, samtidigt som alla andra strängegenskaper lämnas kvar som 512.

Ersätta en befintlig konvention

Ibland i stället för att ta bort en befintlig konvention helt vill vi i stället ersätta den med en konvention som gör i princip samma sak, men med ändrat beteende. Detta är användbart eftersom den befintliga konventionen redan implementerar de gränssnitt som behövs för att utlösas korrekt.

Exempel: Mappning av opt-in-egenskap

EF Core mappar alla offentliga läs- och skrivbara egenskaper enligt konvention. Detta kanske inte är lämpligt för hur dina entitetstyper definieras. För att ändra detta kan vi ersätta PropertyDiscoveryConvention med vår egen implementering som inte mappar någon egenskap om den inte uttryckligen mappas i OnModelCreating eller markeras med ett nytt attribut med namnet Persist:

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

Här är den nya konventionen:

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

Tips/Råd

När du ersätter en inbyggd konvention bör den nya konventionsimplementeringen ärva från den befintliga konventionsklassen. Observera att vissa konventioner har relations- eller providerspecifika implementeringar, i vilket fall den nya konventionsimplementeringen bör ärva från den mest specifika befintliga konventionsklassen för databasleverantören som används.

Konventionen registreras sedan med metoden Replace i ConfigureConventions:

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

Tips/Råd

Det här är ett fall där den befintliga konventionen har beroenden som representeras av ProviderConventionSetBuilderDependencies beroendeobjektet. Dessa hämtas från den interna tjänstleverantören med hjälp av GetRequiredService och skickas till konventionskonstruktorn.

Observera att den här konventionen tillåter att fält mappas (förutom egenskaper) så länge de är markerade med [Persist]. Det innebär att vi kan använda privata fält som dolda nycklar i modellen.

Tänk till exempel på följande entitetstyper:

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

Modellen som skapats av dessa entitetstyper är:

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

Normalt IsClean skulle ha mappats, men eftersom den inte är markerad med [Persist]behandlas den nu som en icke-mappad egenskap.

Tips/Råd

Det gick inte att implementera den här konventionen som en slutlig modellkonvention eftersom det finns befintliga slutliga modellkonventioner som måste köras efter att egenskapen har mappats för ytterligare konfigurering.

Implementeringsöverväganden för konventioner

EF Core håller reda på hur varje konfigurationsdel gjordes. Detta representeras av ConfigurationSource enumerationen. De olika typerna av konfiguration är:

  • Explicit: Modellelementet konfigurerades uttryckligen i OnModelCreating
  • DataAnnotation: Modellelementet konfigurerades med hjälp av ett mappningsattribut (även kallat dataanteckning) för CLR-typen
  • Convention: Modellelementet konfigurerades av en modellbyggnadskonvention

Konventioner bör aldrig åsidosätta konfigurationen som har markerats som DataAnnotation eller Explicit. Detta uppnås med hjälp av en konventionsbyggare, IConventionPropertyBuildertill exempel , som hämtas från Builder egenskapen . Till exempel:

property.Builder.HasMaxLength(512);

Om du anropar HasMaxLength convention builder anges bara maxlängden om den inte redan har konfigurerats av ett mappningsattribut eller i OnModelCreating.

Builder-metoder som denna har också en andra parameter: fromDataAnnotation. Ställ in detta till true om konventionen utför konfigurationen för ett mappningsattribut. Till exempel:

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

Detta ställer in ConfigurationSource till DataAnnotation, vilket innebär att värdet nu kan åsidosättas genom explicit mappning på OnModelCreating, men inte av konventioner för attribut som inte mappas.

Om den aktuella konfigurationen inte kan åsidosättas returnerar nullmetoden . Detta måste tas med i beräkningen om du behöver utföra ytterligare konfiguration:

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

Observera att om unicode-konfigurationen inte kan åsidosättas kommer maxlängden fortfarande att anges. Om du bara behöver konfigurera fasetter när båda anropen lyckas kan du kontrollera detta i förebyggande syfte genom att anropa CanSetMaxLength och 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);
            }
        }
    }
}

Här kan vi vara säkra på att anropet till HasMaxLength inte returnerar null. Vi rekommenderar fortfarande att du använder builder-instansen som returneras från HasMaxLength eftersom den kan skilja sig från propertyBuilder.

Anmärkning

Andra konventioner utlöses inte omedelbart efter att en konvention har gjort en ändring, de fördröjs tills alla konventioner har slutfört bearbetningen av den aktuella ändringen.

IConventionContext

Alla konventionsmetoder har också en IConventionContext<TMetadata> parameter. Den innehåller metoder som kan vara användbara i vissa specifika fall.

Exempel: NotMappedAttribute-konventionen

Den här konventionen söker NotMappedAttribute efter en typ som läggs till i modellen och försöker ta bort den entitetstypen från modellen. Men om entitetstypen tas bort från modellen behöver andra konventioner som implementerar ProcessEntityTypeAdded inte längre köras. Detta kan åstadkommas genom att anropa 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

Varje builder-objekt som skickas till konventionen exponerar en Metadata egenskap som ger åtkomst på låg nivå till de objekt som utgör modellen. I synnerhet finns det metoder som gör att du kan iterera över specifika objekt i modellen och tillämpa gemensam konfiguration på dem enligt exempel: Standardlängd för alla strängegenskaper. Det här API:et liknar IMutableModel, som visas i Masskonfiguration.

Försiktighet

Vi rekommenderar att du alltid utför konfigurationen genom att anropa metoder på byggaren som exponeras som Builder egenskapen, eftersom byggarna kontrollerar om den angivna konfigurationen skulle åsidosätta något som redan har angetts med hjälp av Fluent API eller dataanteckningar.

När du ska använda varje metod för masskonfiguration

Använd metadata-API när:

  • Konfigurationen måste tillämpas vid en viss tidpunkt och inte reagera på senare ändringar i modellen.
  • Modellbygghastigheten är mycket viktig. Metadata-API:et har färre säkerhetskontroller och kan därför vara något snabbare än andra metoder, men att använda en kompilerad modell skulle ge ännu bättre starttider.

Använd modellkonfiguration före konventionen när:

  • Tillämplighetsvillkoret är enkelt eftersom det bara beror på typen.
  • Konfigurationen måste tillämpas när som helst som en egenskap av den angivna typen läggs till i modellen och åsidosätter dataanteckningar och konventioner

Använd Slutför konventioner när:

  • Tillämplighetsvillkoret är komplext.
  • Konfigurationen bör inte åsidosätta vad som anges av dataanteckningar.

Använd interaktiva konventioner när:

  • Flera konventioner är beroende av varandra. Slutgiltiga konventioner utförs i den ordning de har lagts till och kan därmed inte reagera på ändringar som gjorts av senare slutgiltiga konventioner.
  • Logiken delas mellan flera kontexter. Interaktiva konventioner är säkrare än andra metoder.