Převody hodnot

Převaděče hodnot umožňují převod hodnot vlastností při čtení z databáze nebo zápisu do databáze. Tento převod může být z jedné hodnoty na jinou ze stejného typu (například šifrování řetězců) nebo z hodnoty jednoho typu na hodnotu jiného typu (například převod hodnot výčtu do a z řetězců v databázi.)

Tip

Celý kód v tomto dokumentu můžete spustit a ladit tak, že si stáhnete ukázkový kód z GitHubu.

Přehled

Převaděče hodnot jsou určeny z hlediska a ModelClrTypeProviderClrType. Typ modelu je typ .NET vlastnosti v typu entity. Typ zprostředkovatele je typ .NET, kterému poskytovatel databáze rozumí. Pokud chcete například uložit výčty jako řetězce v databázi, typ modelu je typ výčtu a typ zprostředkovatele je String. Tyto dva typy můžou být stejné.

Převody jsou definovány pomocí dvou Func stromů výrazů: jeden z ModelClrType do ProviderClrType a druhý z ProviderClrType do ModelClrType. Stromy výrazů se používají, aby je bylo možné zkompilovat do delegáta přístupu k databázi pro efektivní převody. Strom výrazů může obsahovat jednoduché volání metody převodu pro složité převody.

Poznámka

Vlastnost, která byla nakonfigurována pro převod hodnoty, může také potřebovat zadat ValueComparer<T>. Další informace najdete v následujících příkladech a dokumentaci k porovnání hodnot.

Konfigurace převaděče hodnot

Převody hodnot jsou nakonfigurovány v DbContext.OnModelCreating. Představte si například výčet a typ entity definovaný jako:

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

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

Převody lze nakonfigurovat OnModelCreating tak, aby ukládaly hodnoty výčtu jako řetězce, jako jsou "Donkey", "Mule" atd. V databázi. Stačí zadat jednu funkci, která se převede z ModelClrType hodnoty na ProviderClrTypea druhou pro opačný převod:

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

Poznámka

Hodnota null se nikdy nepředá převaděči hodnot. Hodnota null ve sloupci databáze je vždy null v instanci entity a naopak. Díky tomu je implementace převodů jednodušší a umožňuje je sdílet mezi vlastnostmi s možnou hodnotou null a nenulovou. Další informace najdete v tématu Problém GitHubu č. 13850 .

Hromadná konfigurace převaděče hodnot

Je běžné, že pro každou vlastnost, která používá příslušný typ CLR, se běžně konfiguruje stejný převaděč hodnot. Místo toho, abyste to udělali ručně pro každou vlastnost, můžete použít konfiguraci modelu před konvencí, abyste to udělali jednou pro celý model. Chcete-li to provést, definujte převaděč hodnot jako třídu:

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

Potom přepište ConfigureConventions v kontextu typ a nakonfigurujte převaděč následujícím způsobem:

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

Předdefinované převody

EF Core obsahuje mnoho předdefinovaných převodů, které zabraňují ručnímu zápisu funkcí převodu. Místo toho EF Core vybere převod, který se použije na základě typu vlastnosti v modelu a požadovaného typu poskytovatele databáze.

Například výčty na převody řetězců se používají jako příklad výše, ale EF Core to ve skutečnosti provede automaticky, když je typ zprostředkovatele nakonfigurovaný jako string použití obecného typu HasConversion:

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

Totéž lze dosáhnout explicitním zadáním typu sloupce databáze. Pokud je například typ entity definovaný takto:

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

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

Pak budou hodnoty výčtu uloženy jako řetězce v databázi bez jakékoli další konfigurace v OnModelCreating.

ValueConverter – třída

Volání HasConversion , jak je znázorněno výše, vytvoří ValueConverter<TModel,TProvider> instanci a nastaví ji pro vlastnost. Místo toho je možné vytvořit ValueConverter explicitně. Příklad:

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

To může být užitečné, když více vlastností používá stejný převod.

Integrované převaděče

Jak už bylo zmíněno výše, EF Core se dodává se sadou předdefinovaných ValueConverter<TModel,TProvider> tříd, které se nacházejí v Microsoft.EntityFrameworkCore.Storage.ValueConversion oboru názvů. V mnoha případech EF zvolí vhodný integrovaný převaděč na základě typu vlastnosti v modelu a typu požadovaného v databázi, jak je znázorněno výše pro výčty. Například použití .HasConversion<int>() u bool vlastnosti způsobí, že EF Core převede logické hodnoty na číselnou nulu a jednu hodnotu:

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

To je funkčně stejné jako vytvoření instance integrované BoolToZeroOneConverter<TProvider> a explicitní nastavení:

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

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

Následující tabulka shrnuje běžně používané předdefinované převody z typů modelu nebo vlastností na typy zprostředkovatele databáze. V tabulce any_numeric_type se rozumí jeden z int, , byteushortshortsbyteuintlongulong, char, , decimal, , floatnebo .double

Typ modelu nebo vlastnosti Poskytovatel/typ databáze Převod Využití
bool any_numeric_type False/true až 0/1 .HasConversion<any_numeric_type>()
any_numeric_type False/true pro libovolná dvě čísla Použití BoolToTwoValuesConverter<TProvider>
řetězec False/true na "N"/"Y" .HasConversion<string>()
řetězec False/true pro všechny dva řetězce Použití BoolToStringConverter
any_numeric_type bool 0/1 až false/true .HasConversion<bool>()
any_numeric_type Jednoduché přetypování .HasConversion<any_numeric_type>()
řetězec Číslo jako řetězec .HasConversion<string>()
Výčet any_numeric_type Číselná hodnota výčtu .HasConversion<any_numeric_type>()
řetězec Řetězcová reprezentace hodnoty výčtu .HasConversion<string>()
řetězec bool Parsuje řetězec jako logickou hodnotu. .HasConversion<bool>()
any_numeric_type Parsuje řetězec jako daný číselný typ. .HasConversion<any_numeric_type>()
char První znak řetězce .HasConversion<char>()
DateTime Parsuje řetězec jako DateTime. .HasConversion<DateTime>()
DateTimeOffset Parsuje řetězec jako DateTimeOffset. .HasConversion<DateTimeOffset>()
TimeSpan Parsuje řetězec jako timeSpan. .HasConversion<TimeSpan>()
Identifikátor GUID Parsuje řetězec jako identifikátor GUID. .HasConversion<Guid>()
byte[] Řetězec jako bajty UTF8 .HasConversion<byte[]>()
char řetězec Řetězec s jedním znakem .HasConversion<string>()
DateTime long Zakódované datum a čas zachování dateTime.Kind .HasConversion<long>()
long Klíšťata Použití DateTimeToTicksConverter
řetězec Invariantní řetězec data a času jazykové verze .HasConversion<string>()
DateTimeOffset long Kódované datum a čas s posunem .HasConversion<long>()
řetězec Invariantní řetězec data a času jazykové verze s posunem .HasConversion<string>()
TimeSpan long Klíšťata .HasConversion<long>()
řetězec Řetězec invariantní jazykové verze .HasConversion<string>()
Identifikátor URI řetězec Identifikátor URI jako řetězec .HasConversion<string>()
PhysicalAddress řetězec Adresa jako řetězec .HasConversion<string>()
byte[] Bajty v pořadí velkých koncových sítí .HasConversion<byte[]>()
IP adresa řetězec Adresa jako řetězec .HasConversion<string>()
byte[] Bajty v pořadí velkých koncových sítí .HasConversion<byte[]>()
Identifikátor GUID řetězec Identifikátor GUID ve formátu ddd-dddd-d-d-dd .HasConversion<string>()
byte[] Bajty v pořadí binární serializace .NET .HasConversion<byte[]>()

Všimněte si, že tyto převody předpokládají, že formát hodnoty je vhodný pro převod. Například převod řetězců na čísla selže, pokud řetězcové hodnoty nelze analyzovat jako čísla.

Úplný seznam předdefinovaných převaděčů je:

Všimněte si, že všechny předdefinované převaděče jsou bezstavové, takže jednu instanci může bezpečně sdílet více vlastností.

Fasety sloupců a rady pro mapování

Některé typy databází mají omezující vlastnosti, které upravují způsob ukládání dat. Tady jsou některé z nich:

  • Přesnost a měřítko pro desetinné čárky a sloupce s datem a časem
  • Velikost a délka binárních a řetězcových sloupců
  • Unicode pro sloupce řetězců

Tyto omezující vlastnosti lze nakonfigurovat běžným způsobem pro vlastnost, která používá převaděč hodnot, a použije se na převedený typ databáze. Například při převodu z výčtu na řetězce můžeme určit, že sloupec databáze by měl být jiný než Unicode, a uložit až 20 znaků:

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

Nebo při vytváření převaděče explicitně:

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

Výsledkem je varchar(20) sloupec při použití migrací EF Core na SQL Server:

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

Pokud by však měly být varchar(20)ve výchozím nastavení všechny EquineBeast sloupce , pak mohou být tyto informace předány převaděči hodnot jako ConverterMappingHints. Příklad:

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

Teď, když se tento převaděč použije, bude sloupec databáze bez kódování Unicode s maximální délkou 20. Jedná se však pouze o rady, protože jsou přepsány všemi omezujícími vlastnostmi explicitně nastavenými na mapované vlastnosti.

Příklady

Jednoduché objekty hodnot

Tento příklad používá jednoduchý typ k zabalení primitivního typu. To může být užitečné v případě, že chcete, aby byl typ v modelu konkrétnější (a proto bezpečnější než primitivní typ). V tomto příkladu je Dollarstento typ , který zabalí desítkové primitivy:

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

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

To lze použít v typu entity:

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

    public Dollars Price { get; set; }
}

A při uložení v databázi se převede na podklad decimal :

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

Poznámka

Tento objekt hodnoty je implementován jako struktura jen pro čtení. To znamená, že EF Core může pořizovat snímky a porovnávat hodnoty bez problému. Další informace najdete v tématu Porovnání hodnot.

Složené objekty hodnot

V předchozím příkladu typ objektu hodnoty obsahoval pouze jednu vlastnost. Běžnější je vytvoření více vlastností, které společně tvoří koncept domény. Například obecný Money typ, který obsahuje částku i měnu:

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
}

Tento objekt hodnoty lze použít v typu entity jako předtím:

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

    public Money Price { get; set; }
}

Převaděče hodnot můžou v současné době převádět pouze hodnoty do a z jednoho sloupce databáze. Toto omezení znamená, že všechny hodnoty vlastností z objektu musí být kódovány do jedné hodnoty sloupce. Obvykle se to zpracovává serializací objektu, protože jde do databáze, a pak deserializaci znovu na cestě ven. Například pomocí :System.Text.Json

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

Poznámka

Plánujeme povolit mapování objektu na více sloupců v budoucí verzi EF Core, což odstraňuje nutnost použití serializace zde. Tento problém sleduje GitHub #13947.

Poznámka

Stejně jako v předchozím příkladu se tento objekt hodnoty implementuje jako struktura jen pro čtení. To znamená, že EF Core může pořizovat snímky a porovnávat hodnoty bez problému. Další informace najdete v tématu Porovnání hodnot.

Kolekce primitiv

Serializace se dá použít také k uložení kolekce primitivních hodnot. Příklad:

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

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

Znovu použijte 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> představuje proměnlivý typ odkazu. To znamená, že je potřeba, ValueComparer<T> aby EF Core mohl správně sledovat a zjišťovat změny. Další informace najdete v tématu Porovnání hodnot.

Kolekce objektů hodnot

Kombinací předchozích dvou příkladů můžeme vytvořit kolekci hodnotových objektů. Představte si AnnualFinance například typ, který modeluje finance blogu za jeden rok:

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

Tento typ tvoří několik typů, které Money jsme vytvořili dříve:

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
}

Pak můžeme do našeho typu entity přidat kolekci AnnualFinance :

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

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

A znovu použijte serializaci k uložení:

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

Poznámka

Stejně jako předtím tento převod vyžaduje ValueComparer<T>. Další informace najdete v tématu Porovnání hodnot.

Objekty hodnot jako klíče

Někdy můžou být primitivní vlastnosti klíče zabalené do objektů hodnot, aby se při přiřazování hodnot přidala další úroveň zabezpečení typu. Mohli bychom například implementovat typ klíče pro blogy a typ klíče pro příspěvky:

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

Ty se pak dají použít v doménovém modelu:

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

Všimněte si, že Blog.Id nelze omylem přiřadit PostKey, a Post.Id nelze omylem přiřadit BlogKey. Podobně musí být vlastnost cizího Post.BlogId klíče přiřazena .BlogKey

Poznámka

Zobrazení tohoto vzoru neznamená, že ho doporučujeme. Pečlivě zvažte, jestli tato úroveň abstrakce pomáhá nebo brání vašemu vývojovému prostředí. Zvažte také použití navigace a vygenerovaných klíčů místo přímé práce s hodnotami klíčů.

Tyto klíčové vlastnosti je pak možné mapovat pomocí převaděčů hodnot:

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

Poznámka

Klíčové vlastnosti s převody můžou používat pouze vygenerované hodnoty klíče od EF Core 7.0.

Použití příkazu ulong pro časové razítko nebo rowversion

SQL Server podporuje automatickou optimistickou souběžnost pomocí binárních sloupců s 8 bajtyrowversion/timestamp. Ty se vždy čtou a zapisují do databáze pomocí pole 8 bajtů. Bajtová pole jsou však proměnlivým referenčním typem, což je poněkud bolestné při řešení. Převaděče hodnot umožňují rowversion místo toho namapovat na ulong vlastnost, která je mnohem vhodnější a snadno použitelná než pole bajtů. Představte si například entitu Blog s tokenem ulong concurrency:

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

To lze namapovat na sloupec SERVERU SQL rowversion pomocí převaděče hodnot:

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

Určení dateTime.Kind při čtení kalendářních dat

SQL Server zahodí DateTime.Kind příznak při ukládání příznaku DateTime jako nebo datetime2datetime . To znamená, že hodnoty DateTime vracející se z databáze mají vždy hodnotu DateTimeKindUnspecified.

Převaděče hodnot lze použít dvěma způsoby, jak to vyřešit. Za prvé, EF Core má převaděč hodnot, který vytvoří 8bajtů neprůhlenou hodnotu, která zachovává Kind příznak. Příklad:

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

To umožňuje v databázi kombinovat hodnoty DateTime s různými Kind příznaky.

Problém s tímto přístupem spočívá v tom, že databáze již nemá rozpoznatelné datetime sloupce nebo datetime2 sloupce. Místo toho je běžné vždy ukládat čas UTC (nebo méně často místní čas) a pak buď ignorovat Kind příznak, nebo ho nastavit na odpovídající hodnotu pomocí převaděče hodnot. Následující převaděč například zajistí, že DateTime hodnota načtená z databáze bude obsahovat DateTimeKindUTC:

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

Pokud se v instancích entit nastavuje kombinace místních hodnot a hodnot UTC, lze převaděč před vložením použít k převodu odpovídajícím způsobem. Příklad:

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

Poznámka

Pečlivě zvažte sjednocení veškerého přístupového kódu databáze, aby se vždy používal čas UTC, a to pouze při prezentování dat uživatelům v místním čase.

Použití řetězcových klíčů bez rozlišování velkých a velkých písmen

Některé databáze, včetně SQL Serveru, ve výchozím nastavení provádějí porovnání řetězců bez rozlišování malých a velkých písmen. .NET naopak ve výchozím nastavení provádí porovnávání řetězců s rozlišováním velkých a malých písmen. To znamená, že hodnota cizího klíče, jako je DotNet, bude odpovídat hodnotě primárního klíče "dotnet" na SQL Serveru, ale nebude se shodovat s hodnotou primárního klíče na SQL Serveru, ale nebude se shodovat s hodnotou primárního klíče v EF Core. Porovnávací nástroj pro hodnoty pro klíče lze použít k vynucení porovnání řetězců bez rozlišování velkých a malých písmen, jako je v databázi. Představte si například model blogů a příspěvků s řetězcovými klíči:

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

To nebude fungovat podle očekávání, pokud některé hodnoty Post.BlogId mají různá velikost velikostí. Chyby způsobené tím budou záviset na tom, co aplikace dělá, ale obvykle zahrnují grafy objektů, které nejsou správně opravené , a/nebo aktualizace, které selžou, protože hodnota FK je nesprávná. K opravě tohoto parametru je možné použít porovnávač hodnot:

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

Poznámka

Porovnání řetězců .NET a porovnání databázových řetězců se můžou lišit více než jen citlivostí velkých a malých písmen. Tento vzor funguje pro jednoduché klíče ASCII, ale u klíčů s libovolným typem znaků specifických pro jazykovou verzi může selhat. Další informace najdete v tématu Kolace a citlivost písmen.

Zpracování databázových řetězců s pevnou délkou

Předchozí příklad nepotřeboval převaděč hodnot. Převaděč však může být užitečný pro typy řetězců databáze s pevnou délkou, jako char(20) nebo nchar(20). Řetězce s pevnou délkou jsou vycpané na celou délku při každém vložení hodnoty do databáze. To znamená, že hodnota klíče "dotnet" se načte z databáze jako "dotnet..............", kde . představuje znak mezery. To pak nebude správně porovnávat s hodnotami klíčů, které nejsou vycpané.

Převaděč hodnot lze použít k oříznutí odsazení při čtení hodnot klíče. To lze zkombinovat s porovnávačem hodnot v předchozím příkladu, aby bylo možné správně porovnat klíče ASCII bez rozlišování velkých a velkých písmen. Příklad:

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

Šifrování hodnot vlastností

Převaděče hodnot lze použít k šifrování hodnot vlastností před jejich odesláním do databáze a pak je dešifrovat na cestě. Například použití návratu řetězce jako náhrady skutečného šifrovacího algoritmu:

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

Poznámka

V současné době neexistuje způsob, jak získat odkaz na aktuální DbContext nebo jiný stav relace z převaděče hodnot. Tím se omezí druhy šifrování, které je možné použít. Hlasujte pro problém GitHubu č. 11597 , abyste toto omezení odebrali.

Upozorňující

Pokud za účelem ochrany citlivých dat zahrnete vlastní šifrování, ujistěte se, že rozumíte všem důsledkům. Zvažte místo toho použití předem připravených mechanismů šifrování, jako je funkce Always Encrypted na SQL Serveru.

Omezení

Existuje několik známých aktuálních omezení systému převodu hodnot:

  • Jak je uvedeno výše, null nelze převést. Pokud je to něco, co potřebujete, hlasujte (👍) pro problém GitHubu #13850 .
  • V dotazech LINQ není možné dotazovat na vlastnosti převedené na hodnoty, například odkazy na členy typu .NET převedené na hodnotu. Pokud je to něco, co potřebujete, hlasujte (👍) pro problém GitHubu #10434 , ale zvažte použití sloupce JSON.
  • V současné době neexistuje způsob, jak rozložit převod jedné vlastnosti na více sloupců nebo naopak. Pokud je to něco, co potřebujete, hlasujte (👍) pro problém GitHubu č. 13947 .
  • Generování hodnot není podporováno pro většinu klíčů mapovaných prostřednictvím převaděčů hodnot. Pokud je to něco, co potřebujete, hlasujte prosím (👍) pro problém GitHubu č. 11597 .
  • Převody hodnot nemohou odkazovat na aktuální instanci DbContext. Pokud je to něco, co potřebujete, hlasujte (👍) pro problém GitHubu č. 12205 .
  • Parametry používající typy převedené hodnotou se v současné době nedají použít v nezpracovaných rozhraních SQL API. Pokud je to něco, co potřebujete, hlasujte (👍) pro problém GitHubu #27534 .

Odebrání těchto omezení se považuje za budoucí verze.