Değer Dönüştürmeleri
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 ve ModelClrType
ProviderClrType
olarak 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 listesi türü, sağlayıcı türü ise şeklindedir String
. Bu iki tür aynı olabilir.
Dönüştürmeler iki Func
ifade ağacı kullanılarak tanımlanır: biri 'den ve ProviderClrType
diğeri 'den'e ModelClrType
ProviderClrType
.ModelClrType
İfade ağaçları, verimli dönüştürmeler için 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 de belirtmesi ValueComparer<T>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 içinde DbContext.OnModelCreatingyapılandırılır. Örneğin, olarak tanımlanan bir sabit listesi 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 sabit listesi değerlerini veritabanında "Eşek", "Katır" vb. gibi dizeler olarak depolanacak şekilde yapılandırılabilir; yalnızca dosyasından ModelClrType
ProviderClrType
öğesine dönüştüren bir işlev ve ters dönüştürme 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
Değer null
dönüştürücüye hiçbir zaman bir değer 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ü toplu yapılandırma
İ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, yukarıda örnek olarak dizeye sabit listesi kullanılır, ancak sağlayıcı türü genel türü HasConversionkullanılarak yapılandırıldığında EF Core bunu otomatik olarak string
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, içinde başka bir yapılandırma OnModelCreatingolmadan veritabanında dize olarak kaydedilir.
ValueConverter sınıfı
Yukarıda gösterildiği gibi çağrıldıkça HasConversion bir ValueConverter<TModel,TProvider> örnek oluşturulur ve özelliğinde 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 Microsoft.EntityFrameworkCore.Storage.ValueConversion bulunan bir dizi önceden tanımlanmış ValueConverter<TModel,TProvider> sınıfla birlikte gelir. Çoğu durumda EF, yukarıda sabit listeleri için gösterildiği gibi 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
özellikte kullanmak .HasConversion<int>()
EF Core'un bool değerlerini sayısal sıfıra ve bir değere 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
biri , , short
, long
, , byte
, uint
, , ushort
, , ulong
, sbyte
float
char
decimal
veya double
anlamına gelir.int
Model/özellik türü | Sağlayıcı/veritabanı türü | Dönüştürme | Kullanım |
---|---|---|---|
ikili | any_numeric_type | 0/1 için yanlış/doğru | .HasConversion<any_numeric_type>() |
any_numeric_type | İki sayıya yanlış/doğru | BoolToTwoValuesConverter<TProvider> komutunu kullanma | |
Dize | False/true to "N"/"Y" | .HasConversion<string>() |
|
Dize | İki dize için false/true | BoolToStringConverter komutunu kullanma | |
any_numeric_type | ikili | 0/1 -false/true | .HasConversion<bool>() |
any_numeric_type | Basit atama | .HasConversion<any_numeric_type>() |
|
Dize | Dize olarak sayı | .HasConversion<string>() |
|
Sabit listesi | any_numeric_type | Numaralandırmanın sayısal değeri | .HasConversion<any_numeric_type>() |
Dize | Sabit listesi değerinin dize gösterimi | .HasConversion<string>() |
|
Dize | ikili | Dizeyi bool olarak ayrıştırıyor | .HasConversion<bool>() |
any_numeric_type | Dizeyi verilen sayısal tür olarak ayrıştırıyor | .HasConversion<any_numeric_type>() |
|
char | Dizenin ilk karakteri | .HasConversion<char>() |
|
DateTime | Dizeyi DateTime olarak ayrıştırır | .HasConversion<DateTime>() |
|
DateTimeOffset | Dizeyi DateTimeOffset olarak ayrıştırır | .HasConversion<DateTimeOffset>() |
|
TimeSpan | Dizeyi TimeSpan olarak ayrıştırır | .HasConversion<TimeSpan>() |
|
GUID | Dizeyi Guid olarak ayrıştırıyor | .HasConversion<Guid>() |
|
bayt[] | UTF8 bayt olarak dize | .HasConversion<byte[]>() |
|
char | Dize | Tek karakterli dize | .HasConversion<string>() |
DateTime | uzun | DateTime.Kind'i koruyan kodlanmış tarih/saat | .HasConversion<long>() |
uzun | Ticks | DateTimeToTicksConverter komutunu kullanma | |
Dize | Sabit kültür tarih/saat dizesi | .HasConversion<string>() |
|
DateTimeOffset | uzun | Uzaklık ile kodlanmış tarih/saat | .HasConversion<long>() |
Dize | Uzaklık ile sabit kültür tarih/saat dizesi | .HasConversion<string>() |
|
TimeSpan | uzun | Ticks | .HasConversion<long>() |
Dize | Sabit kültür zaman aralığı dizesi | .HasConversion<string>() |
|
Uri | Dize | Dize olarak URI | .HasConversion<string>() |
PhysicalAddress | Dize | Dize olarak adres | .HasConversion<string>() |
bayt[] | Büyük uç ağ düzenindeki bayt sayısı | .HasConversion<byte[]>() |
|
IPAddress | Dize | Dize olarak adres | .HasConversion<string>() |
bayt[] | Büyük uç ağ düzenindeki bayt sayısı | .HasConversion<byte[]>() |
|
GUID | Dize | 'ddddd-d-dd-dddd-dd' biçimindeki GUID | .HasConversion<string>() |
bayt[] | .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 özellikleri dönüştürülüyor:
- BoolToStringConverter - "N" ve "Y" gibi dizelere bool
- BoolToTwoValuesConverter<TProvider> - İki değere bool
- BoolToZeroOneConverter<TProvider> - Bool değeri sıfıra ve bire
- Bayt dizisi özellikleri dönüştürülüyor:
- BytesToStringConverter - Bayt dizisini Base64 ile kodlanmış dizeye
- Yalnızca tür ataması gerektiren dönüştürmeler
- CastingConverter<TModel,TProvider> - Yalnızca tür ataması gerektiren dönüştürmeler
- Char özellikleri dönüştürülüyor:
- CharToStringConverter - Karakterden tek karaktere dize
- Özellikler dönüştürülüyor DateTimeOffset :
- DateTimeOffsetToBinaryConverter - DateTimeOffset ikili kodlanmış 64 bit değerine
- DateTimeOffsetToBytesConverter - DateTimeOffset bayt dizisi
- DateTimeOffsetToStringConverter - DateTimeOffset dizeye
- Özellikler dönüştürülüyor DateTime :
- DateTimeToBinaryConverter - DateTime DateTimeKind dahil olmak üzere 64 bit değere
- DateTimeToStringConverter - DateTime dizeye
- DateTimeToTicksConverter - DateTime onay işareti eklemek için
- Sabit listesi özellikleri dönüştürülüyor:
- EnumToNumberConverter<TEnum,TNumber> - Temel alınan numaraya sabit listesi
- EnumToStringConverter<TEnum> - Dizeye sabit listesi
- Özellikler dönüştürülüyor Guid :
- GuidToBytesConverter - Guid bayt dizisi
- GuidToStringConverter - Guid dizeye
- Özellikler dönüştürülüyor IPAddress :
- IPAddressToBytesConverter - IPAddress bayt dizisi
- IPAddressToStringConverter - IPAddress dizeye
- 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
- Özellikler dönüştürülüyor PhysicalAddress :
- PhysicalAddressToBytesConverter - PhysicalAddress bayt dizisi
- PhysicalAddressToStringConverter - PhysicalAddress dizeye
- Dize özellikleri dönüştürülüyor:
- StringToBoolConverter - Bool için "N" ve "Y" gibi dizeler
- StringToBytesConverter - UTF8 bayt dizesi
- StringToCharConverter - Dizeden karaktere
- StringToDateTimeConverter - Dize: DateTime
- StringToDateTimeOffsetConverter - Dize: DateTimeOffset
- StringToEnumConverter<TEnum> - Sabit listesi dizesi
- StringToGuidConverter - Dize: Guid
- StringToNumberConverter<TNumber> - Sayısal türe dize
- StringToTimeSpanConverter - Dize: TimeSpan
- StringToUriConverter - Dize: Uri
- Özellikler dönüştürülüyor TimeSpan :
- TimeSpanToStringConverter - TimeSpan dizeye
- TimeSpanToTicksConverter - TimeSpan onay işareti eklemek için
- Özellikler dönüştürülüyor Uri :
- 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 modelleri ve eşleme ipuçları
Bazı veritabanı türlerinin, verilerin depolanma biçimini değiştiren modelleri vardır. Bu modüller şunlardır:
- Ondalıklar ve tarih/saat sütunları için duyarlık ve ölçeklendirme
- İ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, varsayılan olarak tüm EquineBeast
sütunların olması varchar(20)
gerekiyorsa, bu bilgiler değer dönüştürücüsünü olarak ConverterMappingHintsverilebilir. Ö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 yalnızca eşlenen özellikte açıkça ayarlanan modeller tarafından geçersiz kılındığından ipuçlarıdır.
Örnekler
Basit değer nesneleri
Bu örnek basit bir tür kullanarak ilkel 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, bu tür ondalık temel öğesini sarmalayan şeklindedir Dollars
:
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; }
}
Ve veritabanında depolandığında temel decimal
alınana dönüştürülür:
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 seri hale getirilerek ve çıkışta yeniden seri durumdan çıkarılarak 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ı .
İlkel 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; }
}
Yeniden kullanma System.Text.Json :
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 türlerden birkaçını Money
oluşturur:
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 atanamayacağına BlogKey
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ürmeleri olan anahtar özellikleri yalnızca EF Core 7.0'dan başlayarak oluşturulan anahtar değerlerini kullanabilir.
Zaman damgası/rowversion için ulong kullanma
SQL Server, 8 baytlık ikili rowversion
timestamp
/sütunları kullanarak otomatik iyimser eşzamanlılığı 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 ve bu da bunlarla ilgilenmeleri biraz acı vericidir. Değer dönüştürücüleri bunun yerine bayt dizisine göre çok daha uygun ve kullanımı kolay bir ulong
özelliğe eşlenmesine olanak sağlarrowversion
. Ö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, veya DateTime datetime2
olarak datetime
depolarken bayrağını atarDateTime.Kind. Bu, veritabanından geri gelen DateTime değerlerinin her zaman bir DateTimeKind Unspecified
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, EF Core'un bayrağını koruyan 8 baytlık bir opak değer oluşturan bir değer dönüştürücüsü Kind
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 saatini (veya daha az yaygın olarak her zaman yerel saati) depolamak ve ardından bayrağı yoksaymak Kind
veya bir değer dönüştürücüsü kullanarak uygun değere ayarlamak yaygın bir durumdur. Örneğin, aşağıdaki dönüştürücü veritabanından DateTimeKind UTC
okunan DateTime
değerin değerinin 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ı, büyük/küçük harf duyarlılığından daha farklı olabilir. 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 bkz . Harmanlamalar ve Büyük/Küçük Harf Duyarlılığı .
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ü veya nchar(20)
gibi char(20)
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, doldurulmayan anahtar değerleriyle doğru şekilde karşılaştırılmaz.
Değer dönüştürücüsü, anahtar değerlerini okurken doldurmayı 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ü içinden geçerli DbContext veya başka bir oturum durumuna başvuru alma 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:
- Yukarıda belirtildiği gibi dönüştürülemez
null
. 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 için oy verin (👍).
- Şu anda bir özelliğin dönüştürmesini birden çok sütuna yayma veya tam tersi yoktur. Bu, ihtiyacınız olan bir şeyse github sorunu #13947 için () oy👍 verin.
- 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 için () oy👍 verin.
- Değer dönüştürmeleri geçerli DbContext örneğine başvuramaz. Bu, ihtiyacınız olan bir şeyse GitHub sorunu #12205 için () oy👍 verin.
- Değer dönüştürülen türleri kullanan parametreler şu anda ham SQL API'lerinde kullanılamaz. Bu, ihtiyacınız olan bir şeyse gitHub sorunu #27534 için () oy👍 verin.
Bu sınırlamaların kaldırılması, gelecek sürümlerde dikkate alınacaktır.