Bagikan melalui


Konversi Nilai

Pengonversi nilai memungkinkan nilai properti dikonversi saat membaca dari atau menulis ke database. Konversi ini bisa dari satu nilai ke nilai lain dengan jenis yang sama (misalnya, mengenkripsi string) atau dari nilai satu jenis ke nilai jenis lain (misalnya, mengonversi nilai enum ke dan dari string dalam database.)

Tip

Anda dapat menjalankan dan men-debug ke semua kode dalam dokumen ini dengan mengunduh kode sampel dari GitHub.

Gambaran Umum

Pengonversi nilai ditentukan dalam hal ModelClrType dan ProviderClrType. Jenis model adalah jenis .NET properti dalam jenis entitas. Jenis penyedia adalah jenis .NET yang dipahami oleh penyedia database. Misalnya, untuk menyimpan enum sebagai string dalam database, jenis model adalah jenis enum, dan jenis penyedianya adalah String. Kedua jenis ini bisa sama.

Konversi didefinisikan menggunakan dua Func pohon ekspresi: satu dari ModelClrType ke ProviderClrType dan yang lainnya dari ProviderClrType ke ModelClrType. Pohon ekspresi digunakan sehingga dapat dikompilasi ke dalam delegasi akses database untuk konversi yang efisien. Pohon ekspresi mungkin berisi panggilan sederhana ke metode konversi untuk konversi yang kompleks.

Catatan

Properti yang telah dikonfigurasi untuk konversi nilai mungkin juga perlu menentukan ValueComparer<T>. Lihat contoh di bawah ini, dan dokumentasi Value Comparers untuk informasi selengkapnya.

Mengonfigurasi pengonversi nilai

Konversi nilai dikonfigurasi dalam DbContext.OnModelCreating. Misalnya, pertimbangkan enum dan jenis entitas yang didefinisikan sebagai:

public class Rider
{
    public int Id { get; set; }
    public EquineBeast Mount { get; set; }
}

public enum EquineBeast
{
    Donkey,
    Mule,
    Horse,
    Unicorn
}

Konversi dapat dikonfigurasi OnModelCreating untuk menyimpan nilai enum sebagai string seperti "Keledai", "Mule", dll. dalam database; Anda hanya perlu menyediakan satu fungsi yang mengonversi dari ModelClrType ke ProviderClrType, dan yang lain untuk konversi yang berlawanan:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder
        .Entity<Rider>()
        .Property(e => e.Mount)
        .HasConversion(
            v => v.ToString(),
            v => (EquineBeast)Enum.Parse(typeof(EquineBeast), v));
}

Catatan

Nilai null tidak akan pernah diteruskan ke pengonversi nilai. Null dalam kolom database selalu null dalam instans entitas, dan sebaliknya. Ini membuat implementasi konversi lebih mudah dan memungkinkannya dibagikan di antara properti nullable dan non-nullable. Lihat Masalah GitHub #13850 untuk informasi selengkapnya.

Mengonfigurasi pengonversi nilai secara massal

Umum untuk pengonversi nilai yang sama dikonfigurasi untuk setiap properti yang menggunakan jenis CLR yang relevan. Daripada melakukan ini secara manual untuk setiap properti, Anda dapat menggunakan konfigurasi model pra-konvensi untuk melakukan ini sekali untuk seluruh model Anda. Untuk melakukan ini, tentukan pengonversi nilai Anda sebagai kelas:

public class CurrencyConverter : ValueConverter<Currency, decimal>
{
    public CurrencyConverter()
        : base(
            v => v.Amount,
            v => new Currency(v))
    {
    }
}

Kemudian, ambil alih ConfigureConventions dalam jenis konteks Anda dan konfigurasikan pengonversi sebagai berikut:

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

Konversi yang telah ditentukan sebelumnya

EF Core berisi banyak konversi yang telah ditentukan sebelumnya yang menghindari kebutuhan untuk menulis fungsi konversi secara manual. Sebagai gantinya, EF Core akan memilih konversi yang akan digunakan berdasarkan jenis properti dalam model dan jenis penyedia database yang diminta.

Misalnya, enum ke konversi string digunakan sebagai contoh di atas, tetapi EF Core akan benar-benar melakukan ini secara otomatis ketika jenis penyedia dikonfigurasi sebagai string menggunakan jenis generik dari HasConversion:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder
        .Entity<Rider>()
        .Property(e => e.Mount)
        .HasConversion<string>();
}

Hal yang sama dapat dicapai dengan secara eksplisit menentukan jenis kolom database. Misalnya, jika jenis entitas didefinisikan seperti:

public class Rider2
{
    public int Id { get; set; }

    [Column(TypeName = "nvarchar(24)")]
    public EquineBeast Mount { get; set; }
}

Kemudian nilai enum akan disimpan sebagai string dalam database tanpa konfigurasi lebih lanjut di OnModelCreating.

Kelas ValueConverter

Panggilan seperti yang HasConversion ditunjukkan di atas akan membuat ValueConverter<TModel,TProvider> instans dan mengaturnya pada properti . sebagai gantinya ValueConverter dapat dibuat secara eksplisit. Contohnya:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    var converter = new ValueConverter<EquineBeast, string>(
        v => v.ToString(),
        v => (EquineBeast)Enum.Parse(typeof(EquineBeast), v));

    modelBuilder
        .Entity<Rider>()
        .Property(e => e.Mount)
        .HasConversion(converter);
}

Ini dapat berguna ketika beberapa properti menggunakan konversi yang sama.

Konverter bawaan

Seperti disebutkan di atas, EF Core dikirim dengan sekumpulan kelas yang telah ditentukan ValueConverter<TModel,TProvider> sebelumnya, yang ditemukan di Microsoft.EntityFrameworkCore.Storage.ValueConversion namespace layanan. Dalam banyak kasus EF akan memilih konverter bawaan yang sesuai berdasarkan jenis properti dalam model dan jenis yang diminta dalam database, seperti yang ditunjukkan di atas untuk enum. Misalnya, menggunakan .HasConversion<int>() pada bool properti akan menyebabkan EF Core mengonversi nilai bool menjadi nol numerik dan satu nilai:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder
        .Entity<User>()
        .Property(e => e.IsActive)
        .HasConversion<int>();
}

Ini secara fungsional sama dengan membuat instans bawaan BoolToZeroOneConverter<TProvider> dan mengaturnya secara eksplisit:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    var converter = new BoolToZeroOneConverter<int>();

    modelBuilder
        .Entity<User>()
        .Property(e => e.IsActive)
        .HasConversion(converter);
}

Tabel berikut ini merangkum konversi yang umum digunakan sebelumnya dari jenis model/properti ke jenis penyedia database. Dalam tabel any_numeric_type berarti salah satu dari int, , longshort, byte, uint, ushort, ulong, sbyte, char, decimal, float, atau double.

Jenis model/properti Jenis penyedia/database Konversi Penggunaan
bool any_numeric_type False/true ke 0/1 .HasConversion<any_numeric_type>()
any_numeric_type False/true ke dua angka apa pun Menggunakan BoolToTwoValuesConverter<TProvider>
string False/true ke "N"/"Y" .HasConversion<string>()
string False/true ke dua string apa pun Menggunakan BoolToStringConverter
any_numeric_type bool 0/1 ke false/true .HasConversion<bool>()
any_numeric_type Pemeran sederhana .HasConversion<any_numeric_type>()
string Angka sebagai string .HasConversion<string>()
Enum any_numeric_type Nilai numerik enum .HasConversion<any_numeric_type>()
string Representasi string dari nilai enum .HasConversion<string>()
string bool Mengurai string sebagai bool .HasConversion<bool>()
any_numeric_type Mengurai string sebagai jenis numerik yang diberikan .HasConversion<any_numeric_type>()
char Karakter pertama string .HasConversion<char>()
DateTime Mengurai string sebagai DateTime .HasConversion<DateTime>()
DateTimeOffset Mengurai string sebagai DateTimeOffset .HasConversion<DateTimeOffset>()
TimeSpan Mengurai string sebagai TimeSpan .HasConversion<TimeSpan>()
Guid Mengurai string sebagai Guid .HasConversion<Guid>()
byte[] String sebagai byte UTF8 .HasConversion<byte[]>()
char string String karakter tunggal .HasConversion<string>()
DateTime long Tanggal/waktu yang dikodekan mempertahankan DateTime.Kind .HasConversion<long>()
long Tanda Waktu Menggunakan DateTimeToTicksConverter
string String tanggal/waktu budaya invarian .HasConversion<string>()
DateTimeOffset long Tanggal/waktu yang dikodekan dengan offset .HasConversion<long>()
string String tanggal/waktu budaya yang invarian dengan offset .HasConversion<string>()
TimeSpan long Tanda Waktu .HasConversion<long>()
string String rentang waktu budaya invariant .HasConversion<string>()
Uri string URI sebagai string .HasConversion<string>()
PhysicalAddress string Alamat sebagai string .HasConversion<string>()
byte[] Byte dalam urutan jaringan big-endian .HasConversion<byte[]>()
IPAddress string Alamat sebagai string .HasConversion<string>()
byte[] Byte dalam urutan jaringan big-endian .HasConversion<byte[]>()
Guid string GUID dalam format 'ddddddd-dddd-dddd-dddd-ddddddddddddd' .HasConversion<string>()
byte[] Byte dalam urutan serialisasi biner .NET .HasConversion<byte[]>()

Perhatikan bahwa konversi ini mengasumsikan bahwa format nilai sesuai untuk konversi. Misalnya, mengonversi string menjadi angka akan gagal jika nilai string tidak dapat diurai sebagai angka.

Daftar lengkap konverter bawaan adalah:

Perhatikan bahwa semua konverter bawaan tanpa status dan sehingga satu instans dapat dibagikan dengan aman oleh beberapa properti.

Faset kolom dan petunjuk pemetaan

Beberapa jenis database memiliki faset yang mengubah cara data disimpan. Ini termasuk:

  • Presisi dan skala untuk kolom desimal dan tanggal/waktu
  • Ukuran/panjang untuk kolom biner dan string
  • Unicode untuk kolom string

Faset ini dapat dikonfigurasi dengan cara normal untuk properti yang menggunakan pengonversi nilai, dan akan berlaku untuk jenis database yang dikonversi. Misalnya, saat mengonversi dari enum ke string, kita dapat menentukan bahwa kolom database harus non-Unicode dan menyimpan hingga 20 karakter:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder
        .Entity<Rider>()
        .Property(e => e.Mount)
        .HasConversion<string>()
        .HasMaxLength(20)
        .IsUnicode(false);
}

Atau, saat membuat konverter secara eksplisit:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    var converter = new ValueConverter<EquineBeast, string>(
        v => v.ToString(),
        v => (EquineBeast)Enum.Parse(typeof(EquineBeast), v));

    modelBuilder
        .Entity<Rider>()
        .Property(e => e.Mount)
        .HasConversion(converter)
        .HasMaxLength(20)
        .IsUnicode(false);
}

Ini menghasilkan varchar(20) kolom saat menggunakan migrasi EF Core terhadap SQL Server:

CREATE TABLE [Rider] (
    [Id] int NOT NULL IDENTITY,
    [Mount] varchar(20) NOT NULL,
    CONSTRAINT [PK_Rider] PRIMARY KEY ([Id]));

Namun, jika secara default semua EquineBeast kolom harus varchar(20), maka informasi ini dapat diberikan kepada pengonversi nilai sebagai ConverterMappingHints. Contohnya:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    var converter = new ValueConverter<EquineBeast, string>(
        v => v.ToString(),
        v => (EquineBeast)Enum.Parse(typeof(EquineBeast), v),
        new ConverterMappingHints(size: 20, unicode: false));

    modelBuilder
        .Entity<Rider>()
        .Property(e => e.Mount)
        .HasConversion(converter);
}

Sekarang setiap kali konverter ini digunakan, kolom database akan menjadi non-unicode dengan panjang maksimum 20. Namun, ini hanya petunjuk karena mereka ditimpa oleh faset apa pun yang secara eksplisit diatur pada properti yang dipetakan.

Contoh

Objek nilai sederhana

Contoh ini menggunakan jenis sederhana untuk membungkus jenis primitif. Ini dapat berguna ketika Anda ingin jenis dalam model Anda lebih spesifik (dan karenanya lebih aman jenis) daripada jenis primitif. Dalam contoh ini, jenis tersebut adalah Dollars, yang membungkus primitif desimal:

public readonly struct Dollars
{
    public Dollars(decimal amount) 
        => Amount = amount;
        
    public decimal Amount { get; }

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

Ini dapat digunakan dalam jenis entitas:

public class Order
{
    public int Id { get; set; }

    public Dollars Price { get; set; }
}

Dan dikonversi ke yang mendasar decimal saat disimpan dalam database:

modelBuilder.Entity<Order>()
    .Property(e => e.Price)
    .HasConversion(
        v => v.Amount,
        v => new Dollars(v));

Catatan

Objek nilai ini diimplementasikan sebagai struktur baca-saja. Ini berarti bahwa EF Core dapat mengambil rekam jepret dan membandingkan nilai tanpa masalah. Lihat Pembanding Nilai untuk informasi selengkapnya.

Objek nilai komposit

Dalam contoh sebelumnya, jenis objek nilai hanya berisi satu properti. Lebih umum bagi jenis objek nilai untuk menyusun beberapa properti yang bersama-sama membentuk konsep domain. Misalnya, jenis umum Money yang berisi jumlah dan mata uang:

public readonly struct Money
{
    [JsonConstructor]
    public Money(decimal amount, Currency currency)
    {
        Amount = amount;
        Currency = currency;
    }

    public override string ToString()
        => (Currency == Currency.UsDollars ? "$" : "£") + Amount;

    public decimal Amount { get; }
    public Currency Currency { get; }
}

public enum Currency
{
    UsDollars,
    PoundsSterling
}

Objek nilai ini dapat digunakan dalam jenis entitas seperti sebelumnya:

public class Order
{
    public int Id { get; set; }

    public Money Price { get; set; }
}

Pengonversi nilai saat ini hanya dapat mengonversi nilai ke dan dari satu kolom database. Batasan ini berarti bahwa semua nilai properti dari objek harus dikodekan ke dalam satu nilai kolom. Ini biasanya ditangani dengan menserialisasikan objek saat masuk ke database, dan kemudian mendeserialisasinya lagi saat keluar. Misalnya, menggunakan System.Text.Json:

modelBuilder.Entity<Order>()
    .Property(e => e.Price)
    .HasConversion(
        v => JsonSerializer.Serialize(v, (JsonSerializerOptions)null),
        v => JsonSerializer.Deserialize<Money>(v, (JsonSerializerOptions)null));

Catatan

Kami berencana untuk mengizinkan pemetaan objek ke beberapa kolom dalam versi EF Core yang akan datang, menghapus kebutuhan untuk menggunakan serialisasi di sini. Ini dilacak oleh masalah GitHub #13947.

Catatan

Seperti contoh sebelumnya, objek nilai ini diimplementasikan sebagai struktur baca-saja. Ini berarti bahwa EF Core dapat mengambil rekam jepret dan membandingkan nilai tanpa masalah. Lihat Pembanding Nilai untuk informasi selengkapnya.

Kumpulan primitif

Serialisasi juga dapat digunakan untuk menyimpan kumpulan nilai primitif. Contohnya:

public class Post
{
    public int Id { get; set; }
    public string Title { get; set; }
    public string Contents { get; set; }

    public ICollection<string> Tags { get; set; }
}

Menggunakan System.Text.Json lagi:

modelBuilder.Entity<Post>()
    .Property(e => e.Tags)
    .HasConversion(
        v => JsonSerializer.Serialize(v, (JsonSerializerOptions)null),
        v => JsonSerializer.Deserialize<List<string>>(v, (JsonSerializerOptions)null),
        new ValueComparer<ICollection<string>>(
            (c1, c2) => c1.SequenceEqual(c2),
            c => c.Aggregate(0, (a, v) => HashCode.Combine(a, v.GetHashCode())),
            c => (ICollection<string>)c.ToList()));

ICollection<string> mewakili jenis referensi yang dapat diubah. Ini berarti bahwa ValueComparer<T> diperlukan agar EF Core dapat melacak dan mendeteksi perubahan dengan benar. Lihat Pembanding Nilai untuk informasi selengkapnya.

Kumpulan objek nilai

Menggabungkan dua contoh sebelumnya bersama-sama kita dapat membuat kumpulan objek nilai. Misalnya, pertimbangkan AnnualFinance jenis yang memodelkan keuangan blog selama satu tahun:

public readonly struct AnnualFinance
{
    [JsonConstructor]
    public AnnualFinance(int year, Money income, Money expenses)
    {
        Year = year;
        Income = income;
        Expenses = expenses;
    }

    public int Year { get; }
    public Money Income { get; }
    public Money Expenses { get; }
    public Money Revenue => new Money(Income.Amount - Expenses.Amount, Income.Currency);
}

Jenis ini menyusun beberapa jenis yang Money kami buat sebelumnya:

public readonly struct Money
{
    [JsonConstructor]
    public Money(decimal amount, Currency currency)
    {
        Amount = amount;
        Currency = currency;
    }

    public override string ToString()
        => (Currency == Currency.UsDollars ? "$" : "£") + Amount;

    public decimal Amount { get; }
    public Currency Currency { get; }
}

public enum Currency
{
    UsDollars,
    PoundsSterling
}

Kita kemudian dapat menambahkan kumpulan AnnualFinance ke jenis entitas kita:

public class Blog
{
    public int Id { get; set; }
    public string Name { get; set; }

    public IList<AnnualFinance> Finances { get; set; }
}

Dan sekali lagi gunakan serialisasi untuk menyimpan ini:

modelBuilder.Entity<Blog>()
    .Property(e => e.Finances)
    .HasConversion(
        v => JsonSerializer.Serialize(v, (JsonSerializerOptions)null),
        v => JsonSerializer.Deserialize<List<AnnualFinance>>(v, (JsonSerializerOptions)null),
        new ValueComparer<IList<AnnualFinance>>(
            (c1, c2) => c1.SequenceEqual(c2),
            c => c.Aggregate(0, (a, v) => HashCode.Combine(a, v.GetHashCode())),
            c => (IList<AnnualFinance>)c.ToList()));

Catatan

Seperti sebelumnya, konversi ini memerlukan ValueComparer<T>. Lihat Pembanding Nilai untuk informasi selengkapnya.

Objek nilai sebagai kunci

Terkadang properti kunci primitif dapat dibungkus dalam objek nilai untuk menambahkan tingkat keamanan jenis tambahan dalam menetapkan nilai. Misalnya, kita dapat menerapkan jenis kunci untuk blog, dan jenis kunci untuk posting:

public readonly struct BlogKey
{
    public BlogKey(int id) => Id = id;
    public int Id { get; }
}

public readonly struct PostKey
{
    public PostKey(int id) => Id = id;
    public int Id { get; }
}

Ini kemudian dapat digunakan dalam model domain:

public class Blog
{
    public BlogKey Id { get; set; }
    public string Name { get; set; }

    public ICollection<Post> Posts { get; set; }
}

public class Post
{
    public PostKey Id { get; set; }

    public string Title { get; set; }
    public string Content { get; set; }

    public BlogKey? BlogId { get; set; }
    public Blog Blog { get; set; }
}

Perhatikan bahwa Blog.Id tidak dapat secara tidak sengaja ditetapkan PostKey, dan Post.Id tidak dapat secara tidak sengaja ditetapkan .BlogKey Demikian pula, Post.BlogId properti kunci asing harus diberi BlogKey.

Catatan

Menampilkan pola ini tidak berarti kami merekomendasikannya. Pertimbangkan dengan cermat apakah tingkat abstraksi ini membantu atau menghambat pengalaman pengembangan Anda. Selain itu, pertimbangkan untuk menggunakan navigasi dan kunci yang dihasilkan alih-alih berurusan dengan nilai kunci secara langsung.

Properti kunci ini kemudian dapat dipetakan menggunakan pengonversi nilai:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    var blogKeyConverter = new ValueConverter<BlogKey, int>(
        v => v.Id,
        v => new BlogKey(v));

    modelBuilder.Entity<Blog>().Property(e => e.Id).HasConversion(blogKeyConverter);

    modelBuilder.Entity<Post>(
        b =>
        {
            b.Property(e => e.Id).HasConversion(v => v.Id, v => new PostKey(v));
            b.Property(e => e.BlogId).HasConversion(blogKeyConverter);
        });
}

Catatan

Properti utama dengan konversi hanya dapat menggunakan nilai kunci yang dihasilkan yang dimulai dengan EF Core 7.0.

Gunakan ulong untuk tanda waktu/rowversion

SQL Server mendukung konkurensi optimis otomatis menggunakan kolom biner rowversion/timestamp 8-byte. Ini selalu dibaca dari dan ditulis ke database menggunakan array 8-byte. Namun, array byte adalah jenis referensi yang dapat diubah, yang membuatnya agak menyakitkan untuk ditangani. Pengonversi nilai rowversion memungkinkan untuk dipetakan ke ulong properti, yang jauh lebih tepat dan mudah digunakan daripada array byte. Misalnya, pertimbangkan Blog entitas dengan token konkurensi ulong:

public class Blog
{
    public int Id { get; set; }
    public string Name { get; set; }
    public ulong Version { get; set; }
}

Ini dapat dipetakan ke kolom server rowversion SQL menggunakan pengonversi nilai:

modelBuilder.Entity<Blog>()
    .Property(e => e.Version)
    .IsRowVersion()
    .HasConversion<byte[]>();

Tentukan DateTime.Kind saat membaca tanggal

SQL Server membuang DateTime.Kind bendera saat menyimpan DateTime sebagai datetime atau datetime2. Ini berarti bahwa nilai DateTime yang kembali dari database selalu memiliki DateTimeKind .Unspecified

Pengonversi nilai dapat digunakan dalam dua cara untuk menangani hal ini. Pertama, EF Core memiliki pengonversi nilai yang membuat nilai buram 8 byte yang mempertahankan Kind bendera. Contohnya:

modelBuilder.Entity<Post>()
    .Property(e => e.PostedOn)
    .HasConversion<long>();

Ini memungkinkan nilai DateTime dengan bendera yang berbeda Kind untuk dicampur dalam database.

Masalah dengan pendekatan ini adalah database tidak lagi dapat dikenali datetime atau datetime2 kolom. Jadi sebaliknya, biasanya selalu menyimpan waktu UTC (atau, kurang umum, selalu waktu lokal) dan kemudian mengabaikan Kind bendera atau mengaturnya ke nilai yang sesuai menggunakan pengonversi nilai. Misalnya, konverter di bawah ini memastikan bahwa nilai yang DateTime dibaca dari database akan memiliki DateTimeKindUTC:

modelBuilder.Entity<Post>()
    .Property(e => e.LastUpdated)
    .HasConversion(
        v => v,
        v => new DateTime(v.Ticks, DateTimeKind.Utc));

Jika campuran nilai lokal dan UTC diatur dalam instans entitas, maka konverter dapat digunakan untuk mengonversi dengan tepat sebelum menyisipkan. Contoh:

modelBuilder.Entity<Post>()
    .Property(e => e.LastUpdated)
    .HasConversion(
        v => v.ToUniversalTime(),
        v => new DateTime(v.Ticks, DateTimeKind.Utc));

Catatan

Pertimbangkan dengan cermat untuk menyatukan semua kode akses database untuk menggunakan waktu UTC sepanjang waktu, hanya berurusan dengan waktu lokal saat menyajikan data kepada pengguna.

Menggunakan kunci string yang tidak peka huruf besar/kecil

Beberapa database, termasuk SQL Server, melakukan perbandingan string yang tidak peka huruf besar/kecil secara default. .NET, di sisi lain, melakukan perbandingan string peka huruf besar/kecil secara default. Ini berarti bahwa nilai kunci asing seperti "DotNet" akan cocok dengan nilai kunci utama "dotnet" di SQL Server, tetapi tidak akan cocok dengannya di EF Core. Pembanding nilai untuk kunci dapat digunakan untuk memaksa EF Core ke dalam perbandingan string yang tidak peka huruf besar/kecil seperti dalam database. Misalnya, pertimbangkan model blog/posting dengan kunci string:

public class Blog
{
    public string Id { get; set; }
    public string Name { get; set; }

    public ICollection<Post> Posts { get; set; }
}

public class Post
{
    public string Id { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }

    public string BlogId { get; set; }
    public Blog Blog { get; set; }
}

Ini tidak akan berfungsi seperti yang Post.BlogId diharapkan jika beberapa nilai memiliki casing yang berbeda. Kesalahan yang disebabkan oleh ini akan tergantung pada apa yang dilakukan aplikasi, tetapi biasanya melibatkan grafik objek yang tidak diperbaiki dengan benar, dan/atau pembaruan yang gagal karena nilai FK salah. Perbandingan nilai dapat digunakan untuk memperbaiki ini:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    var comparer = new ValueComparer<string>(
        (l, r) => string.Equals(l, r, StringComparison.OrdinalIgnoreCase),
        v => v.ToUpper().GetHashCode(),
        v => v);

    modelBuilder.Entity<Blog>()
        .Property(e => e.Id)
        .Metadata.SetValueComparer(comparer);

    modelBuilder.Entity<Post>(
        b =>
        {
            b.Property(e => e.Id).Metadata.SetValueComparer(comparer);
            b.Property(e => e.BlogId).Metadata.SetValueComparer(comparer);
        });
}

Catatan

Perbandingan string .NET dan perbandingan string database dapat berbeda dalam lebih dari sekadar sensitivitas kasus. Pola ini berfungsi untuk kunci ASCII sederhana, tetapi mungkin gagal untuk kunci dengan segala jenis karakter khusus budaya. Lihat Kolate dan Sensitivitas Kasus untuk informasi selengkapnya.

Menangani string database dengan panjang tetap

Contoh sebelumnya tidak memerlukan pengonversi nilai. Namun, pengonversi dapat berguna untuk jenis string database dengan panjang tetap seperti char(20) atau nchar(20). String panjang tetap diisi dengan panjang penuh setiap kali nilai dimasukkan ke dalam database. Ini berarti bahwa nilai kunci "dotnet" akan dibaca kembali dari database sebagai "dotnet..............", di mana . mewakili karakter spasi. Ini kemudian tidak akan membandingkan dengan benar dengan nilai kunci yang tidak diisi.

Pengonversi nilai dapat digunakan untuk memangkas padding saat membaca nilai kunci. Ini dapat dikombinasikan dengan pembanding nilai dalam contoh sebelumnya untuk membandingkan kunci ASCII yang tidak peka huruf besar/kecil dengan panjang tetap dengan benar. Contohnya:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    var converter = new ValueConverter<string, string>(
        v => v,
        v => v.Trim());
        
    var comparer = new ValueComparer<string>(
        (l, r) => string.Equals(l, r, StringComparison.OrdinalIgnoreCase),
        v => v.ToUpper().GetHashCode(),
        v => v);

    modelBuilder.Entity<Blog>()
        .Property(e => e.Id)
        .HasColumnType("char(20)")
        .HasConversion(converter, comparer);

    modelBuilder.Entity<Post>(
        b =>
        {
            b.Property(e => e.Id).HasColumnType("char(20)").HasConversion(converter, comparer);
            b.Property(e => e.BlogId).HasColumnType("char(20)").HasConversion(converter, comparer);
        });
}

Mengenkripsi nilai properti

Pengonversi nilai dapat digunakan untuk mengenkripsi nilai properti sebelum mengirimkannya ke database, lalu mendekripsinya saat keluar. Misalnya, menggunakan pembalikan string sebagai pengganti algoritma enkripsi nyata:

modelBuilder.Entity<User>().Property(e => e.Password).HasConversion(
    v => new string(v.Reverse().ToArray()),
    v => new string(v.Reverse().ToArray()));

Catatan

Saat ini tidak ada cara untuk mendapatkan referensi ke DbContext saat ini, atau status sesi lainnya, dari dalam pengonversi nilai. Ini membatasi jenis enkripsi yang dapat digunakan. Pilih masalah GitHub #11597 agar batasan ini dihapus.

Peringatan

Pastikan untuk memahami semua implikasi jika Anda menggulung enkripsi Anda sendiri untuk melindungi data sensitif. Pertimbangkan untuk menggunakan mekanisme enkripsi bawaan, seperti Always Encrypted di SQL Server.

Batasan

Ada beberapa batasan yang diketahui saat ini dari sistem konversi nilai:

  • Seperti disebutkan di atas, null tidak dapat dikonversi. Silakan pilih (👍) untuk masalah GitHub #13850 jika ini adalah sesuatu yang Anda butuhkan.
  • Tidak dimungkinkan untuk mengkueri ke properti yang dikonversi nilai, misalnya anggota referensi pada jenis .NET yang dikonversi nilai dalam kueri LINQ Anda. Silakan pilih (👍) untuk masalah GitHub #10434 jika ini adalah sesuatu yang Anda butuhkan - tetapi mempertimbangkan untuk menggunakan kolom JSON sebagai gantinya.
  • Saat ini tidak ada cara untuk menyebarkan konversi satu properti ke beberapa kolom atau sebaliknya. Silakan pilih (👍) untuk masalah GitHub #13947 jika ini adalah sesuatu yang Anda butuhkan.
  • Pembuatan nilai tidak didukung untuk sebagian besar kunci yang dipetakan melalui pengonversi nilai. Silakan pilih (👍) untuk masalah GitHub #11597 jika ini adalah sesuatu yang Anda butuhkan.
  • Konversi nilai tidak dapat mereferensikan instans DbContext saat ini. Silakan pilih (👍) untuk masalah GitHub #12205 jika ini adalah sesuatu yang Anda butuhkan.
  • Parameter yang menggunakan jenis yang dikonversi nilai saat ini tidak dapat digunakan dalam API SQL mentah. Silakan pilih (👍) untuk masalah GitHub #27534 jika ini adalah sesuatu yang Anda butuhkan.

Penghapusan batasan ini sedang dipertimbangkan untuk rilis mendatang.