Aracılığıyla paylaş


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 ProviderClrTypeolarak 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, sbytefloatchardecimalveya doubleanlamı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:

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 PostKeyatanamayacağına ve Post.Id yanlışlıkla bir atanamayacağına BlogKeydikkat 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 rowversiontimestamp/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 datetime2olarak datetime depolarken bayrağını atarDateTime.Kind. Bu, veritabanından geri gelen DateTime değerlerinin her zaman bir DateTimeKind Unspecifieddeğ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 UTCokunan 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.