Dela via


Värdekonverteringar

Värdekonverterare tillåter att egenskapsvärden konverteras när du läser från eller skriver till databasen. Den här konverteringen kan vara från ett värde till ett annat av samma typ (till exempel kryptering av strängar) eller från ett värde av en typ till ett värde av en annan typ (till exempel konvertera uppräkningsvärden till och från strängar i databasen.)

Tips/Råd

Du kan köra och felsöka all kod i det här dokumentet genom att ladda ned exempelkoden från GitHub.

Översikt

Värdekonverterare anges i termer av en ModelClrType och en ProviderClrType. Modelltypen är .NET-typen för egenskapen i entitetstypen. Providertypen är den .NET-typ som förstås av databasprovidern. Om du till exempel vill spara enums som strängar i databasen är modelltypen typen av enum och providertypen är String. Dessa två typer kan vara samma.

Konverteringar definieras med två Func uttrycksträd: en från ModelClrType till ProviderClrType och en från ProviderClrType till ModelClrType. Uttrycksträd används så att de kan kompileras till databasåtkomstdelegaten för effektiva konverteringar. Uttrycksträdet kan innehålla ett enkelt anrop till en konverteringsmetod för komplexa konverteringar.

Anmärkning

En egenskap som har konfigurerats för värdekonvertering kan också behöva ange en ValueComparer<T>. Se exemplen nedan och värdejämföreldokumentationen för mer information.

Konfigurera en värdekonverterare

Värdekonverteringar konfigureras i DbContext.OnModelCreating. Ta till exempel en uppräkningstyp och en entitetstyp som är definierade som:

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

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

Konverteringar kan konfigureras i OnModelCreating för att lagra uppräkningsvärden som strängar som "Åsna", "Mule" osv. i databasen. Du behöver bara ange en funktion som konverterar från ModelClrType till ProviderClrTypeoch en annan för motsatt konvertering:

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

Anmärkning

Ett null värde skickas aldrig till en värdekonverterare. En null i en databaskolumn är alltid null i entitetsinstansen och vice versa. Detta gör implementeringen av konverteringar enklare och gör att de kan delas mellan null- och icke-nullbara egenskaper. Mer information finns i GitHub-problem #13850 .

Masskonfigurera en värdekonverterare

Det är vanligt att samma värdekonverterare konfigureras för varje egenskap som använder relevant CLR-typ. I stället för att göra detta manuellt för varje egenskap kan du använda förkonvent modellkonfiguration för att göra detta en gång för hela modellen. Det gör du genom att definiera värdekonverteraren som en klass:

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

Åsidosätt ConfigureConventions sedan i kontexttypen och konfigurera konverteraren på följande sätt:

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

Fördefinierade konverteringar

EF Core innehåller många fördefinierade konverteringar som undviker behovet av att skriva konverteringsfunktioner manuellt. I stället väljer EF Core den konvertering som ska användas baserat på egenskapstypen i modellen och den begärda databasprovidertypen.

Till exempel används konverteringar från enum till sträng som ett exempel ovan, men EF Core gör det faktiskt automatiskt när providertypen är konfigurerad att använda den generiska typen av string.

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

Samma sak kan uppnås genom att uttryckligen ange databaskolumntypen. Om t.ex. entitetstypen har definierats så här:

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

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

Sedan sparas enum-värdena som strängar i databasen utan ytterligare konfiguration i OnModelCreating.

Klassen ValueConverter

Om du anropar HasConversion enligt ovan skapar du en ValueConverter<TModel,TProvider> instans och ställer in den på egenskapen. Det ValueConverter kan i stället skapas explicit. Till exempel:

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

Detta kan vara användbart när flera egenskaper använder samma konvertering.

Inbyggda konverterare

Som nämnts ovan levereras EF Core med en uppsättning fördefinierade ValueConverter<TModel,TProvider> klasser som finns i Microsoft.EntityFrameworkCore.Storage.ValueConversion namnområdet. I många fall väljer EF lämplig inbyggd konverterare baserat på typen av egenskapen i modellen och den typ som efterfrågades i databasen, som demonstrerat ovan för uppräkningar. Om du till exempel använder .HasConversion<int>() på en bool egenskap kan EF Core konvertera bool-värden till numeriska noll och ett värden:

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

Detta är funktionellt detsamma som att skapa en instans av den inbyggda BoolToZeroOneConverter<TProvider> och ange den explicit:

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

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

I följande tabell sammanfattas vanliga fördefinierade konverteringar från modell-/egenskapstyper till databasprovidertyper. I tabellen any_numeric_type betyder något av int, short, long, byte, uint, ushort, ulong, sbyte, char, , decimal, floateller double.

Modell/egenskapstyp Provider/databastyp Omvandling Användning
Bool any_numerisk_typ Falskt/sant till 0/1 .HasConversion<any_numeric_type>()
any_numerisk_typ Sant/falskt för vilka två tal som helst Använd BoolToTwoValuesConverter<TProvider>
snöre Falskt/sant till "N"/"Y" .HasConversion<string>()
snöre Falskt/sant för vilka två strängar som helst Använd BoolToStringConverter
any_numerisk_typ Bool 0/1 blir falskt/sant .HasConversion<bool>()
any_numerisk_typ Enkel gjutning .HasConversion<any_numeric_type>()
snöre Talet som en sträng .HasConversion<string>()
Räkna upp any_numerisk_typ Det numeriska värdet för uppräkningen .HasConversion<any_numeric_type>()
snöre Strängrepresentationen av uppräkningsvärdet .HasConversion<string>()
snöre Bool Parsar strängen som en bool .HasConversion<bool>()
any_numerisk_typ Parsar strängen som den angivna numeriska typen .HasConversion<any_numeric_type>()
röding Strängens första tecken .HasConversion<char>()
Datum och tid Analyserar strängen som ett datum och tid .HasConversion<DateTime>()
DateTimeOffset (tidpunkt med tidsförskjutning) Parsar strängen som en DateTimeOffset .HasConversion<DateTimeOffset>()
Tidsintervall Parsar strängen som en TimeSpan .HasConversion<TimeSpan>()
Guide Parsar strängen som ett guid .HasConversion<Guid>()
byte[] Strängen som UTF8-byten .HasConversion<byte[]>()
röding snöre En sträng med ett tecken .HasConversion<string>()
Datum och tid lång Kodat datum/tid som bevarar DateTime.Kind .HasConversion<long>()
lång Fästingar Använd DateTimeToTicksConverter
snöre Invariant kulturdatum/tidssträng .HasConversion<string>()
DateTimeOffset (tidpunkt med tidsförskjutning) lång Kodat datum/tid med förskjutning .HasConversion<long>()
snöre Invariant kulturdatum/tidssträng med förskjutning .HasConversion<string>()
Tidsintervall lång Fästingar .HasConversion<long>()
snöre Invariant kulturtidsintervallsträng .HasConversion<string>()
Uri snöre URI:n som en sträng .HasConversion<string>()
FysiskAdress snöre Adressen som en sträng .HasConversion<string>()
byte[] Byte i storslutsnätverksordning .HasConversion<byte[]>()
IP-adress snöre Adressen som en sträng .HasConversion<string>()
byte[] Byte i storslutsnätverksordning .HasConversion<byte[]>()
Guide snöre GUID i formatet "dddddddd-dddd-dddd-dddd-dddddddddddd" .HasConversion<string>()
byte[] Byte i .NET:s binära serialiseringsordning .HasConversion<byte[]>()

Observera att dessa konverteringar förutsätter att formatet på värdet är lämpligt för konverteringen. Det går till exempel inte att konvertera strängar till tal om strängvärdena inte kan parsas som tal.

Den fullständiga listan över inbyggda konverterare är:

Observera att alla inbyggda konverterare är tillståndslösa, vilket gör att en enda instans tryggt kan delas mellan flera egenskaper.

Kolumnfasetter och mappningstips

Vissa databastyper har fasetter som ändrar hur data lagras. Dessa inkluderar:

  • Precision och skalning för decimaler och datum/tid-kolumner
  • Storlek/längd för binära kolumner och strängkolumner
  • Unicode för strängkolumner

Dessa fasetter kan konfigureras på normalt sätt för en egenskap som använder en värdekonverterare och som gäller för den konverterade databastypen. När du till exempel konverterar från en uppräkningstyp till strängar kan du ange att databaskolumnen ska vara icke-unicode och lagra upp till 20 tecken.

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

Eller när du explicit skapar konverteraren:

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

Detta resulterar i en varchar(20) kolumn när du använder EF Core-migreringar mot SQL Server:

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

Men om alla EquineBeast kolumner som standard ska vara varchar(20), kan den här informationen ges till värdekonverteraren som en ConverterMappingHints. Till exempel:

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

Varje gång denna konverterare används kommer databaskolumnen att vara icke-unicode med en maxlängd på 20. Dessa är dock bara indikationer eftersom de åsidosätts av alla facetter som uttryckligen anges i den mappade egenskapen.

Exempel

Enkla värdeobjekt

I det här exemplet används en enkel typ för att omsluta en primitiv typ. Detta kan vara användbart när du vill att typen i din modell ska vara mer specifik (och därmed mer typsäker) än en primitiv typ. I det här exemplet är Dollarsden typen , som omsluter det primitiva decimaltecknet:

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

    public decimal Amount { get; }

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

Detta kan användas i en entitetstyp:

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

    public Dollars Price { get; set; }
}

Och konverteras till den underliggande decimal när den lagras i databasen:

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

Anmärkning

Det här värdeobjektet implementeras som en skrivskyddad struct. Det innebär att EF Core kan ta ögonblicksbilder och jämföra värden utan problem. Mer information finns i Värdejämförare .

Sammansatta värdeobjekt

I föregående exempel innehöll värdeobjekttypen endast en enda egenskap. Det är vanligare att en värdeobjekttyp skapar flera egenskaper som tillsammans utgör ett domänkoncept. Till exempel en allmän Money typ som innehåller både beloppet och valutan:

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
}

Det här värdeobjektet kan användas i en entitetstyp som tidigare:

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

    public Money Price { get; set; }
}

Värdekonverterare kan för närvarande bara konvertera värden till och från en enda databaskolumn. Den här begränsningen innebär att alla egenskapsvärden från objektet måste kodas till ett enda kolumnvärde. Detta hanteras vanligtvis genom att serialisera objektet när det hamnar i databasen och sedan deserialisera det igen på vägen ut. Du kan till exempel använda System.Text.Json:

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

Anmärkning

Vi planerar att tillåta mappning av ett objekt till flera kolumner i en framtida version av EF Core, vilket tar bort behovet av att använda serialisering här. Detta spåras av GitHub-problemet #13947.

Anmärkning

Precis som i föregående exempel implementeras det här värdeobjektet som en readonly strukt. Det innebär att EF Core kan ta ögonblicksbilder och jämföra värden utan problem. Mer information finns i Värdejämförare .

Samlingar av primitiver

Serialisering kan också användas för att lagra en samling primitiva värden. Till exempel:

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

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

Använd System.Text.Json igen:

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> representerar en föränderlig referenstyp. Det innebär att en ValueComparer<T> behövs så att EF Core kan spåra och identifiera ändringar korrekt. Mer information finns i Värdejämförare .

Samlingar med värdeobjekt

Genom att kombinera de föregående två exemplen tillsammans kan vi skapa en samling värdeobjekt. Tänk dig till exempel en AnnualFinance typ som modellerar bloggens ekonomi för ett enda år:

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

Den här typen består av flera av de typer som Money vi skapade tidigare:

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
}

Sedan kan vi lägga till en samling av AnnualFinance i vår entitetstyp:

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

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

Och använd återigen serialisering för att lagra följande:

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

Anmärkning

Precis som tidigare kräver den här konverteringen en ValueComparer<T>. Mer information finns i Värdejämförare .

Värdeobjekt som nycklar

Ibland kan primitiva nyckelegenskaper omslutas i värdeobjekt för att lägga till ytterligare en nivå av typsäkerhet vid tilldelning av värden. Vi kan till exempel implementera en nyckeltyp för bloggar och en nyckeltyp för inlägg:

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

Dessa kan sedan användas i domänmodellen:

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

Observera att Blog.Id inte kan tilldelas en PostKey av misstag, och Post.Id inte kan tilldelas en BlogKey av misstag. Post.BlogId På samma sätt måste egenskapen främmande nyckel tilldelas en BlogKey.

Anmärkning

Att visa det här mönstret betyder inte att vi rekommenderar det. Fundera noga på om den här abstraktionsnivån hjälper eller hämmar din utvecklingsupplevelse. Överväg också att använda navigering och genererade nycklar i stället för att hantera nyckelvärden direkt.

Dessa nyckelegenskaper kan sedan mappas med hjälp av värdekonverterare:

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

Anmärkning

Nyckelegenskaper med konverteringar kan bara använda genererade nyckelvärden från och med EF Core 7.0.

Använd ulong för tidsstämpel/rowversion

SQL Server stöder automatisk optimistisk samtidighet med hjälp av binära rowversion/timestamp 8 byte-kolumner. Dessa läss alltid från och skrivs till databasen med hjälp av en 8-bytesmatris. Byte-matriser är dock en föränderlig referenstyp, vilket gör dem något smärtsamma att hantera. Värdekonverterare tillåter rowversion att i stället mappas till en ulong egenskap, vilket är mycket lämpligare och enklare att använda än bytematrisen. Tänk dig till exempel en Blog entitet med en ulong samtidighetstoken:

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

Detta kan mappas till en SQL Server-kolumn rowversion med hjälp av en värdekonverterare:

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

När du läser datum, ange DateTime.Kind

SQL Server tar bort DateTime.Kind flaggan när den DateTime lagras som en datetime eller datetime2. Det innebär att DateTime-värden som kommer tillbaka från databasen alltid har en DateTimeKind av Unspecified.

Värdekonverterare kan användas på två sätt för att hantera detta. För det första har EF Core en värdekonverterare som skapar ett ogenomskinliga 8 byte-värde som bevarar Kind flaggan. Till exempel:

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

På så sätt kan DateTime-värden med olika Kind flaggor blandas i databasen.

Problemet med den här metoden är att databasen inte längre har identifierbara datetime eller datetime2 kolumner. Så i stället är det vanligt att alltid lagra UTC-tid (eller, mindre vanligt, alltid lokal tid) och sedan antingen ignorera Kind flaggan eller ställa in den på lämpligt värde med hjälp av en värdekonverterare. Konverteraren nedan säkerställer till exempel att värdet som har lästs från databasen har DateTimeDateTimeKind.

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

Om en blandning av lokala värden och UTC-värden anges i entitetsinstanser kan konverteraren användas för att konvertera korrekt innan den infogas. Till exempel:

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

Anmärkning

Överväg noggrant att ena all databasåtkomstkod för att använda UTC-tid hela tiden, bara hantera lokal tid när du presenterar data för användare.

Använda skiftlägesokänsliga strängnycklar

Vissa databaser, inklusive SQL Server, utför skiftlägesokänsliga strängjämförelser som standard. .NET å andra sidan utför skiftlägeskänsliga strängjämförelser som standard. Det innebär att ett sekundärnyckelvärde som "DotNet" matchar det primära nyckelvärdet "dotnet" på SQL Server, men inte matchar det i EF Core. En värdejämförare för nycklar kan användas för att tvinga EF Core att göra skiftlägesokänsliga strängjämförelser, precis som i databasen. Överväg till exempel en blogg/inläggsmodell med strängnycklar:

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

Detta fungerar inte som förväntat om vissa av Post.BlogId värdena har olika hölje. De fel som orsakas av detta beror på vad applikationen gör, men omfattar vanligtvis grafer över objekt som inte har korrigerats korrekt och/eller uppdateringar som misslyckas eftersom FK-värdet är felaktigt. En värdekomparator kan användas för att korrigera detta.

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

Anmärkning

.NET-strängjämförelser och databassträngjämförelser kan skilja sig åt på fler sätt än bara skiftlägeskänslighet. Det här mönstret fungerar för enkla ASCII-nycklar, men kan misslyckas för nycklar med alla typer av kulturspecifika tecken. Se Sortering och Skiftlägeskänslighet för mer information.

Hantera databassträngar med fast längd

I föregående exempel behövdes ingen värdekonverterare. En konverterare kan dock vara användbar för databassträngstyper med fast längd som char(20) eller nchar(20). Strängar med fast längd är vadderade till sin fulla längd när ett värde infogas i databasen. Det innebär att ett nyckelvärde för "dotnet" kommer att läsas tillbaka från databasen som "dotnet..............", där . representerar ett blankstegstecken. Detta jämförs sedan inte korrekt med nyckelvärden som inte är vadderade.

En värdekonverterare kan användas för att trimma utfyllnaden när du läser nyckelvärden. Detta kan kombineras med värdejämföraren i föregående exempel för att jämföra skiftlägesokänsliga ASCII-nycklar med fast längd korrekt. Till exempel:

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

Kryptera egenskapsvärden

Värdekonverterare kan användas för att kryptera egenskapsvärden innan de skickas till databasen och sedan dekryptera dem på vägen ut. Du kan till exempel använda strängåtervändning som ersättning för en riktig krypteringsalgoritm:

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

Anmärkning

Det finns för närvarande inget sätt att hämta en referens till den aktuella DbContext eller något annat sessionstillstånd inifrån en värdekonverterare. Detta begränsar vilka typer av kryptering som kan användas. Rösta på GitHub-problem #11597 om du vill att den här begränsningen ska tas bort.

Varning

Se till att förstå alla konsekvenser om du distribuerar din egen kryptering för att skydda känsliga data. Överväg i stället att använda fördefinierade krypteringsmekanismer, till exempel Always Encrypted på SQL Server.

Begränsningar

Det finns några kända aktuella begränsningar i värdekonverteringssystemet:

  • Som nämnts ovan null kan inte konverteras. Rösta (👍) för GitHub-nummer #13850 om det här är något du behöver.
  • Det går inte att fråga efter värdekonverterade egenskaper, t.ex. referensmedlemmar på den värdekonverterade .NET-typen i dina LINQ-frågor. Rösta (👍) för GitHub-problem #10434 om det här är något du behöver – men överväg att använda en JSON-kolumn i stället.
  • Det finns för närvarande inget sätt att sprida en konvertering av en egenskap till flera kolumner eller vice versa. Rösta (👍) för GitHub-nummer #13947 om det här är något du behöver.
  • Värdegenerering stöds inte för de flesta nycklar som mappas via värdekonverterare. Rösta (👍) för GitHub-nummer #11597 om detta är något du behöver.
  • Värdekonverteringar kan inte referera till den aktuella DbContext-instansen. Rösta (👍) för GitHub-nummer #12205 om det här är något du behöver.
  • Parametrar som använder värdekonverterade typer kan för närvarande inte användas i sql-API:er utan rådata. Rösta (👍) för GitHub-nummer #27534 om det här är något du behöver.

Borttagning av dessa begränsningar övervägs för framtida versioner.