Konfigurasi massal model

Ketika aspek perlu dikonfigurasi dengan cara yang sama di beberapa jenis entitas, teknik berikut memungkinkan untuk mengurangi duplikasi kode dan mengonsolidasikan logika.

Lihat proyek sampel lengkap yang berisi cuplikan kode yang disajikan di bawah ini.

Konfigurasi massal di OnModelCreating

Setiap objek penyusun yang dikembalikan dari mengekspos Model properti atau Metadata yang menyediakan akses tingkat rendah ke objek yang terdiri dari ModelBuilder model. Secara khusus, ada metode yang memungkinkan Anda melakukan iterasi atas objek tertentu dalam model dan menerapkan konfigurasi umum padanya.

Dalam contoh berikut, model berisi jenis Currencynilai kustom :

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

    public decimal Amount { get; }

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

Properti jenis ini tidak ditemukan secara default karena penyedia EF saat ini tidak tahu cara memetakannya ke jenis database. Cuplikan OnModelCreating ini menambahkan semua properti jenis Currency dan mengonfigurasi pengonversi nilai ke jenis yang didukung - 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))
    {
    }
}

Kelemahan API Metadata

  • Tidak seperti Fluent API, setiap modifikasi pada model perlu dilakukan secara eksplisit. Misalnya, jika beberapa Currency properti dikonfigurasi sebagai navigasi oleh konvensi maka Anda harus terlebih dahulu menghapus navigasi yang merujuk properti CLR sebelum menambahkan properti jenis entitas untuk properti tersebut. #9117 akan meningkatkan ini.
  • Konvensi berjalan setelah setiap perubahan. Jika Anda menghapus navigasi yang ditemukan oleh konvensi, maka konvensi akan berjalan lagi dan dapat menambahkannya kembali. Untuk mencegah hal ini terjadi, Anda harus menunda konvensi hingga setelah properti ditambahkan dengan memanggil DelayConventions() dan kemudian membuang objek yang dikembalikan atau untuk menandai properti CLR sebagai diabaikan menggunakan AddIgnored.
  • Jenis entitas mungkin ditambahkan setelah perulangan ini terjadi dan konfigurasi tidak akan diterapkan padanya. Ini biasanya dapat dicegah dengan menempatkan kode ini di akhir OnModelCreating, tetapi jika Anda memiliki dua set konfigurasi yang saling bergantung mungkin tidak ada urutan yang akan memungkinkan mereka diterapkan secara konsisten.

Konfigurasi pra-konvensi

EF Core memungkinkan konfigurasi pemetaan ditentukan sekali untuk jenis CLR tertentu; konfigurasi tersebut kemudian diterapkan ke semua properti jenis tersebut dalam model saat ditemukan. Ini disebut "konfigurasi model pra-konvensi", karena mengonfigurasi aspek model sebelum konvensi pembuatan model diizinkan untuk dijalankan. Konfigurasi tersebut diterapkan dengan mengesampingkan ConfigureConventions jenis yang berasal dari DbContext.

Contoh ini menunjukkan cara mengonfigurasi semua properti jenis Currency untuk memiliki pengonversi nilai:

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

Dan contoh ini menunjukkan cara mengonfigurasi beberapa faset pada semua properti jenis string:

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

Catatan

Jenis yang ditentukan dalam panggilan dari ConfigureConventions dapat berupa jenis dasar, antarmuka, atau definisi jenis generik. Semua konfigurasi yang cocok akan diterapkan secara berurutan dari yang paling tidak spesifik:

  1. Antarmuka
  2. Jenis dasar
  3. Definisi jenis generik
  4. Jenis nilai yang tidak dapat diubah ke null
  5. Jenis yang tepat

Penting

Konfigurasi pra-konvensi setara dengan konfigurasi eksplisit yang diterapkan segera setelah objek yang cocok ditambahkan ke model. Ini akan mengambil alih semua konvensi dan Anotasi Data. Misalnya, dengan konfigurasi di atas semua properti kunci asing string akan dibuat sebagai non-unicode dengan 1024, bahkan ketika ini tidak cocok dengan MaxLength kunci utama.

Mengabaikan jenis

Konfigurasi pra-konvensi juga memungkinkan untuk mengabaikan jenis dan mencegahnya ditemukan oleh konvensi baik sebagai jenis entitas atau sebagai properti pada jenis entitas:

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

Pemetaan jenis default

Umumnya, EF dapat menerjemahkan kueri dengan konstanta jenis yang tidak didukung oleh penyedia, selama Anda telah menentukan pengonversi nilai untuk properti jenis ini. Namun, dalam kueri yang tidak melibatkan properti apa pun dari jenis ini, tidak ada cara bagi EF untuk menemukan pengonversi nilai yang benar. Dalam hal ini, dimungkinkan untuk memanggil DefaultTypeMapping untuk menambahkan atau mengambil alih pemetaan jenis penyedia:

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

Batasan konfigurasi pra-konvensi

  • Banyak aspek tidak dapat dikonfigurasi dengan pendekatan ini. #6787 akan memperluas ini ke lebih banyak jenis.
  • Saat ini konfigurasi hanya ditentukan oleh jenis CLR. #20418 akan memungkinkan predikat kustom.
  • Konfigurasi ini dilakukan sebelum model dibuat. Jika ada konflik yang muncul saat menerapkannya, jejak tumpukan pengecualian tidak akan berisi ConfigureConventions metode , sehingga mungkin lebih sulit untuk menemukan penyebabnya.

Konvensi

Catatan

Konvensi pembuatan model kustom diperkenalkan dalam EF Core 7.0.

Konvensi pembuatan model EF Core adalah kelas yang berisi logika yang dipicu berdasarkan perubahan yang dilakukan pada model saat sedang dibangun. Ini membuat model tetap terbaru saat konfigurasi eksplisit dibuat, atribut pemetaan diterapkan, dan konvensi lainnya berjalan. Untuk berpartisipasi dalam hal ini, setiap konvensi mengimplementasikan satu atau beberapa antarmuka yang menentukan kapan metode yang sesuai akan dipicu. Misalnya, konvensi yang menerapkan IEntityTypeAddedConvention akan dipicu setiap kali jenis entitas baru ditambahkan ke model. Demikian juga, konvensi yang mengimplementasikan keduanya IForeignKeyAddedConvention dan IKeyAddedConvention akan dipicu setiap kali kunci atau kunci asing ditambahkan ke model.

Konvensi pembuatan model adalah cara yang ampuh untuk mengontrol konfigurasi model, tetapi bisa kompleks dan sulit untuk disempurnakan. Dalam banyak kasus, konfigurasi model pra-konvensi dapat digunakan sebagai gantinya untuk dengan mudah menentukan konfigurasi umum untuk properti dan jenis.

Menambahkan konvensi baru

Contoh: Batasi panjang properti diskriminator

Strategi pemetaan warisan tabel per hierarki memerlukan kolom diskriminator untuk menentukan jenis mana yang diwakili dalam baris tertentu. Secara default, EF menggunakan kolom string yang tidak terbatas untuk diskriminator, yang memastikan bahwa itu akan berfungsi untuk panjang diskriminator apa pun. Namun, membatasi panjang maksimum string diskriminator dapat membuat penyimpanan dan kueri yang lebih efisien. Mari kita buat konvensi baru yang akan melakukan itu.

Konvensi pembuatan model EF Core dipicu berdasarkan perubahan yang dilakukan pada model saat sedang dibangun. Ini membuat model tetap terbaru saat konfigurasi eksplisit dibuat, atribut pemetaan diterapkan, dan konvensi lainnya berjalan. Untuk berpartisipasi dalam hal ini, setiap konvensi mengimplementasikan satu atau beberapa antarmuka yang menentukan kapan konvensi akan dipicu. Misalnya, konvensi yang menerapkan IEntityTypeAddedConvention akan dipicu setiap kali jenis entitas baru ditambahkan ke model. Demikian juga, konvensi yang mengimplementasikan keduanya IForeignKeyAddedConvention dan IKeyAddedConvention akan dipicu setiap kali kunci atau kunci asing ditambahkan ke model.

Mengetahui antarmuka mana yang akan diterapkan bisa sulit, karena konfigurasi yang dibuat untuk model pada satu titik dapat diubah atau dihapus di titik selanjutnya. Misalnya, kunci dapat dibuat oleh konvensi, tetapi kemudian diganti ketika kunci yang berbeda dikonfigurasi secara eksplisit.

Mari kita buat ini sedikit lebih konkret dengan melakukan upaya pertama untuk menerapkan konvensi panjang diskriminator:

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

Konvensi ini menerapkan IEntityTypeBaseTypeChangedConvention, yang berarti akan dipicu setiap kali hierarki warisan yang dipetakan untuk jenis entitas diubah. Konvensi kemudian menemukan dan mengonfigurasi properti diskriminator string untuk hierarki.

Konvensi ini kemudian digunakan dengan memanggil Add di ConfigureConventions:

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

Catatan

Daripada menambahkan instans konvensi secara langsung, Add metode ini menerima pabrik untuk membuat instans konvensi. Ini memungkinkan konvensi untuk menggunakan dependensi dari penyedia layanan internal EF Core. Karena konvensi ini tidak memiliki dependensi, parameter penyedia layanan diberi nama _, menunjukkan bahwa itu tidak pernah digunakan.

Membangun model dan melihat jenis entitas menunjukkan bahwa ini telah berfungsi - properti diskriminator sekarang dikonfigurasi Post ke dengan panjang maksimum 24:

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

Tetapi apa yang terjadi jika kita sekarang secara eksplisit mengonfigurasi properti diskriminator yang berbeda? Contohnya:

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

Melihat tampilan debug model, kami menemukan bahwa panjang diskriminator tidak lagi dikonfigurasi.

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

Ini karena properti diskriminator yang kami konfigurasi dalam konvensi kami kemudian dihapus ketika diskriminator kustom ditambahkan. Kami dapat mencoba memperbaikinya dengan menerapkan antarmuka lain pada konvensi kami untuk bereaksi terhadap perubahan diskriminator, tetapi mencari tahu antarmuka mana yang akan diterapkan tidak mudah.

Untungnya, ada pendekatan yang lebih mudah. Banyak waktu, tidak peduli seperti apa model saat sedang dibangun, selama model akhir benar. Selain itu, konfigurasi yang ingin kita terapkan sering kali tidak perlu memicu konvensi lain untuk bereaksi. Oleh karena itu, konvensi kami dapat menerapkan IModelFinalizingConvention. Konvensi finalisasi model berjalan setelah semua bangunan model lainnya selesai, dan begitu juga memiliki akses ke status model mendekati akhir. Ini berlawanan dengan konvensi interaktif yang bereaksi terhadap setiap perubahan model dan memastikan bahwa model diperbarui pada titik mana pun dari OnModelCreating eksekusi metode. Konvensi finalisasi model biasanya akan mengulangi seluruh elemen model yang mengonfigurasi model saat berjalan. Jadi, dalam hal ini, kita akan menemukan setiap diskriminator dalam model dan mengonfigurasinya:

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

Setelah membangun model dengan konvensi baru ini, kami menemukan bahwa panjang diskriminator sekarang dikonfigurasi dengan benar meskipun telah disesuaikan:

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

Kita dapat melangkah lebih jauh dan mengonfigurasi panjang maksimum menjadi panjang nilai diskriminator terpanjang:

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

Sekarang panjang maks kolom diskriminator adalah 8, yang merupakan panjang "Unggulan", nilai diskriminator terpanjang yang digunakan.

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

Contoh: Panjang default untuk semua properti string

Mari kita lihat contoh lain di mana konvensi finalisasi dapat digunakan - mengatur panjang maksimum default untuk properti string apa pun . Konvensi ini terlihat sangat mirip dengan contoh sebelumnya:

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

Konvensi ini cukup sederhana. Ini menemukan setiap properti string dalam model dan mengatur panjang maksimumnya menjadi 512. Melihat tampilan debug di properti untuk Post, kita melihat bahwa semua properti string sekarang memiliki panjang maksimum 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)

Catatan

Hal yang sama dapat dicapai dengan konfigurasi pra-konvensi, tetapi menggunakan konvensi memungkinkan untuk memfilter properti yang berlaku lebih lanjut dan untuk Anotasi Data untuk mengambil alih konfigurasi.

Akhirnya, sebelum kita meninggalkan contoh ini, apa yang terjadi jika kita menggunakan dan MaxStringLengthConventionDiscriminatorLengthConvention3 pada saat yang sama? Jawabannya adalah bahwa itu tergantung urutan mana yang ditambahkan, karena model menyelesaikan konvensi berjalan dalam urutan ditambahkan. Jadi jika MaxStringLengthConvention ditambahkan terakhir, maka akan berjalan terakhir, dan akan mengatur panjang maksimum properti diskriminator ke 512. Oleh karena itu, dalam hal ini, lebih baik menambahkan DiscriminatorLengthConvention3 yang terakhir sehingga dapat mengambil alih panjang maks default hanya untuk properti diskriminator, sambil meninggalkan semua properti string lainnya sebagai 512.

Mengganti konvensi yang ada

Terkadang daripada menghapus konvensi yang ada sepenuhnya, kita malah ingin menggantinya dengan konvensi yang pada dasarnya melakukan hal yang sama, tetapi dengan perilaku yang berubah. Ini berguna karena konvensi yang ada sudah akan mengimplementasikan antarmuka yang perlu dipicu dengan tepat.

Contoh: Pemetaan properti keikutsertaan

EF Core memetakan semua properti baca-tulis publik berdasarkan konvensi. Ini mungkin tidak sesuai dengan cara jenis entitas Anda ditentukan. Untuk mengubah ini, kita dapat mengganti PropertyDiscoveryConvention dengan implementasi kita sendiri yang tidak memetakan properti apa pun kecuali secara eksplisit dipetakan atau OnModelCreating ditandai dengan atribut baru yang disebut Persist:

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

Berikut adalah konvensi baru:

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

Tip

Saat mengganti konvensi bawaan, implementasi konvensi baru harus diwarisi dari kelas konvensi yang ada. Perhatikan bahwa beberapa konvensi memiliki implementasi relasional atau khusus penyedia, dalam hal ini implementasi konvensi baru harus mewarisi dari kelas konvensi yang paling spesifik yang ada untuk penyedia database yang digunakan.

Konvensi kemudian didaftarkan menggunakan Replace metode dalam ConfigureConventions:

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

Tip

Ini adalah kasus di mana konvensi yang ada memiliki dependensi, yang diwakili oleh ProviderConventionSetBuilderDependencies objek dependensi. Ini diperoleh dari penyedia layanan internal menggunakan GetRequiredService dan diteruskan ke konstruktor konvensi.

Perhatikan bahwa konvensi ini memungkinkan bidang dipetakan (selain properti) selama ditandai dengan [Persist]. Ini berarti kita dapat menggunakan bidang privat sebagai kunci tersembunyi dalam model.

Misalnya, pertimbangkan jenis entitas berikut:

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 yang dibangun dari jenis entitas ini adalah:

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

Biasanya, IsClean akan dipetakan, tetapi karena tidak ditandai dengan [Persist], sekarang diperlakukan sebagai properti yang tidak dipetakan.

Tip

Konvensi ini tidak dapat diimplementasikan sebagai konvensi finalisasi model karena ada konvensi finalisasi model yang perlu dijalankan setelah properti dipetakan untuk mengonfigurasinya lebih lanjut.

Pertimbangan implementasi konvensi

EF Core melacak bagaimana setiap bagian konfigurasi dibuat. Ini diwakili oleh ConfigurationSource enum. Jenis konfigurasi yang berbeda adalah:

  • Explicit: Elemen model dikonfigurasi secara eksplisit di OnModelCreating
  • DataAnnotation: Elemen model dikonfigurasi menggunakan atribut pemetaan (alias anotasi data) pada jenis CLR
  • Convention: Elemen model dikonfigurasi oleh konvensi pembuatan model

Konvensi tidak boleh mengambil alih konfigurasi yang ditandai sebagai DataAnnotation atau Explicit. Ini dicapai dengan menggunakan penyusun konvensi, misalnya, IConventionPropertyBuilder, yang diperoleh dari Builder properti . Contohnya:

property.Builder.HasMaxLength(512);

Panggilan HasMaxLength pada penyusun konvensi hanya akan mengatur panjang maksimum jika belum dikonfigurasi oleh atribut pemetaan atau di OnModelCreating.

Metode penyusun seperti ini juga memiliki parameter kedua: fromDataAnnotation. Atur ini ke true jika konvensi membuat konfigurasi atas nama atribut pemetaan. Contohnya:

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

Ini mengatur ConfigurationSource ke DataAnnotation, yang berarti bahwa nilai sekarang dapat ditimpa oleh pemetaan eksplisit pada OnModelCreating, tetapi tidak dengan konvensi atribut non-pemetaan.

Jika konfigurasi saat ini tidak dapat ditimpa maka metode akan mengembalikan null, ini perlu diperhitungkan jika Anda perlu melakukan konfigurasi lebih lanjut:

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

Perhatikan bahwa jika konfigurasi unicode tidak dapat ditimpa, panjang maksimum masih akan diatur. Jika Anda perlu mengonfigurasi faset hanya ketika kedua panggilan berhasil, Maka Anda dapat memeriksanya secara preemptive dengan memanggil CanSetMaxLength dan 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);
            }
        }
    }
}

Di sini kita dapat yakin bahwa panggilan ke HasMaxLength tidak akan kembali null. Masih disarankan untuk menggunakan instans penyusun yang dikembalikan karena mungkin berbeda dari HasMaxLengthpropertyBuilder.

Catatan

Konvensi lain tidak dipicu segera setelah konvensi membuat perubahan, mereka tertunda sampai semua konvensi selesai memproses perubahan saat ini.

IConventionContext

Semua metode konvensi juga memiliki IConventionContext<TMetadata> parameter . Ini menyediakan metode yang dapat berguna dalam beberapa kasus tertentu.

Contoh: Konvensi NotMappedAttribute

Konvensi ini mencari NotMappedAttribute jenis yang ditambahkan ke model dan mencoba menghapus jenis entitas tersebut dari model. Tetapi jika jenis entitas dihapus dari model, maka konvensi lain yang menerapkan ProcessEntityTypeAdded tidak perlu lagi dijalankan. Ini dapat dicapai dengan memanggil 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

Setiap objek penyusun yang diteruskan ke konvensi mengekspos Metadata properti yang menyediakan akses tingkat rendah ke objek yang terdiri dari model. Secara khusus, ada metode yang memungkinkan Anda melakukan iterasi atas objek tertentu dalam model dan menerapkan konfigurasi umum seperti yang terlihat di Contoh: Panjang default untuk semua properti string. API ini mirip IMutableModel dengan yang ditampilkan dalam konfigurasi massal.

Perhatian

Disarankan untuk selalu melakukan konfigurasi dengan memanggil metode pada penyusun yang diekspos sebagai Builder properti, karena penyusun memeriksa apakah konfigurasi yang diberikan akan mengambil alih sesuatu yang sudah ditentukan menggunakan API Fasih atau Anotasi Data.

Kapan menggunakan setiap pendekatan untuk konfigurasi massal

Gunakan API Metadata saat:

  • Konfigurasi perlu diterapkan pada waktu tertentu dan tidak bereaksi terhadap perubahan model nanti.
  • Kecepatan membangun model sangat penting. API Metadata memiliki lebih sedikit pemeriksaan keamanan dan dengan demikian bisa sedikit lebih cepat daripada pendekatan lain, namun menggunakan model yang Dikompilasi akan menghasilkan waktu startup yang lebih baik.

Gunakan Konfigurasi model pra-konvensi saat:

  • Kondisi penerapannya sederhana karena hanya tergantung pada jenisnya.
  • Konfigurasi perlu diterapkan pada titik mana pun properti dari jenis yang diberikan ditambahkan dalam model dan mengambil alih Anotasi dan konvensi Data

Gunakan Finalisasi Konvensi saat:

  • Kondisi penerapannya kompleks.
  • Konfigurasi tidak boleh mengambil alih apa yang ditentukan oleh Anotasi Data.

Gunakan Konvensi Interaktif saat:

  • Beberapa konvensi bergantung satu sama lain. Menyelesaikan konvensi berjalan sesuai urutan penambahannya dan oleh karena itu tidak dapat bereaksi terhadap perubahan yang dilakukan dengan menyelesaikan konvensi nanti.
  • Logika dibagikan di antara beberapa konteks. Konvensi interaktif lebih aman daripada pendekatan lainnya.