WertkonvertierungenValue Conversions

Mithilfe von Wert Konvertern können Eigenschaftswerte beim Lesen aus der Datenbank oder beim Schreiben in die Datenbank konvertiert werden.Value converters allow property values to be converted when reading from or writing to the database. Diese Konvertierung kann von einem Wert in einen anderen desselben Typs (z. b. das Verschlüsseln von Zeichen folgen) oder von einem Wert eines Typs zu einem Wert eines anderen Typs erfolgen (z. b. beim Konvertieren von Enumerationswerten in und aus Zeichen folgen in der Datenbank).This conversion can be from one value to another of the same type (for example, encrypting strings) or from a value of one type to a value of another type (for example, converting enum values to and from strings in the database.)

Tipp

Sie können den gesamten Code in dieser Dokumentation ausführen und debuggen, indem Sie den Beispielcode von GitHub herunterladen.You can run and debug into all the code in this document by downloading the sample code from GitHub.

ÜbersichtOverview

Wert Konverter werden in Bezug auf einen ModelClrType und einen angegeben ProviderClrType .Value converters are specified in terms of a ModelClrType and a ProviderClrType. Der Modelltyp ist der .NET-Typ der-Eigenschaft im-Entitätstyp.The model type is the .NET type of the property in the entity type. Der Anbietertyp ist der .NET-Typ, der vom Datenbankanbieter verstanden wird.The provider type is the .NET type understood by the database provider. Um z. b. Enumerationen als Zeichen folgen in der Datenbank zu speichern, ist der Modelltyp der Typ der Enumeration, und der Anbietertyp ist String .For example, to save enums as strings in the database, the model type is the type of the enum, and the provider type is String. Diese beiden Typen können identisch sein.These two types can be the same.

Konvertierungen werden mithilfe Func von zwei Ausdrucks Baumstrukturen definiert: einer von ModelClrType ProviderClrType und der andere von ProviderClrType bis ModelClrType .Conversions are defined using two Func expression trees: one from ModelClrType to ProviderClrType and the other from ProviderClrType to ModelClrType. Ausdrucks Baumstrukturen werden verwendet, damit Sie für effiziente Konvertierungen in den Datenbankzugriffs Delegaten kompiliert werden können.Expression trees are used so that they can be compiled into the database access delegate for efficient conversions. Die Ausdrucks Baumstruktur kann einen einfachen aufrufungs Methode für komplexe Konvertierungen enthalten.The expression tree may contain a simple call to a conversion method for complex conversions.

Hinweis

Eine Eigenschaft, die für die Wert Konvertierung konfiguriert wurde, muss möglicherweise auch ein angeben ValueComparer<T> .A property that has been configured for value conversion may also need to specify a ValueComparer<T>. Weitere Informationen finden Sie in den Beispielen unten und in der Dokumentation zu den Wert Comparer .See the examples below, and the Value Comparers documentation for more information.

Konfigurieren eines Wert KonvertersConfiguring a value converter

Wert Konvertierungen werden in konfiguriert DbContext.OnModelCreating .Value conversions are configured in DbContext.OnModelCreating. Stellen Sie sich z. b. eine Aufzählung und einen Entitätstyp vorFor example, consider an enum and entity type defined as:

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

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

Konvertierungen können in so konfiguriert werden, OnModelCreating dass die Enumerationswerte als Zeichen folgen wie "Esel", "maulzeichen" usw. in der Datenbank gespeichert werden. Sie müssen lediglich eine Funktion bereitstellen, die aus dem in ModelClrType den konvertiert ProviderClrType und eine andere für die umgekehrte Konvertierung:Conversions can be configured in OnModelCreating to store the enum values as strings such as "Donkey", "Mule", etc. in the database; you simply need to provide one function which converts from the ModelClrType to the ProviderClrType, and another for the opposite conversion:

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

Hinweis

Ein null Wert wird nie an einen Wert Konverter übermittelt.A null value will never be passed to a value converter. Ein NULL-Wert in einer Daten Bank Spalte ist in der Entitäts Instanz immer NULL (und umgekehrt).A null in a database column is always a null in the entity instance, and vice-versa. Dies vereinfacht die Implementierung von Konvertierungen und ermöglicht die gemeinsame Nutzung zwischen NULL-Werten und nicht auf NULL festleg baren Eigenschaften.This makes the implementation of conversions easier and allows them to be shared amongst nullable and non-nullable properties. Weitere Informationen finden Sie unter GitHub-Problem #13850 .See GitHub issue #13850 for more information.

Vordefinierte KonvertierungenPre-defined conversions

EF Core enthält viele vordefinierte Konvertierungen, die das manuelle Schreiben von Konvertierungs Funktionen vermeiden.EF Core contains many pre-defined conversions that avoid the need to write conversion functions manually. Stattdessen wählt EF Core die Konvertierung aus, die basierend auf dem Eigenschaftentyp im Modell und dem angeforderten Daten Bank Anbietertyp verwendet werden soll.Instead, EF Core will pick the conversion to use based on the property type in the model and the requested database provider type.

So werden z. b. Enumeration-zu-Zeichen folgen Konvertierungen als Beispiel verwendet, aber EF Core wird dies automatisch durchführen, wenn der Anbietertyp string mit dem generischen Typ von konfiguriert ist HasConversion :For example, enum to string conversions are used as an example above, but EF Core will actually do this automatically when the provider type is configured as string using the generic type of HasConversion:

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

Dies kann auch durch explizites angeben des Daten Bank Spalten Typs erreicht werden.The same thing can be achieved by explicitly specifying the database column type. Beispielsweise, wenn der Entitätstyp wie folgt definiert ist:For example, if the entity type is defined like so:

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

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

Die Enumerationswerte werden dann in der Datenbank ohne weitere Konfiguration in als Zeichen folgen gespeichert OnModelCreating .Then the enum values will be saved as strings in the database without any further configuration in OnModelCreating.

Die ValueConverter-KlasseThe ValueConverter class

HasConversionWenn Sie wie oben gezeigt aufrufen, wird eine ValueConverter<TModel,TProvider> -Instanz erstellt und für die-Eigenschaft festgelegt.Calling HasConversion as shown above will create a ValueConverter<TModel,TProvider> instance and set it on the property. ValueConverterStattdessen kann explizit erstellt werden.The ValueConverter can instead be created explicitly. Beispiel:For example:

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

Dies kann hilfreich sein, wenn mehrere Eigenschaften dieselbe Konvertierung verwenden.This can be useful when multiple properties use the same conversion.

Integrierte KonverterBuilt-in converters

Wie bereits erwähnt, wird EF Core mit einem Satz vordefinierter Klassen ausgeliefert ValueConverter<TModel,TProvider> , die im- Microsoft.EntityFrameworkCore.Storage.ValueConversion Namespace enthalten sind.As mentioned above, EF Core ships with a set of pre-defined ValueConverter<TModel,TProvider> classes, found in the Microsoft.EntityFrameworkCore.Storage.ValueConversion namespace. In vielen Fällen wählt EF den passenden integrierten Konverter basierend auf dem Typ der Eigenschaft im Modell und dem in der Datenbank angeforderten Typ aus, wie oben für enumeraten gezeigt.In many cases EF will choose the appropriate built-in converter based on the type of the property in the model and the type requested in the database, as shown above for enums. Beispielsweise .HasConversion<int>() bewirkt die Verwendung von für eine Eigenschaft, dass bool EF Core boolesche Werte in numerische NULL und einen Wert konvertiert:For example, using .HasConversion<int>() on a bool property will cause EF Core to convert bool values to numerical zero and one values:

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

Dies ist funktional identisch mit dem Erstellen einer integrierten Instanz BoolToZeroOneConverter<TProvider> und der expliziten Festlegung:This is functionally the same as creating an instance of the built-in BoolToZeroOneConverter<TProvider> and setting it explicitly:

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

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

In der folgenden Tabelle werden häufig verwendete vordefinierte Konvertierungen von Modell-/Eigenschaftentypen in Datenbankanbieter Typen zusammengefasst.The following table summarizes commonly-used pre-defined conversions from model/property types to database provider types. In der Tabelle any_numeric_type ist einer von int , short , long , byte , uint , ushort , ulong , sbyte , char , decimal , float oder gemeint double .In the table any_numeric_type means one of int, short, long, byte, uint, ushort, ulong, sbyte, char, decimal, float, or double.

Modell-/EigenschaftentypModel/property type Anbieter/DatenbanktypProvider/database type KonvertierungConversion VerbrauchUsage
boolbool any_numeric_typeany_numeric_type False/true für 0/1False/true to 0/1 .HasConversion<any_numeric_type>()
any_numeric_typeany_numeric_type False/true für beliebige zwei ZahlenFalse/true to any two numbers Verwenden Sie BoolToTwoValuesConverter<TProvider>Use BoolToTwoValuesConverter<TProvider>
Zeichenfolgestring False/true für "Y"/"N"False/true to "Y"/"N" .HasConversion<string>()
Zeichenfolgestring False/true für beliebige zwei Zeichen folgenFalse/true to any two strings Verwenden Sie BoolToStringConverterUse BoolToStringConverter
any_numeric_typeany_numeric_type boolbool 0/1 zu false/true0/1 to false/true .HasConversion<bool>()
any_numeric_typeany_numeric_type Einfache UmwandlungSimple cast .HasConversion<any_numeric_type>()
Zeichenfolgestring Die Zahl als Zeichenfolge.The number as a string .HasConversion<string>()
EnumerationEnum any_numeric_typeany_numeric_type Der numerische Wert der Enumeration.The numeric value of the enum .HasConversion<any_numeric_type>()
Zeichenfolgestring Die Zeichen folgen Darstellung des Enumerationswerts.The string representation of the enum value .HasConversion<string>()
Zeichenfolgestring boolbool Analysiert die Zeichenfolge als bool.Parses the string as a bool .HasConversion<bool>()
any_numeric_typeany_numeric_type Analysiert die Zeichenfolge als den angegebenen numerischen Typ.Parses the string as the given numeric type .HasConversion<any_numeric_type>()
charchar Das erste Zeichen der Zeichenfolge.The first character of the string .HasConversion<char>()
DatetimeDateTime Analysiert die Zeichenfolge als DateTime-Wert.Parses the string as a DateTime .HasConversion<DateTime>()
DateTimeOffsetDateTimeOffset Analysiert die Zeichenfolge als DateTimeOffset.Parses the string as a DateTimeOffset .HasConversion<DateTimeOffset>()
TimeSpanTimeSpan Analysiert die Zeichenfolge als TimeSpan.Parses the string as a TimeSpan .HasConversion<TimeSpan>()
GuidGuid Analysiert die Zeichenfolge als GUID.Parses the string as a Guid .HasConversion<Guid>()
byte[]byte[] Die Zeichenfolge als UTF8-bytes.The string as UTF8 bytes .HasConversion<byte[]>()
charchar Zeichenfolgestring Eine Zeichenfolge mit einem einzelnen ZeichenA single character string .HasConversion<string>()
DatetimeDateTime longlong Codiertes Datum/Uhrzeit-Beibehaltung von DateTime. KindEncoded date/time preserving DateTime.Kind .HasConversion<long>()
longlong TicktTicks Verwenden Sie DateTimeToTicksConverterUse DateTimeToTicksConverter
Zeichenfolgestring Datums-/Uhrzeitzeichenfolge der invarianten KulturInvariant culture date/time string .HasConversion<string>()
DateTimeOffsetDateTimeOffset longlong Codiertes Datum/Uhrzeit mit OffsetEncoded date/time with offset .HasConversion<long>()
Zeichenfolgestring Datum/Uhrzeit-Zeichenfolge der invarianten Kultur mit OffsetInvariant culture date/time string with offset .HasConversion<string>()
TimeSpanTimeSpan longlong TicktTicks .HasConversion<long>()
Zeichenfolgestring Zeichenfolge für die invariante Kulturzeit SpanneInvariant culture time span string .HasConversion<string>()
UriUri Zeichenfolgestring Der URI als Zeichenfolge.The URI as a string .HasConversion<string>()
PhysicalAddressPhysicalAddress Zeichenfolgestring Die Adresse als Zeichenfolge.The address as a string .HasConversion<string>()
byte[]byte[] Bytes in Big-d-Netzwerk ReihenfolgeBytes in big-endian network order .HasConversion<byte[]>()
IPAddressIPAddress Zeichenfolgestring Die Adresse als Zeichenfolge.The address as a string .HasConversion<string>()
byte[]byte[] Bytes in Big-d-Netzwerk ReihenfolgeBytes in big-endian network order .HasConversion<byte[]>()
GuidGuid Zeichenfolgestring Der GUID im auf: dddddddd-dddd-dddd-dddd-dddddddddddd-Format.The GUID in 'dddddddd-dddd-dddd-dddd-dddddddddddd' format .HasConversion<string>()
byte[]byte[] Bytes in der binären Serialisierungsreihenfolge von .netBytes in .NET binary serialization order .HasConversion<byte[]>()

Beachten Sie, dass diese Konvertierungen davon ausgehen, dass das Format des Werts für die Konvertierung geeignet ist.Note that these conversions assume that the format of the value is appropriate for the conversion. Wenn z. b. Zeichen folgen in Zahlen konvertiert werden, treten Fehler auf, wenn die Zeichen folgen Werte nicht als Zahlen analysiert werden können.For example, converting strings to numbers will fail if the string values cannot be parsed as numbers.

Die vollständige Liste der integrierten Konverter lautet:The full list of built-in converters is:

Beachten Sie, dass alle integrierten Konverter zustandslos sind, sodass eine einzelne Instanz auf sichere Weise von mehreren Eigenschaften gemeinsam genutzt werden kann.Note that all the built-in converters are stateless and so a single instance can be safely shared by multiple properties.

Spalten Facetten und Mapping-HinweiseColumn facets and mapping hints

Einige Datenbanktypen verfügen über Facetten, die die Speicherung der Daten ändern.Some database types have facets that modify how the data is stored. Dazu gehören:These include:

  • Genauigkeit und Dezimalstellen für Dezimalzahlen und Datums-/UhrzeitspaltenPrecision and scale for decimals and date/time columns
  • Größe/Länge für binäre Spalten und Zeichen folgen SpaltenSize/length for binary and string columns
  • Unicode für Zeichen folgen SpaltenUnicode for string columns

Diese Facetten können für eine Eigenschaft, die einen Wert Konverter verwendet, auf normale Weise konfiguriert werden und gelten für den konvertierten Datenbanktyp.These facets can be configured in the normal way for a property that uses a value converter, and will apply to the converted database type. Wenn Sie z. b. von einer Enumeration in Zeichen folgen umrechnen, können wir angeben, dass die Daten Bank Spalte nicht-Unicode sein soll und bis zu 20 Zeichen speichern soll:For example, when converting from an enum to strings, we can specify that the database column should be non-Unicode and store up to 20 characters:

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

Oder, wenn der Konverter explizit erstellt wird:Or, when creating the converter explicitly:

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

Dies führt zu einer varchar(20) Spalte, wenn EF Core Migrationen für SQL Server verwendet werden:This results in a varchar(20) column when using EF Core migrations against SQL Server:

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

Wenn allerdings standardmäßig alle EquineBeast Spalten lauten sollen varchar(20) , können diese Informationen dem Wert Konverter als übergeben werden ConverterMappingHints .However, if by default all EquineBeast columns should be varchar(20), then this information can be given to the value converter as a ConverterMappingHints. Beispiel:For example:

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

Bei jeder Verwendung dieses Konverters ist die Daten Bank Spalte nicht-Unicode mit einer maximalen Länge von 20.Now any time this converter is used, the database column will be non-unicode with a max length of 20. Dies sind jedoch nur Hinweise, da Sie von allen Facetten überschrieben werden, die explizit für die zugeordnete Eigenschaft festgelegt wurden.However, these are only hints since they are be overridden by any facets explicitly set on the mapped property.

BeispieleExamples

Einfache Wert ObjekteSimple value objects

In diesem Beispiel wird ein einfacher Typ verwendet, um einen primitiven Typ zu wrappen.This example uses a simple type to wrap a primitive type. Dies kann hilfreich sein, wenn Sie möchten, dass der Typ im Modell spezifischer (und somit typsicherer) als ein primitiver Typ ist.This can be useful when you want the type in your model to be more specific (and hence more type-safe) than a primitive type. In diesem Beispiel ist Dollars der Typ, der den dezimalen primitiven umschließt:In this example, that type is Dollars, which wraps the decimal primitive:

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

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

Dies kann in einem Entitätstyp verwendet werden:This can be used in an entity type:

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

    public Dollars Price { get; set; }
}

Und in die zugrunde liegende konvertiert, decimal Wenn Sie in der Datenbank gespeichert werden:And converted to the underlying decimal when stored in the database:

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

Hinweis

Dieses Wertobjekt wird als schreibgeschützte Strukturimplementiert.This value object is implemented as a readonly struct. Dies bedeutet, dass EF Core Werte ohne Probleme erstellen und vergleichen können.This means that EF Core can snapshot and compare values without issue. Weitere Informationen finden Sie unter Wert Vergleiche .See Value Comparers for more information.

Zusammengesetzte Wert ObjekteComposite value objects

Im vorherigen Beispiel enthielt der Wert Objekttyp nur eine einzelne Eigenschaft.In the previous example, the value object type contained only a single property. Es kommt häufiger vor, dass ein Wert Objekttyp mehrere Eigenschaften zusammenfasst, die zusammen ein Domänen Konzept bilden.It is more common for a value object type to compose multiple properties that together form a domain concept. Beispielsweise ein allgemeiner Money Typ, der sowohl den Betrag als auch die Währung enthält:For example, a general Money type that contains both the amount and the currency:

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,
    PoundsStirling
}

Dieses Wertobjekt kann in einem Entitätstyp wie zuvor verwendet werden:This value object can be used in an entity type as before:

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

    public Money Price { get; set; }
}

Wert Konverter können derzeit nur Werte in eine und aus einer einzelnen Daten Bank Spalte konvertieren.Value converters can currently only convert values to and from a single database column. Diese Einschränkung bedeutet, dass alle Eigenschaftswerte aus dem Objekt in einen einzelnen Spaltenwert codiert werden müssen.This limitation means that all property values from the object must be encoded into a single column value. Dies wird in der Regel durch Serialisieren des Objekts in die Datenbank und anschließendes Deserialisieren des Objekts behandelt. Verwenden Sie beispielsweise Folgendes System.Text.Json :This is typically handled by serializing the object as it goes into the database, and then deserializing it again on the way out. For example, using System.Text.Json:

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

Hinweis

Wir planen, das Mapping eines Objekts mehreren Spalten in EF Core 6,0 zu ermöglichen. Hierdurch entfällt die Notwendigkeit, die Serialisierung zu verwenden.We plan to allow mapping an object to multiple columns in EF Core 6.0, removing the need to use serialization here. Dies wird durch das GitHub-Problem #13947nachverfolgt.This is tracked by GitHub issue #13947.

Hinweis

Wie im vorherigen Beispiel wird dieses Wertobjekt als schreibgeschützte Strukturimplementiert.As with the previous example, this value object is implemented as a readonly struct. Dies bedeutet, dass EF Core Werte ohne Probleme erstellen und vergleichen können.This means that EF Core can snapshot and compare values without issue. Weitere Informationen finden Sie unter Wert Vergleiche .See Value Comparers for more information.

Auflistungen von primitivenCollections of primitives

Die Serialisierung kann auch zum Speichern einer Auflistung primitiver Werte verwendet werden.Serialization can also be used to store a collection of primitive values. Beispiel:For example:

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

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

Erneutes verwenden System.Text.Json :Using System.Text.Json again:

modelBuilder.Entity<Post>()
    .Property(e => e.Tags)
    .HasConversion(
        v => JsonSerializer.Serialize(v, null),
        v => JsonSerializer.Deserialize<List<string>>(v, 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> stellt einen änderbaren Referenztyp dar.ICollection<string> represents a mutable reference type. Dies bedeutet, dass eine ValueComparer<T> erforderlich ist, damit EF Core Änderungen ordnungsgemäß verfolgen und erkennen können.This means that a ValueComparer<T> is needed so that EF Core can track and detect changes correctly. Weitere Informationen finden Sie unter Wert Vergleiche .See Value Comparers for more information.

Auflistungen von Wert ObjektenCollections of value objects

Wenn Sie die beiden vorherigen Beispiele zusammen kombinieren, können wir eine Auflistung von Wert Objekten erstellen.Combining the previous two examples together we can create a collection of value objects. Stellen Sie sich beispielsweise einen Typ vor, AnnualFinance der die Blog Finanzen für ein einzelnes Jahr modelliert:For example, consider an AnnualFinance type that models blog finances for a single year:

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

Dieser Typ verfasst einige der Typen, die Money wir zuvor erstellt haben:This type composes several of the Money types we created previously:

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,
    PoundsStirling
}

Anschließend können wir dem Entitätstyp eine Auflistung von hinzufügen AnnualFinance :We can then add a collection of AnnualFinance to our entity type:

public class Blog
{
    public int Id { get; set; }
    public string Name { get; set; }
    
    public IList<AnnualFinance> Finances { get; set; }
}

Verwenden Sie die Serialisierung auch, um Folgendes zu speichern:And again use serialization to store this:

modelBuilder.Entity<Blog>()
    .Property(e => e.Finances)
    .HasConversion(
        v => JsonSerializer.Serialize(v, null),
        v => JsonSerializer.Deserialize<List<AnnualFinance>>(v, 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()));

Hinweis

Wie zuvor ist für diese Konvertierung eine erforderlich ValueComparer<T> .As before, this conversion requires a ValueComparer<T>. Weitere Informationen finden Sie unter Wert Vergleiche .See Value Comparers for more information.

Wert Objekte als SchlüsselValue objects as keys

Manchmal können primitive Schlüsseleigenschaften in Wert Objekte umschließt werden, um beim Zuweisen von Werten eine zusätzliche Ebene der Typsicherheit hinzuzufügen.Sometimes primitive key properties may be wrapped in value objects to add an additional level of type-safety in assigning values. Wir könnten beispielsweise einen Schlüsseltyp für Blogs und einen Schlüsseltyp für Beiträge implementieren:For example, we could implement a key type for blogs, and a key type for posts:

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

Diese können dann im Domänen Modell verwendet werden:These can then be used in the domain model:

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

Beachten Sie, dass Blog.Id nicht versehentlich eine zugewiesen werden kann PostKey und Post.Id nicht versehentlich ein zugewiesen werden kann BlogKey .Notice that Blog.Id cannot accidentally be assigned a PostKey, and Post.Id cannot accidentally be assigned a BlogKey. Ebenso muss der Post.BlogId Fremdschlüssel Eigenschaft ein zugewiesen werden BlogKey .Similarly, the Post.BlogId foreign key property must be assigned a BlogKey.

Hinweis

Wenn dieses Muster angezeigt wird, bedeutet dies nicht, dass es empfohlen wird.Showing this pattern does not mean we recommend it. Überlegen Sie, ob diese Abstraktions Ebene das Entwicklungsverfahren unterstützt oder behindert.Carefully consider whether this level of abstraction is helping or hampering your development experience. Sie sollten auch die Verwendung von Navigationen und generierten Schlüsseln in Erwägung gezogen, anstatt direkt mit Schlüsselwerten umzugehen.Also, consider using navigations and generated keys instead of dealing with key values directly.

Diese Schlüsseleigenschaften können dann mithilfe von Wert Konvertern zugeordnet werden:These key properties can then be mapped using value converters:

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

Hinweis

Für Schlüsseleigenschaften mit Konvertierungen können derzeit keine generierten Schlüsselwerte verwendet werden.Currently key properties with conversions cannot use generated key values. Stimmen Sie für das GitHub-Problem #11597 , dass diese Einschränkung entfernt werden soll.Vote for GitHub issue #11597 to have this limitation removed.

Verwenden von ULONG für Timestamp/rowversionUse ulong for timestamp/rowversion

SQL Server unterstützt die automatische voll ständige Parallelität mithilfe von 8-Byte-Binär rowversion / timestamp Spalten.SQL Server supports automatic optimistic concurrency using 8-byte binary rowversion/timestamp columns. Diese werden stets mithilfe eines 8-Byte-Arrays aus der Datenbank gelesen und in diese geschrieben.These are always read from and written to the database using an 8-byte array. Byte Arrays sind jedoch ein änderbarer Verweistyp, der die Handhabung einigermaßen erschwert.However, byte arrays are a mutable reference type, which makes them somewhat painful to deal with. Mithilfe von Wert Konvertern kann rowversion stattdessen eine Eigenschaft zugeordnet ulong werden, die wesentlich besser geeignet und leichter zu verwenden ist als das Bytearray.Value converters allow the rowversion to instead be mapped to a ulong property, which is much more appropriate and easy to use than the byte array. Stellen Sie sich z. b. eine Blog Entität mit einem ULONG-Parallelitäts Token vor:For example, consider a Blog entity with a ulong concurrency token:

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

Dies kann rowversion mit einem Wert Konverter einer SQL Server-Spalte zugeordnet werden:This can be mapped to a SQL server rowversion column using a value converter:

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

DateTime. Kind beim Lesen von Datumsangaben angebenSpecify the DateTime.Kind when reading dates

SQL Server verwirft das- DateTime.Kind Flag, wenn ein DateTime als oder gespeichert wird datetime datetime2 .SQL Server discards the DateTime.Kind flag when storing a DateTime as a datetime or datetime2. Dies bedeutet, dass DateTime-Werte, die von der Datenbank zurückkommen, immer eine DateTimeKind von aufweisen Unspecified .This means that DateTime values coming back from the database always have a DateTimeKind of Unspecified.

Wert Konverter können auf zwei Arten verwendet werden, um damit umzugehen.Value converters can be used in two ways to deal with this. Zuerst verfügt EF Core über einen Wert Konverter, der einen nicht transparenten 8-Byte-Wert erstellt, der das-Flag beibehält Kind .First, EF Core has a value converter that creates an 8-byte opaque value which preserves the Kind flag. Beispiel:For example:

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

Dadurch können DateTime-Werte mit unterschiedlichen Kind Flags in der Datenbank gemischt werden.This allows DateTime values with different Kind flags to be mixed in the database.

Das Problem bei diesem Ansatz besteht darin, dass die Datenbank nicht mehr über erkennbare- datetime oder- datetime2 Spalten verfügt.The problem with this approach is that the database no longer has recognizable datetime or datetime2 columns. Stattdessen ist es üblich, immer die UTC-Zeit (oder seltener, immer Ortszeit) zu speichern und dann entweder das Kind Flag zu ignorieren oder es mit einem Wert Konverter auf den entsprechenden Wert festzulegen.So instead it is common to always store UTC time (or, less commonly, always local time) and then either ignore the Kind flag or set it to the appropriate value using a value converter. Beispielsweise stellt der Konverter unten sicher, dass der DateTime aus der Datenbank gelesene Wert Folgendes hat DateTimeKind UTC :For example, the converter below ensures that the DateTime value read from the database will have the DateTimeKind UTC:

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

Wenn eine Mischung aus lokalen und UTC-Werten in Entitäts Instanzen festgelegt wird, kann der Konverter verwendet werden, um vor dem Einfügen entsprechend zu konvertieren.If a mix of local and UTC values are being set in entity instances, then the converter can be used to convert appropriately before inserting. Beispiel:For example:

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

Hinweis

Achten Sie sorgfältig darauf, den gesamten Datenbankzugriffs Code so zu vereinheitlichen, dass die UTC-Zeit immer verwendet wirdCarefully consider unifying all database access code to use UTC time all the time, only dealing with local time when presenting data to users.

Zeichen folgen Schlüssel mit Groß-/Kleinschreibung verwendenUse case-insensitive string keys

Einige Datenbanken, einschließlich SQL Server, führen standardmäßig Zeichen folgen Vergleiche ohne Berücksichtigung der Groß-und Kleinschreibung durchSome databases, including SQL Server, perform case-insensitive string comparisons by default. .NET hingegen führt standardmäßig die Groß-/Kleinschreibung für Zeichen folgen Vergleiche durch..NET, on the other hand, performs case-sensitive string comparisons by default. Dies bedeutet, dass ein Fremdschlüssel Wert wie "dotnet" mit dem Primärschlüssel Wert "dotnet" auf SQL Server, aber nicht mit dem Wert in EF Core abgeglichen wird.This means that a foreign key value like "DotNet" will match the primary key value "dotnet" on SQL Server, but will not match it in EF Core. Ein Wert Vergleich für Schlüssel kann verwendet werden, um zu erzwingen, dass EF Core groß-und Kleinschreibung für Zeichen folgen Vergleiche wie in der Datenbank nicht beachtet.A value comparer for keys can be used to force EF Core into case-insensitive string comparisons like in the database. Stellen Sie sich beispielsweise ein Blog-/Post-Modell mit Zeichen folgen Schlüsseln vor:For example, consider a blog/posts model with string keys:

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

Dies funktioniert nicht wie erwartet, wenn einige der Werte eine andere Schreibweise Post.BlogId aufweisen.This will not work as expected if some of the Post.BlogId values have different casing. Die Fehler, die dadurch verursacht werden, hängen davon ab, wie die Anwendung ausgeführt wird, aber in der Regel Diagramme von Objekten, die nicht ordnungsgemäß korrigiert werden, und/oder Updates, die fehlschlagen, weil der FK - Wert falsch ist.The errors caused by this will depend on what the application is doing, but typically involve graphs of objects that are not fixed-up correctly, and/or updates that fail because the FK value is wrong. Ein Wert Vergleich kann verwendet werden, um Folgendes zu beheben:A value comparer can be used to correct this:

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

Hinweis

.Net-Zeichen folgen Vergleiche und Daten Bank Zeichen folgen Vergleiche können sich in mehr als nur der Groß-/Kleinschreibung.NET string comparisons and database string comparisons can differ in more than just case sensitivity. Dieses Muster funktioniert für einfache ASCII-Schlüssel, kann jedoch für Schlüssel mit beliebigen kulturspezifischen Zeichen fehlschlagen.This pattern works for simple ASCII keys, but may fail for keys with any kind of culture-specific characters. Weitere Informationen finden Sie unter Sortierungen und Groß-/Kleinschreibung .See Collations and Case Sensitivity for more information.

Verarbeiten von Daten Bank Zeichenfolgen fester LängeHandle fixed-length database strings

Im vorherigen Beispiel wurde kein Wert Konverter benötigt.The previous example did not need a value converter. Ein Konverter kann jedoch für Daten Bank Zeichen folgen Typen mit fester Länge wie oder nützlich sein char(20) nchar(20) .However, a converter can be useful for fixed-length database string types like char(20) or nchar(20). Zeichen folgen mit fester Länge werden in der vollständigen Länge aufgefüllt, wenn ein Wert in die Datenbank eingefügt wird.Fixed-length strings are padded to their full length whenever a value is inserted into the database. Dies bedeutet, dass der Schlüsselwert " dotnet " aus der Datenbank als "" gelesen wird dotnet.............. , wobei . ein Leerzeichen darstellt.This means that a key value of "dotnet" will be read back from the database as "dotnet..............", where . represents a space character. Dies wird dann nicht ordnungsgemäß mit Schlüsselwerten verglichen, die nicht aufgefüllt werden.This will then not compare correctly with key values that are not padded.

Ein Wert Konverter kann verwendet werden, um die Auffüll Zeichen beim Lesen von Schlüsselwerten zu kürzen.A value converter can be used to trim the padding when reading key values. Dies kann im vorherigen Beispiel mit dem Wert Vergleich kombiniert werden, um die Groß-/Kleinschreibung ohne Berücksichtigung der Groß-/Kleinschreibung zu vergleichen.This can be combined with the value comparer in the previous example to compare fixed length case-insensitive ASCII keys correctly. Beispiel:For example:

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

Verschlüsseln von Eigenschafts WertenEncrypt property values

Wert Konverter können verwendet werden, um Eigenschaftswerte vor dem Senden an die Datenbank zu verschlüsseln und Sie dann auf dem Weg zu entschlüsseln. Verwenden Sie z. b. die Zeichen folgen Umkehrung als Ersatz für einen echten Verschlüsselungsalgorithmus:Value converters can be used to encrypt property values before sending them to the database, and then decrypt them on the way out. For example, using string reversal as a substitute for a real encryption algorithm:

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

Hinweis

Es gibt derzeit keine Möglichkeit, einen Verweis auf den aktuellen dbcontext oder einen anderen Sitzungszustand innerhalb eines Wert Konverters zu erhalten.There is currently no way to get a reference to the current DbContext, or other session state, from within a value converter. Dies schränkt die Arten der Verschlüsselung ein, die verwendet werden können.This limits the kinds of encryption that can be used. Stimmen Sie für das GitHub-Problem #11597 , dass diese Einschränkung entfernt werden soll.Vote for GitHub issue #11597 to have this limitation removed.

Warnung

Stellen Sie sicher, dass Sie alle Implikationen verstehen, wenn Sie eine eigene Verschlüsselung zum Schutz sensibler Daten ausführen.Make sure to understand all the implications if you roll your own encryption to protect sensitive data. Verwenden Sie stattdessen vorgefertigte Verschlüsselungsmechanismen, z. b. Always Encrypted auf SQL Server.Consider instead using pre-built encryption mechanisms, such as Always Encrypted on SQL Server.

EinschränkungenLimitations

Es gibt einige bekannte aktuelle Einschränkungen des Value-Konvertierungs Systems:There are a few known current limitations of the value conversion system:

  • Es gibt derzeit keine Möglichkeit, an einem Ort anzugeben, dass jede Eigenschaft eines bestimmten Typs denselben Wert Konverter verwenden muss.There is currently no way to specify in one place that every property of a given type must use the same value converter. Bitte stimmen 👍 Sie () auf GitHub-Problem #10784 ab, wenn dies etwas ist, das Sie benötigen.Please vote (👍) for GitHub issue #10784 if this is something you need.
  • Wie bereits erwähnt, null kann nicht konvertiert werden.As noted above, null cannot be converted. Bitte stimmen 👍 Sie () auf GitHub-Problem #13850 ab, wenn dies etwas ist, das Sie benötigen.Please vote (👍) for GitHub issue #13850 if this is something you need.
  • Zurzeit gibt es keine Möglichkeit, eine Konvertierung einer Eigenschaft in mehrere Spalten zu verteilen (oder umgekehrt).There is currently no way to spread a conversion of one property to multiple columns or vice-versa. Bitte stimmen 👍 Sie () auf GitHub-Problem #13947 ab, wenn dies etwas ist, das Sie benötigen.Please vote (👍) for GitHub issue #13947 if this is something you need.
  • Die Generierung von Werten wird für die meisten durch Wert Konverter zugeordneten Schlüssel nicht unterstützt.Value generation is not supported for most keys mapped through value converters. Bitte stimmen 👍 Sie () auf GitHub-Problem #11597 ab, wenn dies etwas ist, das Sie benötigen.Please vote (👍) for GitHub issue #11597 if this is something you need.
  • Wert Konvertierungen können nicht auf die aktuelle dbcontext-Instanz verweisen.Value conversions cannot reference the current DbContext instance. Bitte stimmen 👍 Sie () auf GitHub-Problem #11597 ab, wenn dies etwas ist, das Sie benötigen.Please vote (👍) for GitHub issue #11597 if this is something you need.

Das Entfernen dieser Einschränkungen wird in zukünftigen Versionen berücksichtigt.Removal of these limitations is being considered for future releases.