Not
Bu sayfaya erişim yetkilendirme gerektiriyor. Oturum açmayı veya dizinleri değiştirmeyi deneyebilirsiniz.
Bu sayfaya erişim yetkilendirme gerektiriyor. Dizinleri değiştirmeyi deneyebilirsiniz.
Değer dönüştürücüleri, veritabanından okurken veya veritabanına yazarken özellik değerlerinin dönüştürülmesini sağlar. Bu dönüştürme, bir değerden aynı türde başka bir değere (örneğin, dizeleri şifreleme) veya bir tür değerinden başka bir türdeki bir değere (örneğin, sabit listesi değerlerini veritabanındaki dizelere ve dizelerden dönüştürme) olabilir.
İpucu
GitHub’dan örnek kodu indirerek bu belgedeki tüm kodları çalıştırabilir ve hataları ayıklayabilirsiniz.
Genel bakış
Değer dönüştürücüleri, ModelClrType ve ProviderClrType cinsinden belirtilir. Model türü, varlık türündeki özelliğin .NET türüdür. Sağlayıcı türü, veritabanı sağlayıcısı tarafından anlaşılan .NET türüdür. Örneğin, sabit listelerini veritabanında dize olarak kaydetmek için model türü sabit liste türündedir ve sağlayıcı türü `String` olarak belirtilir. Bu iki tür aynı olabilir.
Dönüştürmeler, iki Func ifade ağacı kullanılarak tanımlanır: biri ModelClrType'den ProviderClrType'ye ve diğeri ProviderClrType'ten ModelClrType'ye. İfade ağaçları, verimli bir şekilde dönüştürmelerin yapılabilmesini sağlamak amacıyla veritabanı erişim temsilcisine derlenecek şekilde kullanılır. İfade ağacı, karmaşık dönüştürmeler için bir dönüştürme yöntemine basit bir çağrı içerebilir.
Not
Değer dönüştürme için yapılandırılmış bir özelliğin ayrıca ValueComparer<T> belirtmesi gerekebilir. Daha fazla bilgi için aşağıdaki örneklere ve Değer Karşılaştırıcıları belgelerine bakın.
Değer dönüştürücü yapılandırma
Değer dönüştürmeleri DbContext.OnModelCreating içinde yapılandırılır. Örneğin, şöyle tanımlanmış bir enum ve varlık türünü göz önünde bulundurun:
public class Rider
{
public int Id { get; set; }
public EquineBeast Mount { get; set; }
}
public enum EquineBeast
{
Donkey,
Mule,
Horse,
Unicorn
}
Dönüştürmeler, OnModelCreating içinde enum değerlerinin "Eşek", "Katır" gibi dizeler olarak veritabanında saklanması için yapılandırılabilir; sadece ModelClrType'den ProviderClrType'ye dönüşüm yapacak bir işlev ve ters dönüşüm için başka bir işlev sağlamanız gerekir.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder
.Entity<Rider>()
.Property(e => e.Mount)
.HasConversion(
v => v.ToString(),
v => (EquineBeast)Enum.Parse(typeof(EquineBeast), v));
}
Not
Bir null değeri asla bir değer dönüştürücüye geçirilmeyecek. Veritabanı sütunundaki null, varlık örneğinde her zaman null olur ve tam tersi de geçerlidir. Bu, dönüştürmelerin uygulanmasını kolaylaştırır ve bunların null atanabilir ve null değer atanamayan özellikler arasında paylaşılmasını sağlar. Daha fazla bilgi için bkz . GitHub sorunu #13850 .
Değer dönüştürücüsünün toplu yapılandırılması
İlgili CLR türünü kullanan her özellik için aynı değer dönüştürücüsünü yapılandırmak yaygındır. Bunu her özellik için el ile yapmak yerine, bunu modelinizin tamamı için bir kez yapmak üzere kural öncesi model yapılandırmasını kullanabilirsiniz. Bunu yapmak için değer dönüştürücünüzü sınıf olarak tanımlayın:
public class CurrencyConverter : ValueConverter<Currency, decimal>
{
public CurrencyConverter()
: base(
v => v.Amount,
v => new Currency(v))
{
}
}
Ardından bağlam türünüzde geçersiz kılın ConfigureConventions ve dönüştürücüsü aşağıdaki gibi yapılandırın:
protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
configurationBuilder
.Properties<Currency>()
.HaveConversion<CurrencyConverter>();
}
Önceden tanımlanmış dönüştürmeler
EF Core, dönüştürme işlevlerini el ile yazma gereğini önleyen önceden tanımlanmış birçok dönüştürme içerir. Bunun yerine EF Core, modeldeki özellik türüne ve istenen veritabanı sağlayıcısı türüne göre kullanılacak dönüştürmeyi seçer.
Örneğin, enum'dan dizeye dönüşümler daha önce bir örnek olarak gösterilmiştir, ancak sağlayıcı türü string olarak genel tür HasConversion kullanılarak yapılandırıldığında EF Core bunu otomatik olarak yapar.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder
.Entity<Rider>()
.Property(e => e.Mount)
.HasConversion<string>();
}
Veritabanı sütun türü açıkça belirtilerek aynı şey elde edilebilir. Örneğin, varlık türü şöyle tanımlanmışsa:
public class Rider2
{
public int Id { get; set; }
[Column(TypeName = "nvarchar(24)")]
public EquineBeast Mount { get; set; }
}
Daha sonra sabit listesi değerleri, OnModelCreating içinde ek bir yapılandırma olmaksızın veritabanında dize olarak kaydedilir.
ValueConverter sınıfı
Yukarıda gösterildiği gibi HasConversion çağrıldığında, bir ValueConverter<TModel,TProvider> örneği oluşturulup özellik üzerinde ayarlanır.
ValueConverter bunun yerine açıkça oluşturulabilir. Örneğin:
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);
}
Bu, birden çok özellik aynı dönüştürmeyi kullandığında yararlı olabilir.
Yerleşik dönüştürücüler
Yukarıda belirtildiği gibi EF Core, ad alanında ValueConverter<TModel,TProvider> bulunan bir dizi önceden tanımlanmış Microsoft.EntityFrameworkCore.Storage.ValueConversion sınıfla birlikte gelir. Çoğu durumda, yukarıda numaralandırmalar için gösterildiği gibi, EF modeldeki özelliğin türüne ve veritabanında istenen türe göre uygun yerleşik dönüştürücüleri seçer. Örneğin, bir bool özelliğinde .HasConversion<int>() kullanmak, EF Core'un bool değerlerini sayısal sıfır ve bir değerlerine dönüştürmesine neden olur.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder
.Entity<User>()
.Property(e => e.IsActive)
.HasConversion<int>();
}
Bu işlev, yerleşik BoolToZeroOneConverter<TProvider> bir örneğini oluşturma ve açıkça ayarlama ile aynıdır:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
var converter = new BoolToZeroOneConverter<int>();
modelBuilder
.Entity<User>()
.Property(e => e.IsActive)
.HasConversion(converter);
}
Aşağıdaki tabloda, model/özellik türlerinden veritabanı sağlayıcısı türlerine yaygın olarak kullanılan önceden tanımlanmış dönüştürmeler özetlenmiştir. Tabloda any_numeric_type; int, short, long, byte, uint, ushort, ulong, sbyte, char, decimal, float veya double anlamına gelir.
| Model/özellik türü | Sağlayıcı/veritabanı türü | Dönüştürme | Kullanım |
|---|---|---|---|
| bool | herhangi_bir_sayısal_tür | Yanlış/Doğru'yu 0/1'e dönüştürme. | .HasConversion<any_numeric_type>() |
| herhangi_bir_sayısal_tür | Herhangi iki sayı için yanlış/doğru değerler | BoolToTwoValuesConverter<TProvider> komutunu kullanma | |
| Dize | Yanlış/Doğru "N"/"Y" olarak | .HasConversion<string>() |
|
| Dize | İki dize için false/true değerlendirmesi | BoolToStringConverter komutunu kullanma | |
| herhangi_bir_sayısal_tür | bool | 0/1 - yanlış/doğru | .HasConversion<bool>() |
| herhangi_bir_sayısal_tür | Basit tür dönüşümü | .HasConversion<any_numeric_type>() |
|
| Dize | Dize olarak sayı | .HasConversion<string>() |
|
| enum | herhangi_bir_sayısal_tür | Enum'un sayısal değeri | .HasConversion<any_numeric_type>() |
| Dize | Enum değerinin dize gösterimi | .HasConversion<string>() |
|
| Dize | bool | Dizeyi bool olarak ayrıştırıyor | .HasConversion<bool>() |
| herhangi_bir_sayısal_tür | Dizeyi verilen sayısal tür olarak ayrıştırıyor | .HasConversion<any_numeric_type>() |
|
| karakter | Dizenin ilk karakteri | .HasConversion<char>() |
|
| Tarih ve Saat | Dizeyi DateTime olarak ayrıştırır | .HasConversion<DateTime>() |
|
| TarihSaatÖtelemesi | Dizeyi DateTimeOffset olarak ayrıştırır | .HasConversion<DateTimeOffset>() |
|
| Zaman Aralığı | Dizeyi TimeSpan olarak ayrıştırır | .HasConversion<TimeSpan>() |
|
| GUID | Dizeyi GUID olarak parçalıyor | .HasConversion<Guid>() |
|
| byte[] | UTF8 bayt olarak dize | .HasConversion<byte[]>() |
|
| karakter | Dize | Tek karakterli dize | .HasConversion<string>() |
| Tarih ve Saat | uzun | DateTime.Kind'i koruyan kodlanmış tarih/saat | .HasConversion<long>() |
| uzun | Keneler | DateTimeToTicksConverter komutunu kullanma | |
| Dize | Sabit kültür tarih/saat dizesi | .HasConversion<string>() |
|
| TarihSaatÖtelemesi | uzun | Zaman farkı ile kodlanmış tarih/saat | .HasConversion<long>() |
| Dize | Zaman farkı ile değişmez kültür tarih/saat dizesi | .HasConversion<string>() |
|
| Zaman Aralığı | uzun | Keneler | .HasConversion<long>() |
| Dize | Sabit kültür zaman aralığı dizesi | .HasConversion<string>() |
|
| Urı | Dize | URI'nin dize hali | .HasConversion<string>() |
| Fiziksel Adres | Dize | Adres bir dize olarak | .HasConversion<string>() |
| byte[] | Büyük uç ağ düzenindeki bayt sayısı | .HasConversion<byte[]>() |
|
| IP Adresi | Dize | Adresin bir dize olarak ifade edilmesi. | .HasConversion<string>() |
| byte[] | Büyük endian ağ düzeninde baytlar | .HasConversion<byte[]>() |
|
| GUID | Dize | 'dddddddd-dddd-dddd-dddd-dddddddddddd' biçimindeki GUID | .HasConversion<string>() |
| byte[] | .NET ikili serileştirme sırasına göre bayt sayısı | .HasConversion<byte[]>() |
Bu dönüştürmelerin, değerin biçiminin dönüştürme için uygun olduğunu varsaydığını unutmayın. Örneğin, dize değerleri sayı olarak ayrıştırılamazsa dizeleri sayılara dönüştürme işlemi başarısız olur.
Yerleşik dönüştürücülerin tam listesi:
- Bool özelliklerinin dönüştürülmesi
- BoolToStringConverter - Boolean değerleri "N" ve "Y" gibi dizelere dönüştürme
- BoolToTwoValuesConverter<TProvider> - Boolean değeri iki farklı değere dönüştür
- BoolToZeroOneConverter<TProvider> - Boolean değeri sıfıra ve bire
- Byte dizisi özelliklerinin dönüştürülmesi
- BytesToStringConverter - Bayt dizisini Base64 ile kodlanmış stringe
- Yalnızca tür ataması gerektiren dönüştürmeler
- CastingConverter<TModel,TProvider> - Yalnızca tür ataması gerektiren dönüştürmeler
- Karakter özellikleri dönüştürülüyor:
- CharToStringConverter - Karakteri tek karakterlik dizeye dönüştürme
- Özellikler dönüştürülüyor DateTimeOffset :
- DateTimeOffsetToBinaryConverter - DateTimeOffset ikili kodlanmış 64 bit değerine
- DateTimeOffsetToBytesConverter - DateTimeOffset baytların dizisi
- DateTimeOffsetToStringConverter - DateTimeOffset dizeye
- Özellikler dönüştürülüyor DateTime :
- DateTimeToBinaryConverter - DateTime DateTimeKind dahil olmak üzere 64 bit değere
- DateTimeToStringConverter - DateTime string'e dönüştür
- DateTimeToTicksConverter - DateTime onay işareti eklemek için
- Numaralandırma (enum) özelliklerini dönüştürme:
- EnumToNumberConverter<TEnum,TNumber> - Temel numaraya enum
- EnumToStringConverter<TEnum> - Enum'u dizeye
-
Guid özellikleri dönüştürülüyor.
- GuidToBytesConverter - Guid bayt dizisine dönüştürme
- GuidToStringConverter - Guid stringe
-
IPAddress özellikleri dönüştürülüyor:
- IPAddressToBytesConverter - IPAddress bayt dizisi
- IPAddressToStringConverter - IPAddress string'e
- Sayısal (int, double, decimal vb.) özellikleri dönüştürülüyor:
- NumberToBytesConverter<TNumber> - Bayt dizisi için herhangi bir sayısal değer
- NumberToStringConverter<TNumber> - Dizeye herhangi bir sayısal değer
-
PhysicalAddress özellikleri dönüştürülüyor:
- PhysicalAddressToBytesConverter - PhysicalAddress bayt dizisi
- PhysicalAddressToStringConverter - PhysicalAddress dizeye
- Dize özelliklerini dönüştürme:
- StringToBoolConverter - Bool için "N" ve "Y" gibi dizeler
- StringToBytesConverter - Dizgi UTF8 baytlara dönüştürme
- StringToCharConverter - Dizeden karaktere dönüştürme
- StringToDateTimeConverter - Dize Çevir: DateTime
- StringToDateTimeOffsetConverter - Dize: DateTimeOffset
- StringToEnumConverter<TEnum> - Dizeyi enum'a dönüştür
- StringToGuidConverter - Dize: Guid
- StringToNumberConverter<TNumber> - Dizeden sayısal türe
- StringToTimeSpanConverter - Dize: TimeSpan
- StringToUriConverter - Dize olarak Uri
-
TimeSpan özellikleri dönüştürülüyor:
- TimeSpanToStringConverter - TimeSpan dizeye
- TimeSpanToTicksConverter - TimeSpan onay işareti eklemek için
-
Uri özellikleri dönüştürülüyor:
- UriToStringConverter - Uri dizeye
Tüm yerleşik dönüştürücülerin durum bilgisi olmadığını ve bu nedenle tek bir örneğin birden çok özellik tarafından güvenli bir şekilde paylaşılabildiğini unutmayın.
Sütun yüzeyleri ve haritalama ipuçları
Bazı veritabanı türlerinin, verilerin depolanma biçimini değiştiren modelleri vardır. Bunlar şunlardır:
- Ondalık ve tarih/saat sütunları için hassasiyet ve ölçek
- İkili ve dize sütunları için boyut/uzunluk
- Dize sütunları için Unicode
Bu modeller, değer dönüştürücü kullanan bir özellik için normal şekilde yapılandırılabilir ve dönüştürülen veritabanı türüne uygulanır. Örneğin, bir numaralandırmadan dizelere dönüştürürken veritabanı sütununun Unicode olmayan olması ve en fazla 20 karakter depolaması gerektiğini belirtebiliriz:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder
.Entity<Rider>()
.Property(e => e.Mount)
.HasConversion<string>()
.HasMaxLength(20)
.IsUnicode(false);
}
Alternatif olarak, dönüştürücü açıkça oluşturulurken:
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);
}
Bu, SQL Server'a karşı EF Core geçişleri kullanılırken bir varchar(20) sütuna neden olur:
CREATE TABLE [Rider] (
[Id] int NOT NULL IDENTITY,
[Mount] varchar(20) NOT NULL,
CONSTRAINT [PK_Rider] PRIMARY KEY ([Id]));
Ancak, tüm EquineBeast sütunlar varsayılan olarak varchar(20) olmalıysa, bu bilgiler değer dönüştürücüsüne ConverterMappingHints olarak verilebilir. Örneğin:
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);
}
Artık bu dönüştürücü her kullanıldığında, veritabanı sütunu en fazla 20 uzunlukla unicode olmayan bir sütun olacaktır. Ancak bunlar, eşlenen özellikte açıkça ayarlanan yönler tarafından geçersiz kılındığından yalnızca ipuçlarıdır.
Örnekler
Basit değer nesneleri
Bu örnek, basit bir tür kullanarak primitif bir türü sarmalar. Bu, modelinizdeki türün ilkel bir türe göre daha belirgin (ve dolayısıyla tür açısından daha güvenli) olmasını istediğinizde yararlı olabilir. Bu örnekte, ondalık ilkelini saran tür Dollars'dır.
public readonly struct Dollars
{
public Dollars(decimal amount)
=> Amount = amount;
public decimal Amount { get; }
public override string ToString()
=> $"${Amount}";
}
Bu, bir varlık türünde kullanılabilir:
public class Order
{
public int Id { get; set; }
public Dollars Price { get; set; }
}
Veritabanında depolandığında alttaki decimal haline dönüştürülerek:
modelBuilder.Entity<Order>()
.Property(e => e.Price)
.HasConversion(
v => v.Amount,
v => new Dollars(v));
Not
Bu değer nesnesi salt okunur bir yapı olarak uygulanır. Bu, EF Core'un sorunsuz bir şekilde değerleri anlık görüntüleyip karşılaştırabileceği anlamına gelir. Daha fazla bilgi için bkz . Değer Karşılaştırıcıları .
Bileşik değer nesneleri
Önceki örnekte değer nesnesi türü yalnızca tek bir özellik içeriyordu. Bir değer nesne türünün birlikte etki alanı kavramı oluşturan birden çok özellik oluşturması daha yaygındır. Örneğin, hem tutarı hem de para birimini içeren genel Money bir tür:
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
}
Bu değer nesnesi bir varlık türünde daha önce olduğu gibi kullanılabilir:
public class Order
{
public int Id { get; set; }
public Money Price { get; set; }
}
Değer dönüştürücüleri şu anda yalnızca tek bir veritabanı sütununa ve bu sütundan değerleri dönüştürebilir. Bu sınırlama, nesnedeki tüm özellik değerlerinin tek bir sütun değerine kodlanması gerektiği anlamına gelir. Bu işlem genellikle nesne veritabanına girdikçe serileştirilerek ve çıkışta yeniden deserileştirilerek işlenir. Örneğin, kullanarak System.Text.Json:
modelBuilder.Entity<Order>()
.Property(e => e.Price)
.HasConversion(
v => JsonSerializer.Serialize(v, (JsonSerializerOptions)null),
v => JsonSerializer.Deserialize<Money>(v, (JsonSerializerOptions)null));
Not
Bir nesnenin EF Core'un gelecekteki bir sürümünde birden çok sütuna eşlenmesine olanak tanıyarak burada serileştirme kullanma gereksinimini ortadan kaldırmayı planlıyoruz. Bu GitHub sorunu #13947 tarafından izlenir.
Not
Önceki örnekte olduğu gibi, bu değer nesnesi salt okunur bir yapı olarak uygulanır. Bu, EF Core'un sorunsuz bir şekilde değerleri anlık görüntüleyip karşılaştırabileceği anlamına gelir. Daha fazla bilgi için bkz . Değer Karşılaştırıcıları .
Primitif koleksiyonlar
Serileştirme, ilkel değerler koleksiyonunu depolamak için de kullanılabilir. Örneğin:
public class Post
{
public int Id { get; set; }
public string Title { get; set; }
public string Contents { get; set; }
public ICollection<string> Tags { get; set; }
}
System.Text.Json yeniden kullanma:
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> değiştirilebilir başvuru türünü temsil eder. Bu, EF Core'un değişiklikleri doğru şekilde izleyebilmesi ve algılaması için bir ValueComparer<T> gereklidir. Daha fazla bilgi için bkz . Değer Karşılaştırıcıları .
Değer nesneleri koleksiyonları
Önceki iki örneği birlikte birleştirerek bir değer nesneleri koleksiyonu oluşturabiliriz. Örneğin, blogu tek bir yıl için finanse eden bir AnnualFinance tür düşünün:
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);
}
Bu tür, daha önce oluşturduğumuz Money türlerinden birkaçından oluşur.
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
}
Ardından varlık türümüze bir koleksiyonu AnnualFinance ekleyebiliriz:
public class Blog
{
public int Id { get; set; }
public string Name { get; set; }
public IList<AnnualFinance> Finances { get; set; }
}
Bunu depolamak için serileştirmeyi tekrar kullanın:
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()));
Not
Daha önce olduğu gibi, bu dönüştürme için bir ValueComparer<T>gerekir. Daha fazla bilgi için bkz . Değer Karşılaştırıcıları .
Nesneleri anahtar olarak değerle
Bazen ilkel anahtar özellikleri, değer atamada ek tür güvenliği düzeyi eklemek için değer nesnelerine sarmalanabilir. Örneğin, bloglar için bir anahtar türü ve gönderiler için bir anahtar türü uygulayabiliriz:
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; }
}
Bunlar daha sonra etki alanı modelinde kullanılabilir:
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; }
}
Blog.Id yanlışlıkla bir PostKey atanamayacağına ve Post.Id yanlışlıkla bir BlogKey atanamayacağına dikkat edin. Benzer şekilde, Post.BlogId yabancı anahtar özelliğine de atanmalıdır BlogKey.
Not
Bu desenin gösterilmesi, bunu önerdiğimiz anlamına gelmez. Bu soyutlama düzeyinin geliştirme deneyiminize yardımcı olup olmadığını dikkatle göz önünde bulundurun. Ayrıca, doğrudan anahtar değerleriyle ilgilenmek yerine gezintileri ve oluşturulan anahtarları kullanmayı göz önünde bulundurun.
Bu anahtar özellikler daha sonra değer dönüştürücüleri kullanılarak eşlenebilir:
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);
});
}
Not
Dönüştürmelere sahip anahtar özellikler yalnızca EF Core 7.0'dan başlayarak otomatik oluşturulan anahtar değerlerini kullanabilir.
Zaman damgası/rowversion için ulong kullanın
SQL Server, 8 baytlık ikili sütunlar kullanarak otomatik iyimser eşzamanlılık kontrolünü destekler. Bunlar her zaman 8 baytlık bir dizi kullanılarak veritabanından okunur ve veritabanına yazılır. Ancak, bayt dizileri değiştirilebilir bir başvuru türüdür, bu da onlarla uğraşmayı biraz zahmetli hale getirir. Değer dönüştürücüleri bunun yerine bayt dizisine göre çok daha uygun ve kullanımı kolay bir rowversion özelliğe eşlenmesine olanak sağlarulong. Örneğin, ulong eşzamanlılık belirtecine sahip bir varlığı göz önünde bulundurun Blog :
public class Blog
{
public int Id { get; set; }
public string Name { get; set; }
public ulong Version { get; set; }
}
Bu, değer dönüştürücüsü kullanılarak bir SQL server rowversion sütununa eşlenebilir:
modelBuilder.Entity<Blog>()
.Property(e => e.Version)
.IsRowVersion()
.HasConversion<byte[]>();
Tarihleri okurken DateTime.Kind değerini belirtin
SQL Server, bir DateTime'yi datetime veya datetime2 olarak depolarken DateTime.Kind bayrağını atar. Bu, veritabanından geri gelen DateTime değerlerinin her zaman bir DateTimeKindUnspecified değerine sahip olduğu anlamına gelir.
Değer dönüştürücüleri, bununla başa çıkmak için iki şekilde kullanılabilir. İlk olarak, Kind bayrağını koruyan bir 8 baytlık opak değer oluşturan EF Core değer dönüştürücüsü vardır. Örneğin:
modelBuilder.Entity<Post>()
.Property(e => e.PostedOn)
.HasConversion<long>();
Bu, veritabanında farklı Kind bayraklara sahip DateTime değerlerinin karıştırılmasını sağlar.
Bu yaklaşımla ilgili sorun, veritabanında artık tanınabilir datetime veya datetime2 sütun olmamasıdır. Bu nedenle, her zaman UTC zamanını (veya daha nadir olarak her zaman yerel zamanı) depolamak ve ardından Kind bayrağını yoksaymak ya da bir değer dönüştürücü kullanarak uygun değere ayarlamak yaygındır. Örneğin, aşağıdaki dönüştürücü veritabanından okunan DateTime değerinin DateTimeKindUTC olmasını sağlar:
modelBuilder.Entity<Post>()
.Property(e => e.LastUpdated)
.HasConversion(
v => v,
v => new DateTime(v.Ticks, DateTimeKind.Utc));
Varlık örneklerinde yerel ve UTC değerlerinin bir karışımı ayarlanıyorsa, dönüştürücü eklemeden önce uygun şekilde dönüştürmek için kullanılabilir. Örneğin:
modelBuilder.Entity<Post>()
.Property(e => e.LastUpdated)
.HasConversion(
v => v.ToUniversalTime(),
v => new DateTime(v.Ticks, DateTimeKind.Utc));
Not
Tüm veritabanı erişim kodunu utc saatini her zaman kullanacak şekilde birleştirmeyi, yalnızca kullanıcılara veri sunarken yerel saatle ilgilenmeyi dikkatle düşünün.
Büyük/küçük harfe duyarlı olmayan dize anahtarlarını kullanma
SQL Server dahil olmak üzere bazı veritabanları varsayılan olarak büyük/küçük harfe duyarlı olmayan dize karşılaştırmaları gerçekleştirir. .NET ise büyük/küçük harfe duyarlı dize karşılaştırmalarını varsayılan olarak gerçekleştirir. Bu, "DotNet" gibi bir yabancı anahtar değerinin SQL Server'daki birincil anahtar değeri "dotnet" ile eşleşeceği ancak EF Core'da eşleşmeyeceği anlamına gelir. Anahtarlar için değer karşılaştırıcısı, EF Core'un veritabanındaki gibi büyük/küçük harfe duyarlı olmayan dize karşılaştırmalarına zorlanması için kullanılabilir. Örneğin, dize anahtarları içeren bir blog/gönderi modeli düşünün:
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; }
}
Bazı Post.BlogId değerler farklı büyük/küçük harfe sahipse bu işlem beklendiği gibi çalışmaz. Bunun neden olduğu hatalar uygulamanın ne yaptığına bağlıdır, ancak genellikle doğru düzeltilmemiş nesnelerin grafiklerini ve/veya FK değeri yanlış olduğundan başarısız olan güncelleştirmeleri içerir. Bunu düzeltmek için bir değer karşılaştırıcı kullanılabilir:
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);
});
}
Not
.NET dize karşılaştırmaları ve veritabanı dizesi karşılaştırmaları yalnızca büyük/küçük harf duyarlılığı dışında başka açılardan da farklılık gösterebilir. Bu düzen basit ASCII anahtarlar için çalışır, ancak kültüre özgü karakter türlerine sahip anahtarlar için başarısız olabilir. Daha fazla bilgi için Harmanlamalar ve Büyük/Küçük Harf Duyarlılığı bölümüne bakın.
Sabit uzunlukta veritabanı dizelerini işleme
Önceki örnekte bir değer dönüştürücüsü gerekli değildi. Ancak, bir dönüştürücü, char(20) veya nchar(20) gibi sabit uzunlukta veritabanı dizesi türleri için yararlı olabilir. Sabit uzunluklu dizeler, veritabanına her değer eklendiğinde tam uzunluklarına doldurulur. Bu, "dotnet" anahtar değerinin veritabanından "dotnet.............." olarak okunacağı ve burada . bir boşluk karakterini temsil edeceği anlamına gelir. Bu, dolgulanmamış anahtar değerleriyle doğru şekilde karşılaştırılamaz.
Değer dönüştürücüsü, anahtar değerlerini okurken dolguyu kırpmak için kullanılabilir. Bu, sabit uzunlukta büyük/küçük harfe duyarlı olmayan ASCII anahtarlarını doğru karşılaştırmak için önceki örnekteki değer karşılaştırıcısıyla birleştirilebilir. Örneğin:
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);
});
}
Özellik değerlerini şifreleme
Değer dönüştürücüleri, özellik değerlerini veritabanına göndermeden önce şifrelemek ve çıkışta şifresini çözmek için kullanılabilir. Örneğin, gerçek bir şifreleme algoritmasının yerine dize ters çevirmesini kullanma:
modelBuilder.Entity<User>().Property(e => e.Password).HasConversion(
v => new string(v.Reverse().ToArray()),
v => new string(v.Reverse().ToArray()));
Not
Şu anda bir değer dönüştürücüsünden geçerli DbContext veya başka bir oturum durumuna erişim sağlama imkanı yoktur. Bu, kullanılabilecek şifreleme türlerini sınırlar. Bu sınırlamanın kaldırılması için GitHub sorunu #11597'ye oy verin.
Uyarı
Hassas verileri korumak için kendi şifrelemenizi kullanırsanız tüm etkilerini anladığınızdan emin olun. Bunun yerine SQL Server'da Always Encrypted gibi önceden oluşturulmuş şifreleme mekanizmalarını kullanmayı düşünün.
Sınırlamalar
Değer dönüştürme sisteminin bilinen bazı geçerli sınırlamaları vardır:
-
nullyukarıda belirtildiği gibi dönüştürülemez. Eğer bu ihtiyacınız olan bir şeyse 👍GitHub sorunu #13850 için oy verin. - Değer dönüştürülen özelliklere sorgulama yapmak mümkün değildir; örneğin LINQ sorgularınızdaki değer dönüştürülen .NET türündeki başvuru üyeleri. Bu, ihtiyacınız olan bir şeyse ( ancak bunun yerine bir JSON sütunu kullanmayı göz önünde bulundurarak) GitHub sorunu #10434👍oy verin ().
- Şu anda bir özelliğin dönüşümünü birden çok sütuna veya tam tersi yaymanın bir yolu yoktur. GitHub sorunu #13947
için oy verin eğer bu sizin ihtiyacınız olan bir şeyse. - Değer dönüştürücüleri ile eşlenen çoğu anahtar için değer oluşturma desteklenmez. Bu ihtiyacınız olan bir şeyse, 👍GitHub sorunu #11597'ye oy verin.
- Değer dönüştürmeleri geçerli DbContext örneğine başvuramaz. Lütfen, bu ihtiyacınız olan bir şeyse, 👍GitHub konusuna #12205 oy verin.
- Değer dönüştürülen türleri kullanan parametreler şu anda ham SQL API'lerinde kullanılamaz. İhtiyacınız varsa lütfen 👍 gitHub hatası #27534'e oy verin.
Bu sınırlamaların kaldırılması, gelecek sürümlerde dikkate alınacaktır.