Catatan
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba masuk atau mengubah direktori.
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba mengubah direktori.
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.)
Petunjuk / Saran
Anda dapat menjalankan dan men-debug ke semua kode dalam dokumen ini dengan mengunduh kode sampel dari GitHub.
Gambaran Umum
Pengonversi nilai ditentukan berdasarkan ModelClrType dan ProviderClrType. Jenis model adalah tipe .NET dari properti dalam tipe 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 di 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 satu lagi untuk konversi sebaliknya.
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 berupa null dalam instance 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
Hal yang umum dilakukan adalah mengonfigurasi pengonversi nilai yang sama 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, konversi enum ke string digunakan sebagai contoh di atas, tetapi EF Core akan melakukannya secara otomatis ketika tipe penyedia dikonfigurasi sebagai string menggunakan tipe 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
Memanggil HasConversion seperti yang ditunjukkan di atas akan membuat instance ValueConverter<TModel,TProvider> dan menetapkannya 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 menyediakan sekumpulan kelas yang telah ditentukan sebelumnya, ditemukan di namespace Microsoft.EntityFrameworkCore.Storage.ValueConversion. 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, , shortlong, byte, uint, ushort, ulong, sbyte, char, decimal, float, atau double.
| Jenis model/tipe properti | Tipe penyedia atau basis data | Konversi | Penggunaan |
|---|---|---|---|
| bool | tipe_numerik_apa_saja | Salah/benar ke 0/1 | .HasConversion<any_numeric_type>() |
| tipe_numerik_apa_saja | Salah/benar untuk sembarang dua angka | Menggunakan BoolToTwoValuesConverter<TProvider> | |
| string | Salah/benar ke "N"/"Y" | .HasConversion<string>() |
|
| string | False/true untuk dua string mana pun | Menggunakan BoolToStringConverter | |
| tipe_numerik_apa_saja | bool | 0/1 ke salah/benar | .HasConversion<bool>() |
| tipe_numerik_apa_saja | Pemutaran sederhana | .HasConversion<any_numeric_type>() |
|
| string | Angka sebagai string | .HasConversion<string>() |
|
| Enum | tipe_numerik_apa_saja | Nilai numerik enum | .HasConversion<any_numeric_type>() |
| string | Representasi string dari nilai enum | .HasConversion<string>() |
|
| string | bool | Mengurai string sebagai bool | .HasConversion<bool>() |
| tipe_numerik_apa_saja | Mem-parse string sebagai tipe numerik yang diberikan | .HasConversion<any_numeric_type>() |
|
| char | Karakter pertama dari string | .HasConversion<char>() |
|
| Tanggal dan Waktu | Mengurai string sebagai DateTime | .HasConversion<DateTime>() |
|
| Pengaturan Waktu & Tanggal | Mengurai string sebagai DateTimeOffset | .HasConversion<DateTimeOffset>() |
|
| Rentang Waktu | Mengurai string sebagai TimeSpan | .HasConversion<TimeSpan>() |
|
| Panduan | Mengurai string menjadi Guid | .HasConversion<Guid>() |
|
| (byte[]) | String sebagai byte UTF8 | .HasConversion<byte[]>() |
|
| char | string | Karakter tunggal dari string | .HasConversion<string>() |
| Tanggal dan Waktu | panjang | Tanggal/waktu yang telah dikodekan tetap mempertahankan DateTime.Kind | .HasConversion<long>() |
| panjang | Detik | Gunakan DateTimeToTicksConverter | |
| string | String tanggal/waktu budaya invarian | .HasConversion<string>() |
|
| Pengaturan Waktu & Tanggal | panjang | Tanggal/waktu terkode dengan offset | .HasConversion<long>() |
| string | String tanggal/waktu budaya yang invarian dengan offset | .HasConversion<string>() |
|
| Rentang Waktu | panjang | Tanda Waktu | .HasConversion<long>() |
| string | String rentang waktu budaya invarian | .HasConversion<string>() |
|
| Uri | string | URI sebagai string | .HasConversion<string>() |
| Alamat Fisik | string | Alamat sebagai string | .HasConversion<string>() |
| (byte[]) | Urutan byte dalam jaringan big-endian | .HasConversion<byte[]>() |
|
| Alamat IP | string | Alamat sebagai teks | .HasConversion<string>() |
| (byte[]) | Byte-byte dalam urutan jaringan big-endian | .HasConversion<byte[]>() |
|
| Panduan | string | GUID dalam format 'dddddddd-dddd-dddd-dddd-dddddddddddd' | .HasConversion<string>() |
| (byte[]) | 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:
- Mengonversi properti bool:
- BoolToStringConverter - Ubah Bool menjadi string seperti "N" dan "Y"
- BoolToTwoValuesConverter<TProvider> - Bool ke salah satu dari dua nilai
- BoolToZeroOneConverter<TProvider> - Boolean menjadi nol dan satu
- Mengonversi atribut array byte:
- BytesToStringConverter - Deret byte ke string yang dikodekan Base64
- Konversi apa pun yang hanya memerlukan type-cast
- CastingConverter<TModel,TProvider> - Konversi yang hanya memerlukan pemaksaan tipe
- Mengonversi properti karakter:
- CharToStringConverter - Karakter ke string karakter tunggal
- Mengubah DateTimeOffset sifat:
- DateTimeOffsetToBinaryConverter - DateTimeOffset ke nilai 64-bit yang dikodekan biner
- DateTimeOffsetToBytesConverter - DateTimeOffset menjadi array bita
- DateTimeOffsetToStringConverter - DateTimeOffset menjadi string
- Mengonversi DateTime properti:
- DateTimeToBinaryConverter - DateTime ke nilai 64-bit termasuk DateTimeKind
- DateTimeToStringConverter - DateTime ke string
- DateTimeToTicksConverter - DateTime untuk mencentang
- Mengonversi properti enum:
- EnumToNumberConverter<TEnum,TNumber> - Enum ke nomor dasar
- EnumToStringConverter<TEnum> - Enum ke string
- Mengonversi Guid properti:
- GuidToBytesConverter - Guid ke bita array
- GuidToStringConverter - Guid ke teks
- Mengonversi IPAddress properti:
- IPAddressToBytesConverter - IPAddress ke array byte
- IPAddressToStringConverter - IPAddress ke string
- Mengonversi properti numerik (int, double, desimal, dll.):
- NumberToBytesConverter<TNumber> - Konversi nilai numerik apa pun ke array byte
- NumberToStringConverter<TNumber> - Setiap nilai numerik untuk string
- Mengonversi PhysicalAddress properti:
- PhysicalAddressToBytesConverter - PhysicalAddress ke array bita
- PhysicalAddressToStringConverter - PhysicalAddress menjadi string
- Mengonversi properti string:
- StringToBoolConverter - String seperti "N" dan "Y" ke bool
- StringToBytesConverter - Mengubah string menjadi byte UTF8
- StringToCharConverter - String ke karakter
- StringToDateTimeConverter - String ke DateTime
- StringToDateTimeOffsetConverter - String ke DateTimeOffset
- StringToEnumConverter<TEnum> - String menjadi enum
- StringToGuidConverter - String ke Guid
- StringToNumberConverter<TNumber> - String ke tipe numerik
- StringToTimeSpanConverter - String ke TimeSpan
- StringToUriConverter - String ke Uri
- Mengonversi TimeSpan properti:
- TimeSpanToStringConverter - TimeSpan menjadi string
- TimeSpanToTicksConverter - TimeSpan untuk mencentang
- Mengonversi Uri properti:
- UriToStringConverter - Uri menjadi teks
Perhatikan bahwa semua konverter bawaan adalah nirstatus, oleh karena itu 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 kolom varchar(20) 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 digantikan oleh faset mana 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 tipe di dalam model Anda lebih spesifik (dan karenanya lebih aman tipe) daripada tipe primitif. Dalam contoh ini, tipe 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; }
}
Dikonversi ke bentuk dasar 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 Perbanding 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 struct readonly. Ini berarti bahwa EF Core dapat mengambil rekam jepret dan membandingkan nilai tanpa masalah. Lihat Perbanding Nilai untuk informasi selengkapnya.
Kumpulan elemen dasar
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 Perbanding 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 Perbanding 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 kunci yang mengalami konversi hanya dapat menggunakan nilai kunci yang dihasilkan mulai dari 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 rumit untuk ditangani. Pengonversi nilai memungkinkan rowversion untuk dipetakan ke properti ulong, yang lebih sesuai dan lebih mudah digunakan dibandingkan dengan array byte. Misalnya, pertimbangkan Blog entitas dengan token konkruensi tipe 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 jenis 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 dari Unspecified.
Pengonversi nilai dapat digunakan dalam dua cara untuk menangani hal ini. Pertama, EF Core memiliki pengonversi nilai yang membuat nilai tidak transparan 8 byte yang mempertahankan flag Kind. Contohnya:
modelBuilder.Entity<Post>()
.Property(e => e.PostedOn)
.HasConversion<long>();
Ini memungkinkan penggabungan nilai-nilai DateTime dengan flag yang berbeda Kind dalam database.
Masalah dengan pendekatan ini adalah bahwa database tidak lagi memiliki kolom datetime atau datetime2 yang dapat dikenali. Oleh karena itu, umumnya selalu menyimpan waktu UTC (atau, kurang umum, selalu waktu lokal), lalu mengabaikan penanda Kind 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 cara yang tepat sebelum menyisipkannya. Contohnya:
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 non-sensitif terhadap huruf besar/kecil secara default. .NET, sebaliknya, melakukan perbandingan string yang sensitif terhadap 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 melakukan perbandingan string yang tidak peka terhadap huruf besar dan kecil, seperti yang ada di 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 dengan panjang tetap akan diisi ke panjang penuhnya 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 dari contoh sebelumnya untuk secara benar membandingkan kunci ASCII yang panjangnya tetap dan tidak peka huruf besar/kecil. 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,
nulltidak dapat dikonversi. Silakan pilih (👍) untuk masalah GitHub #13850 jika ini adalah sesuatu yang Anda butuhkan. - Tidak dimungkinkan untuk mengkueri properti yang telah dikonversi nilainya, seperti anggota referensi pada tipe .NET yang telah mengalami konversi 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.