Delen via


Model voor bulkconfiguratie

Wanneer een aspect op dezelfde manier moet worden geconfigureerd voor meerdere entiteitstypen, kunt u met de volgende technieken duplicatie van code verminderen en de logica samenvoegen.

Bekijk het volledige voorbeeldproject met de codefragmenten die hieronder worden weergegeven.

Bulkconfiguratie in OnModelCreating

Elk opbouwobject dat wordt geretourneerd door ModelBuilder heeft een Model- of Metadata-eigenschap die toegang op laag niveau biedt tot de objecten die het model vormen. Er zijn met name methoden waarmee u specifieke objecten in het model kunt herhalen en algemene configuratie hierop kunt toepassen.

In het volgende voorbeeld bevat het model een aangepast waardetype Currency:

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

    public decimal Amount { get; }

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

Eigenschappen van dit type worden niet standaard gedetecteerd omdat de huidige EF-provider niet weet hoe deze moet worden toegewezen aan een databasetype. Met dit fragment worden OnModelCreating alle eigenschappen van het type Currency toegevoegd en wordt een waardeomzetter geconfigureerd voor een ondersteund type: 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))
    {
    }
}

Nadelen van de metagegevens-API

  • In tegenstelling tot Fluent API moet elke wijziging van het model expliciet worden uitgevoerd. Als sommige eigenschappen Currency bijvoorbeeld zijn geconfigureerd als navigatie volgens een conventie, moet u eerst de navigatie verwijderen die verwijst naar de CLR-eigenschap voordat u er een eigenschap voor het entiteitstype voor toevoegt. #9117 zal dit verbeteren.
  • De conventies worden na elke wijziging uitgevoerd. Als u een navigatie verwijdert die door een conventie is gedetecteerd, wordt de conventie opnieuw uitgevoerd en kan deze weer worden toegevoegd. Om dit te voorkomen, moet u de conventies uitstellen totdat de eigenschap is toegevoegd door met DelayConventions() het geretourneerde object aan te roepen en later te verwijderen, of de CLR-eigenschap markeren als genegeerd met behulp van AddIgnored.
  • Entiteitstypen kunnen worden toegevoegd nadat deze iteratie plaatsvindt en de configuratie hierop niet wordt toegepast. Dit kan meestal worden voorkomen door deze code aan het einde van OnModelCreatingte plaatsen, maar als u twee onderling afhankelijke sets configuraties hebt, is er mogelijk geen volgorde waarmee ze consistent kunnen worden toegepast.

Voorconventieconfiguratie

MET EF Core kan de toewijzingsconfiguratie eenmaal worden opgegeven voor een bepaald CLR-type; die configuratie wordt vervolgens toegepast op alle eigenschappen van dat type in het model wanneer ze worden gedetecteerd. Dit wordt 'preconventiemodelconfiguratie' genoemd, omdat hiermee aspecten van het model worden geconfigureerd voordat de modelbouwconventies mogen worden uitgevoerd. Een dergelijke configuratie wordt toegepast door ConfigureConventions op het type dat is afgeleid van DbContext te overschrijven.

In dit voorbeeld ziet u hoe u alle eigenschappen van het type Currency configureert om een waardeconversieprogramma te hebben:

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

In dit voorbeeld ziet u hoe u bepaalde facetten voor alle eigenschappen van het type stringconfigureert:

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

Opmerking

Het type dat is opgegeven in een aanroep van ConfigureConventions, kan een basistype, een interface of een generieke typedefinitie zijn. Alle overeenkomende configuraties worden toegepast op volgorde van het minst specifieke:

  1. gebruikersinterface
  2. Basistype
  3. Algemene typedefinitie
  4. Niet-nullable waardetype
  5. Exacte type

Belangrijk

Configuratie vooraf is gelijk aan expliciete configuratie die wordt toegepast zodra een overeenkomend object aan het model wordt toegevoegd. Het zal alle conventies en Data-annotaties overschrijven. Met de bovenstaande configuratie worden bijvoorbeeld alle eigenschappen van refererende sleutelreeksen gemaakt als niet-unicode met MaxLength 1024, zelfs als dit niet overeenkomt met de principal-sleutel.

Typen negeren

Met de configuratie van de preconventie kunt u ook een type negeren en voorkomen dat het wordt gedetecteerd door conventies, hetzij als entiteitstype of als een eigenschap voor een entiteitstype:

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

Standaardtypetoewijzing

Over het algemeen kan EF query's vertalen met constanten van een type dat niet wordt ondersteund door de provider, zolang u een waardeconversieprogramma voor een eigenschap van dit type hebt opgegeven. In query's waarvoor geen eigenschappen van dit type zijn betrokken, is er echter geen manier voor EF om het juiste waardeconversieprogramma te vinden. In dit geval is het mogelijk om een DefaultTypeMapping providertypetoewijzing toe te voegen of te overschrijven:

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

Beperkingen van pre-conventieconfiguratie

  • Veel aspecten kunnen niet worden geconfigureerd met deze benadering. #6787 breidt dit uit naar meer typen.
  • Momenteel wordt de configuratie alleen bepaald door het CLR-type. #20418 staat aangepaste predicaten toe.
  • Deze configuratie wordt uitgevoerd voordat een model wordt gemaakt. Als er conflicten optreden bij het toepassen ervan, bevat de tracering van de uitzonderingsstack de ConfigureConventions methode niet, dus het kan moeilijker zijn om de oorzaak te vinden.

Verdragen

Ef Core-modelbouwconventies zijn klassen die logica bevatten die wordt geactiveerd op basis van wijzigingen die in het model worden aangebracht terwijl het wordt gebouwd. Hierdoor wordt het model up-to-date behouden wanneer expliciete configuratie wordt gemaakt, namelijk wanneer toewijzingsattributen worden toegepast en andere conventies worden uitgevoerd. Om hieraan deel te nemen, implementeert elke conventie een of meer interfaces die bepalen wanneer de bijbehorende methode wordt geactiveerd. Een conventie die wordt geïmplementeerd, wordt bijvoorbeeld geactiveerd wanneer een nieuw entiteitstype IEntityTypeAddedConvention wordt toegevoegd aan het model. Op dezelfde manier wordt een conventie die zowel IForeignKeyAddedConvention als IKeyAddedConvention implementeert, geactiveerd telkens wanneer een sleutel of een vreemde sleutel aan het model wordt toegevoegd.

Modelbouwconventies zijn een krachtige manier om de modelconfiguratie te beheren, maar kan complex en moeilijk te bereiken zijn. In veel gevallen kan de pre-conventie modelconfiguratie worden gebruikt om eenvoudig gemeenschappelijke configuratie voor eigenschappen en typen op te geven.

Een nieuwe conventie toevoegen

Voorbeeld: Lengte van discriminatoreigenschappen beperken

Voor de toewijzingsstrategie voor overname per hiërarchie is een discriminatorkolom vereist om aan te geven welk type in een bepaalde rij wordt weergegeven. EF maakt standaard gebruik van een niet-gebonden tekenreekskolom voor de discriminator, die ervoor zorgt dat deze werkt voor elke discriminatorlengte. Het beperken van de maximale lengte van discriminatortekenreeksen kan echter zorgen voor efficiëntere opslag en query's. Laten we een nieuwe conventie maken die dat doet.

Bouwconventies voor EF Core-modellen worden geactiveerd op basis van wijzigingen die in het model worden aangebracht terwijl het wordt gebouwd. Hierdoor wordt het model up-to-date behouden wanneer expliciete configuratie wordt gemaakt, namelijk wanneer toewijzingsattributen worden toegepast en andere conventies worden uitgevoerd. Om hieraan deel te nemen, implementeert elke conventie een of meer interfaces die bepalen wanneer de conventie wordt geactiveerd. Een conventie die wordt geïmplementeerd, wordt bijvoorbeeld geactiveerd wanneer een nieuw entiteitstype IEntityTypeAddedConvention wordt toegevoegd aan het model. Op dezelfde manier wordt een conventie die zowel IForeignKeyAddedConvention als IKeyAddedConvention implementeert, geactiveerd telkens wanneer een sleutel of een vreemde sleutel aan het model wordt toegevoegd.

Weten welke interfaces moeten worden geïmplementeerd, kan lastig zijn, omdat de configuratie op één punt in het model kan worden gewijzigd of verwijderd op een later punt. Een sleutel kan bijvoorbeeld worden gemaakt volgens de conventie, maar later vervangen wanneer een andere sleutel expliciet wordt geconfigureerd.

Laten we dit wat concreter maken door eerst een poging te doen om de overeenkomst voor de lengte van de discriminatie te implementeren:

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

Deze conventie implementeert IEntityTypeBaseTypeChangedConvention, wat betekent dat deze wordt geactiveerd wanneer de toegewezen overnamehiërarchie voor een entiteitstype wordt gewijzigd. De conventie zoekt en configureert vervolgens de tekenreeksdiscriminatie-eigenschap voor de hiërarchie.

Deze conventie wordt vervolgens gebruikt door Add aan te roepen in ConfigureConventions:

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

Opmerking

In plaats van rechtstreeks een exemplaar van de conventie toe te voegen, accepteert de Add methode een fabriek voor het maken van exemplaren van de conventie. Hierdoor kan de conventie afhankelijkheden van de interne EF Core-serviceprovider gebruiken. Omdat deze conventie geen afhankelijkheden heeft, heeft de serviceproviderparameter de naam _, wat aangeeft dat deze nooit wordt gebruikt.

Door het model te bouwen en naar het Post entiteitstype te kijken, ziet u dat dit heeft gewerkt: de discriminatoreigenschap is nu geconfigureerd voor een maximale lengte van 24:

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

Maar wat gebeurt er als we nu expliciet een andere discriminatoreigenschap configureren? Voorbeeld:

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

Als we kijken naar de foutopsporingsweergave van het model, vinden we dat de discriminatorlengte niet meer is geconfigureerd.

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

Dit komt doordat de discriminatoreigenschap die we in onze conventie hebben geconfigureerd, later werd verwijderd toen de aangepaste discriminator werd toegevoegd. We kunnen proberen dit op te lossen door een andere interface te implementeren op onze conventie om te reageren op de wijzigingen in de discriminator, maar het bepalen van welke interface te implementeren is niet eenvoudig.

Gelukkig is er een eenvoudigere aanpak. Vaak maakt het niet zoveel uit hoe het model eruitziet terwijl het wordt gebouwd, zolang het uiteindelijke model correct is. Daarnaast hoeft de configuratie die we willen toepassen vaak geen andere conventies te activeren om te reageren. Onze conventie kan daarom worden geïmplementeerd IModelFinalizingConvention. Modelafrondingsconventies worden uitgevoerd nadat alle andere modellering is voltooid en hebben dus toegang tot de bijna-voltooide staat van het model. Dit is in tegenstelling tot interactieve conventies die reageren op elke modelwijziging en ervoor zorgen dat het model op elk moment van de uitvoering van de OnModelCreating methode up-to-date is. Een modelafsluitconventie zal doorgaans het hele model doorlopen en de modelelementen configureren terwijl het voortgaat. In dit geval vinden we dus elke discriminator in het model en configureren we het:

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

Na het bouwen van het model met deze nieuwe conventie, vinden we dat de discriminatorlengte nu correct is geconfigureerd, ook al is deze aangepast:

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

We kunnen nog één stap verder gaan en de maximale lengte configureren als de lengte van de langste discriminatorwaarde:

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

De maximale lengte van de discriminatorkolom is nu 8, de lengte van Aanbevolen, de langste discriminatorwaarde die in gebruik is.

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

Voorbeeld: Standaardlengte voor alle tekenreekseigenschappen

Laten we eens kijken naar een ander voorbeeld waarin een voltooiende conventie kan worden gebruikt: een standaard maximumlengte instellen voor elke tekenreekseigenschap. De conventie ziet er ongeveer hetzelfde uit als in het vorige voorbeeld:

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

Deze conventie is vrij eenvoudig. Hiermee wordt elke tekenreekseigenschap in het model gevonden en wordt de maximale lengte ingesteld op 512. In de foutopsporingsweergave bij de eigenschappen voor Post zien we dat alle tekenreekseigenschappen nu een maximale lengte van 512 hebben.

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)

Opmerking

Hetzelfde kan worden bereikt door de configuratie voorafgaand aan een conventie, maar met behulp van een conventie kunt u toepasselijke eigenschappen verder filteren en om gegevensannotaties de configuratie te laten overschrijven.

Ten slotte, voordat we dit voorbeeld verlaten, wat gebeurt er als we zowel de MaxStringLengthConvention als DiscriminatorLengthConvention3 tegelijkertijd gebruiken? Het antwoord is dat het afhankelijk is van de volgorde die ze worden toegevoegd, omdat modeluitvoeringsconventies worden uitgevoerd in de volgorde waarin ze worden toegevoegd. Dus als MaxStringLengthConvention als laatste wordt toegevoegd, zal het als laatste worden uitgevoerd, en zal de maximale lengte van de discriminator-eigenschap worden ingesteld op 512. Daarom is het in dit geval beter om DiscriminatorLengthConvention3 als laatste toe te voegen, zodat het de standaard maximale lengte alleen voor discriminator-eigenschappen kan overschrijven, terwijl alle andere tekenreekseigenschappen op 512 blijven.

Een bestaande conventie vervangen

Soms in plaats van een bestaande conventie volledig te verwijderen, willen we deze vervangen door een conventie die eigenlijk hetzelfde doet, maar met gewijzigd gedrag. Dit is handig omdat de bestaande conventie al de interfaces implementeert die op de juiste manier moeten worden geactiveerd.

Voorbeeld: Mapping van opt-in-eigenschappen

EF Core wijst alle openbare eigenschappen voor lezen/schrijven per conventie toe. Dit is mogelijk niet geschikt voor de manier waarop uw entiteitstypen worden gedefinieerd. Om dit te veranderen, kunnen we de PropertyDiscoveryConvention vervangen door onze eigen implementatie die geen enkele eigenschap toewijst, tenzij deze expliciet is toegewezen in OnModelCreating of gemarkeerd met een nieuw kenmerk genaamd Persist.

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

Dit is de nieuwe conventie:

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

Aanbeveling

Wanneer u een ingebouwde conventie vervangt, moet de nieuwe conventie-implementatie overnemen van de bestaande conventieklasse. Houd er rekening mee dat sommige conventies relationele of providerspecifieke implementaties hebben, in welk geval de nieuwe conventie-implementatie moet overnemen van de meest specifieke bestaande conventieklasse voor de databaseprovider die wordt gebruikt.

De conventie wordt vervolgens geregistreerd met behulp van de Replace methode in ConfigureConventions:

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

Aanbeveling

Dit is een geval waarin de bestaande conventie afhankelijkheden bevat, vertegenwoordigd door het ProviderConventionSetBuilderDependencies afhankelijkheidsobject. Deze worden verkregen via de interne serviceprovider met behulp van GetRequiredService en doorgegeven aan de constructor van de conventie.

Merk op dat deze conventie het mogelijk maakt om velden te koppelen (naast eigenschappen), zolang ze zijn gemarkeerd met [Persist]. Dit betekent dat we privévelden kunnen gebruiken als verborgen sleutels in het model.

Denk bijvoorbeeld aan de volgende entiteitstypen:

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

Het model dat is gebouwd op basis van deze entiteitstypen is:

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

Normaal gesproken zou IsClean worden afgebeeld, maar omdat het niet gemarkeerd is met [Persist], wordt het nu als een niet-toegewezen eigenschap beschouwd.

Aanbeveling

Deze conventie kan niet worden geïmplementeerd als een modelafvullingsconventie omdat er bestaande modelafmaakconventies zijn die moeten worden uitgevoerd nadat de eigenschap is toegewezen om deze verder te configureren.

Overwegingen bij de implementatie van conventies

EF Core houdt bij hoe elk onderdeel van de configuratie is gemaakt. Dit wordt vertegenwoordigd door de ConfigurationSource opsomming. De verschillende soorten configuratie zijn:

  • Explicit: Het modelelement is expliciet geconfigureerd in OnModelCreating
  • DataAnnotation: Het modelelement is geconfigureerd met behulp van een mapping-kenmerk (ook wel data-anotatie genoemd) op het CLR-type
  • Convention: Het modelelement is geconfigureerd door een modelbouwconventie

Conventies mogen de configuratie die is gemarkeerd als DataAnnotation of Explicitnooit overschrijven. Dit wordt bereikt met behulp van een conventiebouwer, bijvoorbeeld de IConventionPropertyBuilder, die wordt verkregen van de Builder eigenschap. Voorbeeld:

property.Builder.HasMaxLength(512);

Als je de opbouwfunctie voor conventies aanroept HasMaxLength, wordt de maximale lengte alleen ingesteld als deze nog niet is geconfigureerd door een toewijzingskenmerk of in OnModelCreating.

Opbouwmethoden als deze hebben ook een tweede parameter: fromDataAnnotation. Stel dit in op true als de conventie de configuratie maakt namens een koppelingsattribuut. Voorbeeld:

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

Hiermee stelt u de ConfigurationSource in op DataAnnotation, wat betekent dat de waarde nu kan worden overschreven door expliciete toewijzing op OnModelCreating, maar niet door conventies van niet-mapping attributen.

Als de huidige configuratie niet kan worden overschreven, geeft de methode null terug. Houd hier rekening mee als u verdere configuratie moet uitvoeren.

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

Als de Unicode-configuratie niet kan worden overschreven, wordt de maximale lengte nog steeds ingesteld. Als u de facetten alleen moet configureren wanneer beide aanroepen zijn geslaagd, kunt u dit vooraf controleren door te bellen CanSetMaxLength en 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);
            }
        }
    }
}

Hier kunnen we er zeker van zijn dat de oproep naar HasMaxLength geen null zal teruggeven. Het wordt nog steeds aanbevolen om het "builder instance" dat door HasMaxLength wordt geretourneerd te gebruiken, omdat dit anders kan zijn dan propertyBuilder.

Opmerking

Andere conventies worden niet onmiddellijk geactiveerd nadat een conventie een wijziging heeft aangebracht. Ze worden uitgesteld totdat alle conventies de huidige wijziging hebben verwerkt.

IConventionContext

Alle conventiemethoden hebben ook een IConventionContext<TMetadata> parameter. Het biedt methoden die nuttig kunnen zijn in sommige specifieke gevallen.

Voorbeeld: NotMappedAttribute-conventie

Deze conventie zoekt naar NotMappedAttribute op een type dat aan het model wordt toegevoegd en probeert dat type uit het model te verwijderen. Maar als het entiteitstype uit het model wordt verwijderd, hoeven andere conventies die ProcessEntityTypeAdded implementeren niet meer te worden uitgevoerd. Dit kan worden bereikt door het aanroepen van 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

Elk opbouwobject dat aan de conventie wordt doorgegeven, maakt een Metadata eigenschap beschikbaar die toegang op laag niveau biedt tot de objecten die het model vormen. Er zijn met name methoden waarmee u specifieke objecten in het model kunt herhalen en algemene configuratie hierop kunt toepassen, zoals te zien is in voorbeeld: Standaardlengte voor alle tekenreekseigenschappen. Deze API is vergelijkbaar met die bij de IMutableModel.

Waarschuwing

Het wordt aangeraden altijd configuratie uit te voeren door methoden aan te roepen voor de opbouwfunctie die als eigenschap Builder wordt weergegeven, omdat de opbouwfuncties controleren of de opgegeven configuratie iets overschrijft dat al is opgegeven met behulp van Fluent API of Gegevensaantekeningen.

Wanneer moet u elke benadering gebruiken voor bulkconfiguratie

Metagegevens-API gebruiken wanneer:

  • De configuratie moet op een bepaald moment worden toegepast en niet reageren op latere wijzigingen in het model.
  • De snelheid van het bouwen van modellen is erg belangrijk. Metagegevens-API heeft minder veiligheidscontroles en kan dus iets sneller zijn dan andere benaderingen, maar het gebruik van een gecompileerd model zou nog betere opstarttijden opleveren.

Modelconfiguratie van preconventie gebruiken wanneer:

  • De toepasselijkheidsvoorwaarde is eenvoudig omdat deze alleen afhankelijk is van het type.
  • De configuratie moet worden toegepast op elk moment dat een eigenschap van het opgegeven type wordt toegevoegd aan het model en gegevensaantekeningen en conventies overschrijft

Gebruik Voltooien van conventies wanneer:

  • De toepasbaarheidsvoorwaarde is complex.
  • De configuratie mag niet overschrijven wat is opgegeven door gegevensaantekeningen.

Interactieve conventies gebruiken wanneer:

  • Meerdere conventies zijn afhankelijk van elkaar. Het voltooien van conventies wordt uitgevoerd in de volgorde waarin ze zijn toegevoegd en kan daarom niet reageren op wijzigingen die zijn aangebracht door later de conventies te voltooien.
  • De logica wordt gedeeld tussen verschillende contexten. Interactieve conventies zijn veiliger dan andere benaderingen.