Megosztás:


Értékkonvertálások

Az értékkonverterek lehetővé teszik a tulajdonságértékek konvertálását az adatbázisból való olvasáskor vagy az adatbázisba való íráskor. Ez az átalakítás lehet két azonos típusú érték között (például sztringek titkosítása), vagy egy adott típusú értékről egy másik típusú értékre (például az enumerálási értékek konvertálása sztringekre és vissza az adatbázisban).

Jótanács

A mintakód GitHubról való letöltésével futtathatja és hibakeresést végezhet a dokumentum összes kódjában.

Áttekintés

Az értékkonvertereket egy ModelClrType és egy ProviderClrType határozza meg. A modell típusa az entitástípus tulajdonságának .NET-típusa. A szolgáltató típusa az adatbázis-szolgáltató által értelmezett .NET-típus. Ha például sztringekként szeretné menteni az enumerálást az adatbázisban, a modell típusa az enumerálás típusa, a szolgáltató típusa pedig a String. Ez a két típus lehet ugyanaz.

A konverziók két Func kifejezésfával vannak definiálva: az egyik ModelClrType-ból ProviderClrType-ba, a másik pedig ProviderClrType-ból ModelClrType-ba. A kifejezésfákat a rendszer úgy használja, hogy azokat az adatbázis-hozzáférési delegáltba lehessen lefordítani a hatékony átalakítás érdekében. Az kifejezésfa tartalmazhat egyszerű hívást egy módszerre, amely összetett konverziókat végez.

Megjegyzés:

Az értékkonverzióhoz konfigurált tulajdonságnak szintén meg kell adnia egy ValueComparer<T>. További információért tekintse meg az alábbi példákat és az Érték összehasonlítók dokumentációját.

Értékkonverter konfigurálása

Az értékkonvertálások a következőben DbContext.OnModelCreatingvannak konfigurálva: . Vegyük például a definíció alapján az alábbi enum és entitás típust:

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

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

A konverziók a OnModelCreating segítségével konfigurálhatók úgy, hogy az enumerálási értékeket sztringként, például "Donkey", "Öszvér" stb., tárolják az adatbázisban. Ehhez egyszerűen meg kell adnia egy függvényt, amely az értéket a ModelClrType-ból ProviderClrType-ba konvertálja, és egy másikat az ellenkező konverzióhoz.

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

Megjegyzés:

Az null érték soha nem lesz átadva értékkonverternek. Az adatbázisoszlopok null értéke mindig null az entitáspéldányban, és fordítva. Ez megkönnyíti a konverziók végrehajtását, és lehetővé teszi a null értékű és a nem null értékű tulajdonságok közötti megosztást. További információt a GitHub 13850-ös számában talál.

Értékkonverter tömeges konfigurálása

Gyakori, hogy ugyanazt az értékkonvertert minden olyan tulajdonsághoz konfigurálni kell, amely a megfelelő CLR-típust használja. Ahelyett, hogy ezt manuálisan tenné minden tulajdonság esetében, a konvenció előtti modellkonfigurációval ezt a teljes modell esetében egyszer elvégezheti. Ehhez osztályként definiálja az értékkonvertert:

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

Ezután felülbírálja ConfigureConventions a környezet típusát, és konfigurálja a konvertert az alábbiak szerint:

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

Előre definiált konverziók

Az EF Core számos előre definiált konverziót tartalmaz, amelyek nem igénylik a konvertálási függvények manuális írását. Ehelyett az EF Core a modell tulajdonságtípusa és a kért adatbázis-szolgáltató típusa alapján választja ki a használni kívánt átalakítást.

Például az enum típusok sztringgé konvertálását használják mint fentebb, de az EF Core ezt automatikusan végrehajtja, ha a szolgáltató típusa a string típusú általános HasConversion használatával van konfigurálva:

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

Ugyanez érhető el az adatbázis oszloptípusának explicit megadásával. Ha például az entitás típusa a következőképpen van definiálva:

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

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

Ezután az enumerálási értékek sztringekként lesznek mentve az adatbázisban anélkül, hogy további konfigurációt kellene megadni a fájlban OnModelCreating.

A ValueConverter osztály

A fent látható módon történő HasConversion hívás létrehoz egy ValueConverter<TModel,TProvider> példányt, és beállítja a tulajdonsághoz. Ehelyett ValueConverter explicit módon hozható létre. Például:

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);
}

Ez akkor lehet hasznos, ha több tulajdonság ugyanazt az átalakítást használja.

Beépített konverterek

Ahogy fentebb említettük, az EF Core előre definiált ValueConverter<TModel,TProvider> osztályokkal rendelkezik, amely a Microsoft.EntityFrameworkCore.Storage.ValueConversion névtérben található. Az EF sok esetben a modell tulajdonságának típusa és az adatbázisban kért típus alapján választja ki a megfelelő beépített konvertert, ahogy az a fenti példában az enumok esetén bemutatásra került. Például, ha a .HasConversion<int>() egy bool tulajdonságon van használva, az EF Core a bool értékeket numerikus nullára és egyre konvertálja.

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

Ez funkcionálisan megegyezik a beépített BoolToZeroOneConverter<TProvider> példány létrehozásával és explicit beállításával:

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

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

Az alábbi táblázat a modell-/tulajdonságtípusoktól az adatbázis-szolgáltatótípusokig gyakran használt, előre definiált átalakításokat foglalja össze. A táblázatban any_numeric_type az egyik int, short, long, byte, uint, ushort, ulong, sbyte, char, decimal, float vagy double.

Modell/tulajdonság típusa Szolgáltató/adatbázis típusa Konverzió Használat
Boolean bármilyen_numerikus_típus Hamis/igaz helyett 0/1 .HasConversion<any_numeric_type>()
bármilyen_numerikus_típus Bármely két számra igaz/hamis Használja a BoolToTwoValuesConverter<TProvider>-t
karakterlánc Hamis/igaz "N"/"Y" .HasConversion<string>()
karakterlánc Hamis/igaz bármelyik két sztringre Használja a BoolToStringConverter-t
bármilyen_numerikus_típus Boolean 0/1–hamis/igaz .HasConversion<bool>()
bármilyen_numerikus_típus Egyszerű vetítés .HasConversion<any_numeric_type>()
karakterlánc A szám sztringként .HasConversion<string>()
Enumeráció bármilyen_numerikus_típus Az enum számértéke .HasConversion<any_numeric_type>()
karakterlánc Az enumerálási érték sztring-ábrázolása .HasConversion<string>()
karakterlánc Boolean A sztringet boolként elemzi .HasConversion<bool>()
bármilyen_numerikus_típus A sztringet a megadott numerikus típusként elemzi .HasConversion<any_numeric_type>()
char A karakterlánc első karaktere .HasConversion<char>()
Dátum/idő A karakterlánc elemzése DateTime-ként .HasConversion<DateTime>()
IdőpontEltérés (DateTimeOffset) Elemzi a sztringet, mint DateTimeOffset .HasConversion<DateTimeOffset>()
TimeSpan A sztring elemzése TimeSpanként .HasConversion<TimeSpan>()
Guid A karakterláncot útmutatóként elemzi. .HasConversion<Guid>()
bájt[] A karakterlánc UTF8 bájtokként .HasConversion<byte[]>()
char karakterlánc Egyetlen karakterlánc .HasConversion<string>()
Dátum/idő hosszú Kódolt dátum/idő megőrzése DateTime.Kind .HasConversion<long>()
hosszú Kullancsok Használja a DateTimeToTicksConverter-t
karakterlánc Invariáns kultúra dátum/idő karakterlánc .HasConversion<string>()
IdőpontEltérés (DateTimeOffset) hosszú Kódolt dátum/idő eltolással .HasConversion<long>()
karakterlánc Invariáns kultúra dátum/idő karakterlánc időeltolással .HasConversion<string>()
TimeSpan hosszú Kullancsok .HasConversion<long>()
karakterlánc Invariáns kulturális időtartomány sztringje .HasConversion<string>()
Úri karakterlánc Az URI karakterláncként .HasConversion<string>()
Fizikai cím karakterlánc A cím karakterláncként .HasConversion<string>()
bájt[] Nagyvégű hálózati sorrendben lévő bájtok .HasConversion<byte[]>()
IP cím karakterlánc A cím karakterláncként .HasConversion<string>()
bájt[] Nagyvégű hálózati sorrendben lévő bájtok .HasConversion<byte[]>()
Guid karakterlánc A GUID a következő formátumban: 'dddddddd-dddd-dddd-dddd-dddddddddddd' .HasConversion<string>()
bájt[] Bájtok .NET bináris szerializálási sorrendben .HasConversion<byte[]>()

Vegye figyelembe, hogy ezek a konverziók feltételezik, hogy az érték formátuma megfelelő az átalakításhoz. A sztringek számmá alakítása például sikertelen lesz, ha a sztringértékek nem elemezhetők számként.

A beépített konverterek teljes listája a következő:

Vegye figyelembe, hogy az összes beépített konverter állapot nélküli, így egyetlen példány biztonságosan megosztható több tulajdonsággal.

Oszlop jellemzői és leképezési tippek

Egyes adatbázistípusok olyan aspektusokkal rendelkeznek, amelyek módosítják az adatok tárolási módját. Ezek a következők:

  • Pontosság és skálázás decimális és dátum/idő oszlopokhoz
  • Bináris és sztringoszlopok mérete/hossza
  • Unicode sztringoszlopokhoz

Ezek az aspektusok a normál módon konfigurálhatók egy értékkonvertert használó tulajdonsághoz, és a konvertált adatbázistípusra lesznek érvényesek. Ha például számból sztringekké konvertálja az adatbázis oszlopát, megadhatja, hogy az adatbázis oszlopa ne Unicode legyen, és legfeljebb 20 karaktert tároljon:

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

Vagy a konverter explicit létrehozásakor:

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);
}

Ez egy varchar(20) oszlopot eredményez, amikor EF Core-migrálást használ az SQL Serveren:

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

Ha azonban alapértelmezés szerint minden EquineBeast oszlopnak alapértelmezetten varchar(20) kell lennie, akkor ez az információ megadható az értékkonverternek ConverterMappingHints formájában. Például:

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);
}

Valahányszor ezt a konvertert használják, az adatbázis oszlopa nem Unicode kódolású lesz, és legfeljebb 20 karakter hosszúságú lehet. Azonban ezek csak utalások, mivel kifejezetten beállított aspektusok felülbírálják őket a megfeleltetett tulajdonságon.

Példák

Egyszerű értékobjektumok

Ez a példa egy egyszerű típust használ egy primitív típus burkolásához. Ez akkor lehet hasznos, ha azt szeretné, hogy a modell típusa konkrétabb (és ezáltal típusbiztosabb) legyen, mint egy primitív típus. Ebben a példában az a típus Dollars, amely beburkolja a decimális primitívet.

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

    public decimal Amount { get; }

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

Ez egy entitástípusban használható:

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

    public Dollars Price { get; set; }
}

A tároláskor az adatbázisban az alapvető decimal-ra kerül átalakításra:

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

Megjegyzés:

Ez az értékobjektum csak olvasható struktúraként van implementálva. Ez azt jelenti, hogy az EF Core probléma nélkül képes pillanatképeket készíteni és összehasonlítani az értékeket. További információt az Érték-összehasonlítók című témakörben talál.

Összetett érték objektumok

Az előző példában az értékobjektum-típus csak egyetlen tulajdonságot tartalmazott. Gyakoribb, hogy egy értékobjektum-típus több olyan tulajdonságot ír össze, amelyek együttesen alkotnak egy tartományfogalmi fogalmat. Például egy általános Money típus, amely az összeget és a pénznemet is tartalmazza:

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
}

Ez az értékobjektum a korábbiakhoz hasonlóan használható egy entitástípusban:

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

    public Money Price { get; set; }
}

Az értékkonverterek jelenleg csak egyetlen adatbázisoszlopra és -oszlopra konvertálhatnak értékeket. Ez a korlátozás azt jelenti, hogy az objektum összes tulajdonságértékét egyetlen oszlopértékbe kell kódolni. Ezt általában úgy kezelik, hogy szerializálják az objektumot az adatbázisba való bekerüléskor, majd ismét deszerializálják a kifelé vezető úton. Például:System.Text.Json

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

Megjegyzés:

Azt tervezzük, hogy lehetővé tesszük egy objektum több oszlopra való leképezését az EF Core jövőbeli verziójában, így itt nem kell szerializálást használni. Ezt nyomon követi a GitHub 13947-ik száma.

Megjegyzés:

Az előző példához hasonlóan ez az értékobjektum is könnyen használható szerkezetként van implementálva. Ez azt jelenti, hogy az EF Core probléma nélkül képes pillanatképeket készíteni és összehasonlítani az értékeket. További információt az Érték-összehasonlítók című témakörben talál.

Primitívek gyűjteményei

A szerializálás primitív értékek gyűjteményének tárolására is használható. Például:

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

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

A System.Text.Json ismételt használata

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> egy mutable referenciatípust jelöl. Ez azt jelenti, hogy szükség van rá ValueComparer<T> , hogy az EF Core megfelelően nyomon tudja követni és észlelni a változásokat. További információt az Érték-összehasonlítók című témakörben talál.

Értékobjektum-gyűjtemények

Az előző két példa kombinálásával értékobjektumok gyűjteményét hozhatjuk létre. Vegyük például azt a típust AnnualFinance , amely egyetlen évre modellezi a blog pénzügyeit:

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);
}

Ez a típus több korábban létrehozott típust alkot Money.

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
}

Ezután hozzáadhatunk egy gyűjteményt az AnnualFinance entitástípushoz:

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

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

És ismét szerializálással tárolja a következőt:

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()));

Megjegyzés:

A korábbiakhoz hasonlóan ehhez az átalakításhoz is szükség van egy ValueComparer<T>. További információt az Érték-összehasonlítók című témakörben talál.

Objektumok értékének megadása kulcsként

Előfordulhat, hogy a primitív kulcstulajdonságok értékobjektumokba vannak burkolva, hogy az értékek hozzárendelése további típusbiztonsági szintet adjon meg. Implementálhatunk például egy kulcstípust a blogokhoz, és egy kulcstípust a bejegyzésekhez:

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; }
}

Ezek ezután használhatók a tartománymodellben:

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; }
}

Figyelje meg, hogy Blog.Id nem lehet véletlenül hozzárendelni egy PostKey, és Post.Id nem lehet véletlenül hozzárendelni egy BlogKey. Hasonlóképpen, az Post.BlogId idegenkulcs-tulajdonsághoz hozzá kell rendelni egy BlogKey.

Megjegyzés:

A minta megjelenítése nem jelenti azt, hogy javasoljuk. Alaposan gondolja át, hogy az absztrakció ezen szintje segít-e vagy akadályozza-e a fejlesztési élményt. Emellett érdemes lehet navigációs és generált kulcsokat használni a kulcsértékek közvetlen kezelése helyett.

Ezek a kulcstulajdonságok ezután értékkonverterekkel képezhetők le:

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);
        });
}

Megjegyzés:

A konverzióval rendelkező kulcstulajdonságok csak az EF Core 7.0-tól kezdődően használhatnak generált kulcsértékeket.

használja az ulongot időbélyeg/rowversion esetén

Az SQL Server használatával támogatja az automatikus rowversion. Ezek mindig egy 8 bájtos tömb használatával lesznek beolvasva és az adatbázisba írva. A bájttömbök azonban módosítható referencia típusúak, ami némileg megnehezíti a velük való munkát. Az értékkonverterek lehetővé teszik, hogy a rowversion helyett egy ulong tulajdonsághoz legyen társítható, ami sokkal megfelelőbb és könnyebben használható, mint a bájttömb. Vegyük például egy Blog ulong egyidejűségi jogkivonattal rendelkező entitást:

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

Ez egy SQL Server oszlopra leképezhető egy értékkonverter használatával:

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

Dátumok olvasásakor adja meg a DateTime.Kind értéket

Az SQL Server elveti a DateTime.Kind jelölőt, amikor egy DateTime elemet datetime vagy datetime2 tárol. Ez azt jelenti, hogy az adatbázisból visszaérkező DateTime-értékek mindig egy DateTimeKind és Unspecified rendelkeznek.

Az értékkonverterek kétféleképpen kezelhetők. Először is az EF Core-nak van egy értékkonvertere, amely egy 8 bájtos átlátszatlan értéket hoz létre, amely megőrzi a jelölőt Kind . Például:

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

Ez lehetővé teszi, hogy a különböző Kind jelölőkkel rendelkező DateTime-értékek keveredjenek az adatbázisban.

Ezzel a megközelítéssel az a probléma, hogy az adatbázis már nem rendelkezik felismerhető datetime vagy datetime2 oszlopokkal. Ezért gyakran előfordul, hogy mindig az UTC-időt (vagy ritkábban mindig a helyi idő) tárolja, majd figyelmen kívül hagyja a Kind jelölőt, vagy állítsa be a megfelelő értékre egy értékkonverter használatával. Az alábbi konverter például biztosítja, hogy az DateTime adatbázisból beolvasott érték a DateTimeKindUTCkövetkező lesz:

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

Ha az entitáspéldányokban helyi és UTC-értékek kombinációját állítja be, akkor a konverter a beszúrás előtt a megfelelő átalakításra használható. Például:

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

Megjegyzés:

Gondosan fontolja meg az összes adatbázis-hozzáférési kód egységesítését, hogy az UTC-időt mindig használja, és csak a helyi időponttal foglalkozik, amikor adatokat ad át a felhasználóknak.

Kis- és nagybetűket figyelmen kívül hagyó sztringkulcsok használata

Egyes adatbázisok, például az SQL Server, alapértelmezés szerint kis- és nagybetűk érzéketlen szöveg-összehasonlításokat végeznek. A .NET viszont alapértelmezés szerint megkülönbözteti a kis- és nagybetűket. Ez azt jelenti, hogy egy olyan idegen kulcsérték, mint a "DotNet" megegyezik az SQL Server elsődleges kulcsértékével, de nem egyezik meg az EF Core-ban. A kulcsok érték-összehasonlítójának használatával ráveheti az EF Core-t, hogy kis- és nagybetűket ne különböztessen meg az összehasonlítások során, akárcsak az adatbázisban. Vegyük például egy sztringkulcsokkal rendelkező blog-/bejegyzésmodellt:

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; }
}

Ez nem a várt módon működik, ha egyes Post.BlogId értékek eltérő burkolattal rendelkeznek. Az ez által okozott hibák attól függenek, hogy az alkalmazás mit csinál, de általában olyan objektumdiagramokat foglalnak magukban, amelyek nem megfelelően vannak javítva , és/vagy a frissítések sikertelenek, mert az FK értéke hibás. A hiba kijavításához egy érték-összehasonlító használható:

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);
        });
}

Megjegyzés:

A .NET-sztringek és az adatbázis-sztringek összehasonlítása nem csak a kis- és nagybetűk érzékenységében való eltérésben különbözhet. Ez a minta az egyszerű ASCII-kulcsok esetében működik, de a kultúraspecifikus karaktereket tartalmazó kulcsok esetében meghiúsulhat. További információt a Rendezések és a Kis- és nagybetűk megkülönböztetése című témakörben talál.

Rögzített hosszúságú adatbázis-sztringek kezelése

Az előző példában nem volt szükség értékkonverterre. A konverter azonban hasznos lehet rögzített hosszúságú adatbázis-sztringtípusokhoz, például char(20) vagy nchar(20). A rögzített hosszúságú sztringek teljes hosszukig vannak kitöltve, amikor egy értéket beszúrnak az adatbázisba. Ez azt jelenti, hogy a "dotnet" kulcsértéket a rendszer visszaolvassa az adatbázisból "dotnet.............."-ként, ahol . szóköz karaktert jelöl. Ez így nem fog megfelelően hasonlítani a nem kipárnázott kulcsértékekkel.

Értékkonverterrel levághatja a párnázást a kulcsértékek beolvasásakor. Ez kombinálható az előző példában szereplő érték-összehasonlítóval, hogy a rögzített hosszúságú kis- és nagybetű érzéketlen ASCII-kulcsokat pontosan összehasonlítsa. Például:

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);
        });
}

Tulajdonságértékek titkosítása

Az értékkonverterek a tulajdonságértékek titkosítására használhatók, mielőtt azok az adatbázisba kerülnének, majd visszafejthetők, amikor kiolvasásra kerülnek. Például a karakterlánc megfordításának használata valódi titkosítási algoritmus helyett:

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

Megjegyzés:

Egy értékkonverteren belül jelenleg nem lehet az aktuális DbContextre vagy más munkamenetállapotra mutató hivatkozást lekérni. Ez korlátozza a használható titkosítási típusokat. Szavazzon a GitHub 11597- ös problémájára a korlátozás eltávolításához.

Figyelmeztetés

Ügyeljen arra, hogy a bizalmas adatok védelme érdekében mindenképpen tisztában legyen azzal, hogy milyen következményekkel jár, ha saját titkosítást használ. Fontolja meg ehelyett az előre összeállított titkosítási mechanizmusokat, például az SQL Serveren az Always Encryptedt .

Korlátozások

Az értékkonvertálási rendszernek néhány ismert jelenlegi korlátozása van:

  • Ahogy fentebb említettük, null nem konvertálható. Szavazzon (👍) a GitHub 13850-ös problémájára , ha erre szüksége van.
  • Nem lehet értékre konvertált tulajdonságokat lekérdezni, például a LINQ-lekérdezésekben az értékre konvertált .NET-típus tagjaira hivatkozni. Szavazzon (👍) a GitHub probléma #10434-re, ha erre szüksége van – azonban vegye fontolóra egy JSON-oszlop használatát.
  • Jelenleg nincs mód arra, hogy egy tulajdonság konvertálását több oszlopra terjessze, vagy fordítva. Szavazzon (👍) a 13947-es számú GitHub problémára, ha erre szüksége van.
  • Az értékgenerálás nem támogatott az értékkonvertereken keresztül leképezett kulcsok többségénél. Szavazzon (👍) a GitHub 11597-es kérdésre, ha ez fontos önnek.
  • Az értékkonvertálások nem hivatkozhatók az aktuális DbContext-példányra. Szavazzon (👍) a GitHub 12205-ös problémájára , ha erre szüksége van.
  • Az értékátalakított típusokat használó paraméterek jelenleg nem használhatók nyers SQL API-kban. Szavazzon (👍) a GitHub 27534-ös problémájára , ha erre szüksége van.

Ezeknek a korlátozásoknak a eltávolítását a jövőbeni kiadásokban is figyelembe vesszük.