Konfiguracja zbiorcza modelu

Jeśli aspekt musi być skonfigurowany w taki sam sposób w wielu typach jednostek, poniższe techniki umożliwiają zmniejszenie duplikacji kodu i skonsolidowanie logiki.

Zobacz pełny przykładowy projekt zawierający fragmenty kodu przedstawione poniżej.

Konfiguracja zbiorcza w modelu OnModelTworzenie

Każdy obiekt konstruktora zwrócony z ModelBuilder uwidacznia Model właściwość lub Metadata , która zapewnia dostęp niskiego poziomu do obiektów składających się na model. W szczególności istnieją metody, które pozwalają iterować nad określonymi obiektami w modelu i stosować do nich wspólną konfigurację.

W poniższym przykładzie model zawiera niestandardowy typ Currencywartości:

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

    public decimal Amount { get; }

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

Właściwości tego typu nie są domyślnie odnajdywane, ponieważ bieżący dostawca ef nie wie, jak mapować go na typ bazy danych. Ten fragment kodu OnModelCreating dodaje wszystkie właściwości typu Currency i konfiguruje konwerter wartości do obsługiwanego typu : 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))
    {
    }
}

Wady interfejsu API metadanych

  • W przeciwieństwie do interfejsu API Fluent każda modyfikacja modelu musi być wykonywana jawnie. Jeśli na przykład niektóre Currency właściwości zostały skonfigurowane jako nawigacja zgodnie z konwencją, musisz najpierw usunąć nawigację odwołującą się do właściwości CLR przed dodaniem dla niej właściwości typu jednostki. #9117 poprawi to.
  • Konwencje są uruchamiane po każdej zmianie. Jeśli usuniesz nawigację odnalezioną zgodnie z konwencją, konwencja zostanie uruchomiona ponownie i może ją dodać z powrotem. Aby temu zapobiec, należy opóźnić konwencje do momentu dodania właściwości przez wywołanie DelayConventions() metody , a później zdysponować zwrócony obiekt lub oznaczyć właściwość CLR jako ignorowaną przy użyciu polecenia AddIgnored.
  • Typy jednostek mogą zostać dodane po wykonaniu tej iteracji, a konfiguracja nie zostanie zastosowana do nich. Zwykle można temu zapobiec, umieszczając ten kod na końcu OnModelCreatingelementu , ale jeśli masz dwa współzależne zestawy konfiguracji, może nie być kolejność, która pozwoli na ich spójne stosowanie.

Konfiguracja przed konwencją

Program EF Core umożliwia określenie konfiguracji mapowania raz dla danego typu CLR; ta konfiguracja jest następnie stosowana do wszystkich właściwości tego typu w modelu podczas ich odnajdowania. Jest to nazywane "konfiguracją modelu przed konwencją", ponieważ konfiguruje aspekty modelu przed uruchomieniem konwencji tworzenia modelu. Taka konfiguracja jest stosowana przez zastąpienie ConfigureConventions typu pochodzącego z DbContextklasy .

W tym przykładzie pokazano, jak skonfigurować wszystkie właściwości typu Currency , aby mieć konwerter wartości:

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

W tym przykładzie pokazano, jak skonfigurować niektóre aspekty we wszystkich właściwościach typu string:

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

Uwaga

Typ określony w wywołaniu z ConfigureConventions może być typem podstawowym, interfejsem lub definicją typu ogólnego. Wszystkie pasujące konfiguracje zostaną zastosowane w kolejności od najmniej określonej:

  1. Interfejs
  2. Typ podstawowy
  3. Definicja typu ogólnego
  4. Typ wartości bez wartości null
  5. Dokładny typ

Ważne

Konfiguracja przed konwencją jest równoważna jawnej konfiguracji, która jest stosowana zaraz po dodaniu zgodnego obiektu do modelu. Spowoduje to zastąpienie wszystkich konwencji i adnotacji danych. Na przykład przy powyższej konfiguracji wszystkie właściwości klucza obcego ciągu zostaną utworzone jako nie-unicode z wartością MaxLength 1024, nawet jeśli nie jest to zgodne z kluczem głównym.

Ignorowanie typów

Konfiguracja przed konwencją umożliwia również zignorowanie typu i uniemożliwienie odnajdowania go przez konwencje jako typ jednostki lub właściwość typu jednostki:

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

Mapowanie typów domyślnych

Ogólnie rzecz biorąc, ef jest w stanie tłumaczyć zapytania z stałymi typu, który nie jest obsługiwany przez dostawcę, o ile określono konwerter wartości dla właściwości tego typu. Jednak w zapytaniach, które nie obejmują żadnych właściwości tego typu, nie ma możliwości znalezienia poprawnego konwertera wartości przez program EF. W takim przypadku można wywołać metodę DefaultTypeMapping dodawania lub zastępowania mapowania typu dostawcy:

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

Ograniczenia konfiguracji przed konwencją

  • Nie można skonfigurować wielu aspektów przy użyciu tego podejścia. #6787 rozszerzy ten typ na więcej typów.
  • Obecnie konfiguracja jest określana tylko przez typ CLR. #20418 zezwala na niestandardowe predykaty.
  • Ta konfiguracja jest wykonywana przed utworzeniem modelu. Jeśli podczas stosowania wystąpią jakiekolwiek konflikty, ślad stosu wyjątków nie będzie zawierać ConfigureConventions metody, więc znalezienie przyczyny może być trudniejsze.

Konwencje

Uwaga

Niestandardowe konwencje tworzenia modeli zostały wprowadzone w programie EF Core 7.0.

Konwencje tworzenia modeli platformy EF Core to klasy zawierające logikę wyzwalaną na podstawie zmian wprowadzonych w modelu podczas jego tworzenia. Dzięki temu model jest aktualny w miarę tworzenia jawnej konfiguracji, są stosowane atrybuty mapowania i są uruchamiane inne konwencje. Aby wziąć udział w tym procesie, każda konwencja implementuje jeden lub więcej interfejsów, które określają, kiedy zostanie wyzwolona odpowiednia metoda. Na przykład konwencja implementowana zostanie wyzwolona IEntityTypeAddedConvention za każdym razem, gdy nowy typ jednostki zostanie dodany do modelu. Podobnie konwencja implementowana zarówno, IForeignKeyAddedConvention jak i IKeyAddedConvention zostanie wyzwolona za każdym razem, gdy do modelu zostanie dodany klucz lub klucz obcy.

Konwencje tworzenia modeli to zaawansowany sposób kontrolowania konfiguracji modelu, ale może być złożony i trudny do uzyskania właściwego rozwiązania. W wielu przypadkach można użyć konfiguracji modelu przed konwencją, aby łatwo określić wspólną konfigurację właściwości i typów.

Dodawanie nowej konwencji

Przykład: ograniczenie długości właściwości dyskryminujących

Strategia mapowania dziedziczenia tabeli na hierarchię wymaga dyskryminującej kolumny w celu określenia, który typ jest reprezentowany w danym wierszu. Domyślnie program EF używa niezwiązanej kolumny ciągu dla dyskryminującego, co gwarantuje, że będzie działać dla dowolnej długości dyskryminującej. Ograniczenie maksymalnej długości ciągów dyskryminujących może jednak zwiększyć wydajność magazynowania i zapytań. Utwórzmy nową konwencję, która to zrobi.

Konwencje tworzenia modeli platformy EF Core są wyzwalane na podstawie zmian wprowadzonych w modelu podczas jego tworzenia. Dzięki temu model jest aktualny w miarę tworzenia jawnej konfiguracji, są stosowane atrybuty mapowania i są uruchamiane inne konwencje. Aby wziąć udział w tym procesie, każda konwencja implementuje jeden lub więcej interfejsów, które określają, kiedy konwencja zostanie wyzwolona. Na przykład konwencja implementowana zostanie wyzwolona IEntityTypeAddedConvention za każdym razem, gdy nowy typ jednostki zostanie dodany do modelu. Podobnie konwencja implementowana zarówno, IForeignKeyAddedConvention jak i IKeyAddedConvention zostanie wyzwolona za każdym razem, gdy do modelu zostanie dodany klucz lub klucz obcy.

Znajomość interfejsów do zaimplementowania może być trudna, ponieważ konfiguracja w danym momencie może zostać zmieniona lub usunięta w późniejszym momencie. Na przykład klucz może zostać utworzony zgodnie z konwencją, ale później zastąpiony podczas jawnego konfigurowania innego klucza.

Zróbmy to nieco bardziej konkretnie, podejmując pierwszą próbę wdrożenia konwencji o długości dyskryminującej:

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

Ta konwencja implementuje IEntityTypeBaseTypeChangedConventionelement , co oznacza, że zostanie wyzwolony przy każdej zmianie zamapowanej hierarchii dziedziczenia dla typu jednostki. Następnie konwencja znajduje i konfiguruje właściwość dyskryminującą ciąg dla hierarchii.

Ta konwencja jest następnie używana przez wywołanie metody Add w pliku ConfigureConventions:

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

Uwaga

Zamiast bezpośrednio dodawać wystąpienie konwencji, Add metoda akceptuje fabrykę do tworzenia wystąpień konwencji. Dzięki temu konwencja może używać zależności od wewnętrznego dostawcy usług EF Core. Ponieważ ta konwencja nie ma zależności, parametr dostawcy usług ma nazwę _, wskazując, że nigdy nie jest używany.

Utworzenie modelu i przyjrzenie się typowi Post jednostki pokazuje, że to zadziałało — właściwość dyskryminująca jest teraz skonfigurowana do maksymalnej długości 24:

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

Ale co się stanie, jeśli teraz jawnie skonfigurujemy inną właściwość dyskryminującą? Przykład:

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

Patrząc na widok debugowania modelu, uważamy, że długość dyskryminująca nie jest już skonfigurowana.

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

Wynika to z faktu, że właściwość dyskryminująca skonfigurowana w naszej konwencji została później usunięta po dodaniu niestandardowego dyskryminatora. Moglibyśmy spróbować rozwiązać ten problem, wdrażając inny interfejs w naszej konwencji, aby reagować na zmiany dyskryminujące, ale ustalenie, który interfejs do zaimplementowania nie jest łatwy.

Na szczęście istnieje łatwiejsze podejście. Wiele czasu nie ma znaczenia, jak wygląda model podczas kompilowania, o ile ostateczny model jest poprawny. Ponadto konfiguracja, którą chcemy zastosować, często nie musi wyzwalać innych konwencji, aby reagować. W związku z tym nasza konwencja może implementować IModelFinalizingConvention. Konwencje finalizacji modelu są uruchamiane po zakończeniu wszystkich innych kompilacji modeli i mają dostęp do niemal końcowego stanu modelu. Jest to sprzeczne z konwencjami interaktywnymi , które reagują na każdą zmianę modelu i upewnij się, że model jest aktualny w dowolnym momencie OnModelCreating wykonywania metody. Konwencja finalizowania modelu zwykle iteruje cały model konfigurujący elementy modelu w miarę jego działania. W tym przypadku znajdziemy więc każdy dyskryminator w modelu i skonfigurujemy go:

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

Po utworzeniu modelu z tą nową konwencją okazuje się, że długość dyskryminująca jest teraz poprawnie skonfigurowana, mimo że została ona dostosowana:

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

Możemy pójść o krok dalej i skonfigurować maksymalną długość najdłuższej wartości dyskryminującej:

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

Teraz maksymalna długość kolumny dyskryminującej wynosi 8, czyli długość "Polecane", najdłuższa wartość dyskryminująca w użyciu.

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

Przykład: domyślna długość wszystkich właściwości ciągu

Przyjrzyjmy się innemu przykładowi, w którym można użyć konwencji finalizowania — ustawiając domyślną maksymalną długość dowolnej właściwości ciągu. Konwencja wygląda podobnie do poprzedniego przykładu:

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

Ta konwencja jest dość prosta. Znajduje on każdą właściwość ciągu w modelu i ustawia maksymalną długość na 512. Patrząc w widoku debugowania we właściwościach Postelementu , widzimy, że wszystkie właściwości ciągu mają teraz maksymalną długość 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)

Uwaga

Można to zrobić za pomocą konfiguracji przed konwencją, ale użycie konwencji umożliwia dalsze filtrowanie odpowiednich właściwości i adnotacji danych w celu zastąpienia konfiguracji.

Na koniec, zanim opuścimy ten przykład, co się stanie, jeśli używamy obu elementów MaxStringLengthConvention i DiscriminatorLengthConvention3 w tym samym czasie? Odpowiedź polega na tym, że zależy od kolejności ich dodania, ponieważ konwencje finalizowania modelu są uruchamiane w kolejności ich dodawania. Więc jeśli MaxStringLengthConvention zostanie dodany ostatnio, będzie on uruchamiany jako ostatni, i ustawi maksymalną długość właściwości dyskryminującej na 512. W związku z tym w tym przypadku lepiej jest dodać DiscriminatorLengthConvention3 ostatnio, aby można było zastąpić domyślną maksymalną długość tylko właściwości dyskryminujących, pozostawiając wszystkie inne właściwości ciągu jako 512.

Zastępowanie istniejącej konwencji

Czasami zamiast całkowicie usuwać istniejącą konwencję, zamiast tego chcemy zastąpić ją konwencją, która zasadniczo robi to samo, ale ze zmienionym zachowaniem. Jest to przydatne, ponieważ istniejąca konwencja będzie już implementować interfejsy, które muszą zostać odpowiednio wyzwolone.

Przykład: mapowanie właściwości opt-in

Program EF Core mapuje wszystkie publiczne właściwości odczytu i zapisu według konwencji. Może to nie być odpowiednie dla sposobu definiowania typów jednostek. Aby to zmienić, możemy zastąpić PropertyDiscoveryConvention element własnym implementacją, która nie mapuje żadnej właściwości, chyba że jest jawnie zamapowana lub OnModelCreating oznaczona nowym atrybutem o nazwie Persist:

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

Oto nowa konwencja:

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

Napiwek

Podczas zastępowania wbudowanej konwencji nowa implementacja konwencji powinna dziedziczyć z istniejącej klasy konwencji. Należy pamiętać, że niektóre konwencje mają implementacje specyficzne dla relacyjnych lub dostawcy, w tym przypadku nowa implementacja konwencji powinna dziedziczyć z najbardziej konkretnej istniejącej klasy konwencji dla dostawcy bazy danych w użyciu.

Konwencja jest następnie rejestrowana przy użyciu metody w pliku ReplaceConfigureConventions:

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

Napiwek

Jest to przypadek, w którym istniejąca konwencja ma zależności reprezentowane przez ProviderConventionSetBuilderDependencies obiekt zależności. Są one uzyskiwane od wewnętrznego dostawcy usług przy użyciu GetRequiredService i przekazywane do konstruktora konwencji.

Należy zauważyć, że ta konwencja umożliwia mapowanie pól (oprócz właściwości), o ile są one oznaczone znakiem [Persist]. Oznacza to, że możemy używać pól prywatnych jako ukrytych kluczy w modelu.

Rozważmy na przykład następujące typy jednostek:

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

Model utworzony na podstawie tych typów jednostek to:

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

Zwykle mapowanoby wartość , IsClean ale ponieważ nie jest ona oznaczona za pomocą [Persist]polecenia , jest teraz traktowana jako właściwość niemapowana.

Napiwek

Nie można zaimplementować tej konwencji jako konwencji finalizowania modelu, ponieważ istnieją konwencje finalizowania modelu, które należy uruchomić po zamapowaniu właściwości w celu jej dalszej konfiguracji.

Zagadnienia dotyczące implementacji konwencji

Program EF Core śledzi, jak każda konfiguracja została wykonana. Jest to reprezentowane przez wyliczenie ConfigurationSource . Różne rodzaje konfiguracji to:

  • Explicit: Element modelu został jawnie skonfigurowany w programie OnModelCreating
  • DataAnnotation: Element modelu został skonfigurowany przy użyciu atrybutu mapowania (adnotacji danych) w typie CLR
  • Convention: Element modelu został skonfigurowany zgodnie z konwencją tworzenia modelu

Konwencje nigdy nie powinny zastępować konfiguracji oznaczonej jako DataAnnotation lub Explicit. Jest to osiągane przy użyciu konstruktora konwencji, na przykład IConventionPropertyBuilder, który jest uzyskiwany z Builder właściwości . Przykład:

property.Builder.HasMaxLength(512);

Wywołanie metody HasMaxLength na konstruktorze konwencji spowoduje ustawienie maksymalnej długości tylko wtedy, gdy nie został jeszcze skonfigurowany przez atrybut mapowania lub w .OnModelCreating

Metody konstruktora takie jak ten mają również drugi parametr: fromDataAnnotation. Ustaw tę wartość na true wartość , jeśli konwencja tworzy konfigurację w imieniu atrybutu mapowania. Przykład:

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

Spowoduje to ustawienie ConfigurationSource wartości na DataAnnotation, co oznacza, że wartość może być teraz zastępowana przez jawne mapowanie na OnModelCreating, ale nie przez konwencje atrybutów niemapowania.

Jeśli nie można zastąpić bieżącej konfiguracji, metoda zwróci nullwartość , należy ją uwzględnić, jeśli musisz wykonać dalszą konfigurację:

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

Zwróć uwagę, że jeśli nie można zastąpić konfiguracji unicode, maksymalna długość będzie nadal ustawiona. W przypadku, gdy trzeba skonfigurować aspekty tylko wtedy, gdy oba wywołania kończą się powodzeniem, można to wstępnie sprawdzić, wywołując CanSetMaxLength metodę i 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);
            }
        }
    }
}

W tym miejscu możemy mieć pewność, że wywołanie HasMaxLength metody nie zwróci nullwartości . Nadal zaleca się używanie wystąpienia konstruktora zwróconego z HasMaxLength programu , ponieważ może się różnić od propertyBuilder.

Uwaga

Inne konwencje nie są wyzwalane natychmiast po wprowadzeniu zmiany konwencji, są opóźnione do momentu zakończenia przetwarzania bieżącej zmiany przez wszystkie konwencje.

IConventionContext

Wszystkie metody konwencji mają IConventionContext<TMetadata> również parametr . Udostępnia metody, które mogą być przydatne w niektórych konkretnych przypadkach.

Przykład: Konwencja NotMappedAttribute

Ta konwencja szuka NotMappedAttribute typu dodanego do modelu i próbuje usunąć ten typ jednostki z modelu. Jeśli jednak typ jednostki zostanie usunięty z modelu, wszystkie inne konwencje, które implementują ProcessEntityTypeAdded , nie muszą już być uruchamiane. Można to osiągnąć, wywołując polecenie 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

Każdy obiekt konstruktora przekazany do konwencji uwidacznia Metadata właściwość, która zapewnia dostęp niskiego poziomu do obiektów składających się na model. W szczególności istnieją metody, które pozwalają iterować nad określonymi obiektami w modelu i stosować do nich wspólną konfigurację, jak pokazano w przykładzie: Domyślna długość wszystkich właściwości ciągu. Ten interfejs API jest podobny do IMutableModel pokazanego w konfiguracji zbiorczej.

Uwaga

Zaleca się zawsze wykonywanie konfiguracji przez wywołanie metod na konstruktorze uwidocznionych jako Builder właściwość, ponieważ konstruktorzy sprawdzają, czy dana konfiguracja zastąpi coś, co zostało już określone przy użyciu interfejsu API Fluent lub adnotacji danych.

Kiedy należy używać każdego podejścia do konfiguracji zbiorczej

Użyj interfejsu API metadanych, gdy:

  • Konfiguracja musi być stosowana w określonym czasie i nie reagować na późniejsze zmiany w modelu.
  • Szybkość tworzenia modelu jest bardzo ważna. Interfejs API metadanych ma mniej kontroli bezpieczeństwa i dlatego może być nieco szybszy niż inne podejścia, jednak użycie skompilowanego modelu dałoby jeszcze lepsze czasy uruchamiania.

Użyj konfiguracji modelu przed konwencją, gdy:

  • Warunek stosowania jest prosty, ponieważ zależy tylko od typu.
  • Konfiguracja musi być stosowana w dowolnym punkcie, a właściwość danego typu jest dodawana w modelu i zastępuje adnotacje i konwencje danych

Użyj konwencji finalizowania, gdy:

  • Warunek stosowania jest złożony.
  • Konfiguracja nie powinna zastępować elementów określonych przez adnotacje danych.

Użyj konwencji interakcyjnych, gdy:

  • Wiele konwencji zależy od siebie. Finalizowanie konwencji jest uruchamiane w kolejności ich dodania i dlatego nie może reagować na zmiany wprowadzone przez późniejsze finalizowanie konwencji.
  • Logika jest współdzielona między kilkoma kontekstami. Konwencje interakcyjne są bezpieczniejsze niż inne podejścia.