Sdílet prostřednictvím


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.)

Návod

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 specifikovány z hlediska ModelClrType a ProviderClrType. Typ modelu je .NET typ 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 v OnModelCreating tak, aby ukládaly hodnoty výčtu jako řetězce, jako jsou "Donkey", "Mule" atd.; v databázi. Stačí zadat jednu funkci, která převádí z ModelClrType na ProviderClrType, a 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))
    {
    }
}

Poté přepište ConfigureConventions ve vašem typu kontextu 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 konverze z výčtu na řetězec jsou uvedeny jako příklad výše, ale EF Core to automaticky provede, když je typ poskytovatele nakonfigurován jako string, s použitím 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.

Třída ValueConverter

Volání HasConversion, jak je znázorněno výše, vytvoří instanci ValueConverter<TModel,TProvider> a nastaví ji jako 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);
}

Toto 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 vestavěného BoolToZeroOneConverter<TProvider> a její 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, short, long, byte, uint, ushort, ulong, sbyte, char, decimal, float nebo double.

Typ modelu nebo vlastnosti Poskytovatel/typ databáze Převod Využití
Booleova hodnota jakýkoli_numerický_typ False/true na 0/1 .HasConversion<any_numeric_type>()
jakýkoli_numerický_typ False/true pro libovolná dvě čísla Použijte BoolToTwoValuesConverter<TProvider>
řetězec Nepravda/pravda na "N"/"Y" .HasConversion<string>()
řetězec False/true pro všechny dva řetězce Použijte BoolToStringConverter
jakýkoli_numerický_typ Booleova hodnota 0/1 až false/true .HasConversion<bool>()
jakýkoli_numerický_typ Jednoduché přetypování .HasConversion<any_numeric_type>()
řetězec Číslo jako řetězec .HasConversion<string>()
Výčet jakýkoli_numerický_typ Číselná hodnota výčtového typu .HasConversion<any_numeric_type>()
řetězec Řetězcová reprezentace hodnoty výčtu .HasConversion<string>()
řetězec Booleova hodnota Parsuje řetězec jako datový typ bool. .HasConversion<bool>()
jakýkoli_numerický_typ Parsuje řetězec jako daný číselný typ. .HasConversion<any_numeric_type>()
char První znak řetězce .HasConversion<char>()
Datum a čas Parsuje řetězec jako DateTime. .HasConversion<DateTime>()
Posun data a času Parsuje řetězec jako DateTimeOffset. .HasConversion<DateTimeOffset>()
Časový rozsah Analyzuje řetězec jako TimeSpan. .HasConversion<TimeSpan>()
Průvodce Parsuje řetězec jako GUID. .HasConversion<Guid>()
byte[] Řetězec jako bajty UTF8 .HasConversion<byte[]>()
char řetězec Řetězec s jedním znakem .HasConversion<string>()
Datum a čas dlouhý Zakódované datum a čas při zachování DateTime.Kind .HasConversion<long>()
dlouhý Klíšťata Použijte DateTimeToTicksConverter
řetězec Řetězec data a času nezávislý na kulturní verzi .HasConversion<string>()
Posun data a času dlouhý Kódované datum a čas s časovým posunem .HasConversion<long>()
řetězec Neutrální kulturní nastavení řetězce data a času s časovým posunem .HasConversion<string>()
Časový rozsah dlouhý Klíšťata .HasConversion<long>()
řetězec Řetězec časového rozsahu invariantní kultury .HasConversion<string>()
Identifikátor URI řetězec Identifikátor URI jako řetězec .HasConversion<string>()
Fyzická adresa řetězec Adresa jako řetězec .HasConversion<string>()
byte[] Bajty v síťovém pořadí big-endian .HasConversion<byte[]>()
IP adresa řetězec Adresa jako řetězec .HasConversion<string>()
byte[] Bajty v síťovém pořadí big-endian .HasConversion<byte[]>()
Průvodce řetězec Identifikátor GUID ve formátu 'dddddddd-dddd-dddd-dddd-dddddddddddd' .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 integrovaných převaděčů je:

Všimněte si, že všechny integrované 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á čísla a sloupce typu datum a čas
  • Velikost a délka binárních a řetězcových sloupců
  • Unicode pro řetězcové sloupce

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 ne-Unicode a pojmout 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 toho je varchar(20) sloupec při použití migrací EF Core proti SQL Serveru:

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

Pokud by však ve výchozím nastavení měly být všechny EquineBeastvarchar(20) 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 typ Dollars, který obaluje desetinný primitiv:

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é hodnotové objekty

V předchozím příkladu typ objektu hodnoty obsahoval pouze jednu vlastnost. Je běžnější, že typ hodnotového objektu zahrnuje více vlastností, které společně tvoří koncept v doméně. 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, když jde do databáze, a poté se deserializuje při návratu. 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 primitivních datových typů

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 zahrnuje několik z typů Money, které 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 vlastnosti cizího klíče Post.BlogId 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 mohou používat pouze automaticky generované hodnoty klíče, počínaje verzí EF Core 7.0.

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

SQL Server podporuje automatickou optimistickou souběžnost pomocí 8-bytových binárních rowversion/timestamp sloupců. Ty se vždy čtou a zapisují do databáze pomocí 8-bajtového pole. Bajtová pole jsou však proměnlivým referenčním typem, který z nich dělá poněkud nepříjemnou záležitost při zpracování. 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ů. Například zvažte entitu Blog s tokenem souběžnosti typu ulong:

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í DateTime jako datetime nebo datetime2. 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í malý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 touto hodnotou v EF Core. Porovnávač hodnot pro klíče lze použít k vynucení porovnávání řetězců bez rozlišování velkých a malých písmen, což je běžné v databázích. 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; }
}

Nebude to fungovat podle očekávání, pokud některé hodnoty Post.BlogId mají různou velikost písmen. 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. Pro další informace se podívejte na Kolace a citlivost na velikost 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 zarovnané.

Převaděč hodnot lze použít k oříznutí mezer při čtení hodnot klíče. To lze zkombinovat s porovnávačem hodnot v předchozím příkladu pro správné porovnání klíčů ASCII bez rozlišování velikosti 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 následně k jejich dešifrování při načítání z databáze. Například použití obrácení řetězce jako náhradu 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 v převaděči hodnot získat odkaz na aktuální DbContext ani jiný stav relace. Tím se omezí druhy šifrování, které je možné použít. Hlasujte pro problém GitHubu č. 11597 , abyste toto omezení odebrali.

Varování

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 zvažuje pro budoucí vydání.