Generierte Werte

Datenbankspalten können ihre Werte auf verschiedene Arten generieren: Primärschlüsselspalten werden häufig automatisch um ganze Zahlen erhöht, andere Spalten haben Standard- oder berechnete Werte usw. Auf dieser Seite werden verschiedene Muster für die Generierung von Konfigurationswerten mit EF Core beschrieben.

Standardwerte

In relationalen Datenbanken kann eine Spalte mit einem Standardwert konfiguriert werden. Wenn eine Zeile ohne Wert für diese Spalte eingefügt wird, wird der Standardwert verwendet.

Sie können einen Standardwert für eine Eigenschaft konfigurieren:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .Property(b => b.Rating)
        .HasDefaultValue(3);
}

Sie können auch ein SQL-Fragment angeben, das zum Berechnen des Standardwerts verwendet wird:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .Property(b => b.Created)
        .HasDefaultValueSql("getdate()");
}

Berechnete Spalten

In den meisten relationalen Datenbanken kann eine Spalte so konfiguriert werden, dass ihr Wert in der Datenbank mit einem Ausdruck berechnet wird, der auf andere Spalten verweist:

modelBuilder.Entity<Person>()
    .Property(p => p.DisplayName)
    .HasComputedColumnSql("[LastName] + ', ' + [FirstName]");

Im obigen Beispiel wird eine virtuelle berechnete Spalte erstellt, deren Wert jedes Mal berechnet wird, wenn er aus der Datenbank abgerufen wird. Sie können auch angeben, dass eine berechnete Spalte gespeichert wird (manchmal auch persistiert genannt), was bedeutet, dass sie für jede Aktualisierung der Zeile berechnet wird und auf dem Datenträger zusammen mit regulären Spalten gespeichert wird:

modelBuilder.Entity<Person>()
    .Property(p => p.NameLength)
    .HasComputedColumnSql("LEN([LastName]) + LEN([FirstName])", stored: true);

Primärschlüssel

Standardmäßig werden nicht zusammengesetzte Primärschlüssel vom Typ "short", "int", "long" oder "GUID" so eingerichtet, dass Werte für eingefügte Entitäten generiert werden, wenn ein Wert nicht von der Anwendung bereitgestellt wird. Ihr Datenbankanbieter kümmert sich in der Regel um die erforderliche Konfiguration. Beispielsweise wird automatisch ein numerischer Primärschlüssel in SQL Server als IDENTITY-Spalte eingerichtet.

Weitere Informationen finden Sie in der Dokumentation zu Schlüsseln und der Anleitungen für spezifische Vererbungszuordnungsstrategien.

Explizite Konfiguration der Wertgenerierung

Wir haben oben gesehen, dass EF Core automatisch die Wertgenerierung für Primärschlüssel einrichtet, aber möglicherweise möchten wir dasselbe auch für Nichtschlüsseleigenschaften tun. Sie können jede Eigenschaft so konfigurieren, dass ihr Wert für eingefügte Entitäten wie folgt generiert wird:

public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }

    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public DateTime Inserted { get; set; }
}

Ebenso kann eine Eigenschaft so konfiguriert werden, dass ihr Wert beim Hinzufügen oder Aktualisieren generiert wird:

public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }

    [DatabaseGenerated(DatabaseGeneratedOption.Computed)]
    public DateTime LastUpdated { get; set; }
}

Im Gegensatz zu Standardwerten oder berechneten Spalten geben wir nicht an, wie die Werte generiert werden sollen. Dies hängt vom verwendeten Datenbankanbieter ab. Datenbankanbieter können die Wertgenerierung für einige Eigenschaftstypen automatisch einrichten, andere erfordern jedoch möglicherweise, dass Sie die Art und Weise der Wertgenerierung manuell einrichten.

Wenn beispielsweise eine GUID-Eigenschaft auf SQL Server als Primärschlüssel konfiguriert ist, führt der Anbieter automatisch clientseitige Wertgenerierung mithilfe eines Algorithmus aus, um optimale sequenzielle GUID-Werte zu generieren. Die Angabe von ValueGeneratedOnAdd in einer DateTime-Eigenschaft hat jedoch keine Auswirkung (siehe Abschnitt unten für die DateTime-Wertgenerierung).

Ebenso werden Byte[]-Eigenschaften, die beim Hinzufügen oder Aktualisieren konfiguriert und als Parallelitätstoken gekennzeichnet werden, mit dem Datentyp Rowversion eingerichtet, damit Werte automatisch in der Datenbank generiert werden. Die Angabe von ValueGeneratedOnAdd hat jedoch keine Auswirkung.

In der Dokumentation Ihres Anbieters finden Sie die spezifischen unterstützten Techniken zur Wertgenerierung. Die Dokumentation zur SQL Server-Wertgenerierung finden Sie hier.

Generieren von Datums-/Uhrzeitwerten

Häufig wird eine Datenbankspalte gewünscht, die das Datum/die Uhrzeit enthält, zu dem/der die Zeile zum ersten Mal eingefügt wurde (Wert wird beim Hinzufügen erzeugt) oder zu dem/der sie zuletzt aktualisiert wurde (Wert wird beim Hinzufügen oder Aktualisieren erzeugt). Da es hierfür verschiedene Strategien gibt, richten EF Core-Anbieter in der Regel keine Wertgenerierung automatisch für Datums-/Uhrzeitspalten ein. Sie müssen dies selbst konfigurieren.

Erstellungszeitstempel

Das Konfigurieren einer Datums-/Uhrzeitspalte für den Zeitstempel der Zeile ist in der Regel eine Frage der Konfiguration eines Standardwerts mit der entsprechenden SQL-Funktion. In SQL Server können Sie beispielsweise Folgendes verwenden:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .Property(b => b.Created)
        .HasDefaultValueSql("getdate()");
}

Achten Sie darauf, die entsprechende Funktion auszuwählen, da mehrere vorhanden sein können (z. B. GETDATE() vs. GETUTCDATE()).

Zeitstempel aktualisieren

Obwohl gespeicherte berechnete Spalten wie eine gute Lösung zum Verwalten von Zeitstempeln der letzten Aktualisierung erscheinen, lassen Datenbanken in der Regel keine Funktionen wie GETDATE() in einer berechneten Spalte zu. Alternativ können Sie einen Datenbanktrigger einrichten, um denselben Effekt zu erzielen:

CREATE TRIGGER [dbo].[Blogs_UPDATE] ON [dbo].[Blogs]
    AFTER UPDATE
AS
BEGIN
    SET NOCOUNT ON;

    IF ((SELECT TRIGGER_NESTLEVEL()) > 1) RETURN;

    UPDATE B
    SET LastUpdated = GETDATE()
    FROM dbo.Blogs AS B
    INNER JOIN INSERTED AS I
        ON B.BlogId = I.BlogId
END

Informationen zum Erstellen von Triggern finden Sie in der Dokumentation zur Verwendung von rohem SQL-Code in Migrationen.

Überschreiben der Wertgenerierung

Obwohl eine Eigenschaft für die Wertgenerierung konfiguriert ist, können Sie in vielen Fällen dennoch explizit einen Wert dafür festlegen. Ob dies tatsächlich funktioniert, hängt von dem spezifischen Mechanismus der Wertgenerierung ab, der konfiguriert wurde. Sie können zwar einen expliziten Wert angeben, anstatt den Standardwert einer Spalte zu verwenden, aber nicht bei berechneten Spalten.

Wenn Sie die Wertgenerierung mit einem expliziten Wert überschreiben möchten, legen Sie einfach die Eigenschaft auf einen beliebigen Wert fest, der nicht der CLR-Standardwert für den Typ dieser Eigenschaft ist (null für string, 0 für int, Guid.Empty für Guid, usw.).

Hinweis

Der Versuch, explizite Werte in SQL Server IDENTITY einzufügen, schlägt standardmäßig fehl. Eine Problemumgehung finden Sie in diesen Dokumenten.

Um einen expliziten Wert für Eigenschaften bereitzustellen, die beim Hinzufügen oder Aktualisieren als Wert konfiguriert wurden, müssen Sie die Eigenschaft auch wie folgt konfigurieren:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>().Property(b => b.LastUpdated)
        .ValueGeneratedOnAddOrUpdate()
        .Metadata.SetAfterSaveBehavior(PropertySaveBehavior.Save);
}

Keine Wertgenerierung

Neben bestimmten Szenarien wie den oben beschriebenen, sind Eigenschaften in der Regel nicht für die Wertgenerierung konfiguriert. Dies bedeutet, dass die Anwendung immer einen Wert angibt, der in der Datenbank gespeichert werden soll. Dieser Wert muss den neuen Entitäten zugewiesen werden, bevor sie dem Kontext hinzugefügt werden.

In einigen Fällen möchten Sie jedoch möglicherweise die Wertgenerierung deaktivieren, die nach Konventionen eingerichtet wurde. Beispielsweise wird ein Primärschlüssel vom Typ int in der Regel implizit als wertgeneriert-on-add (z. B. Identitätsspalte in SQL Server) konfiguriert. Sie können dies auf folgende Art und Weise deaktivieren:

public class Blog
{
    [DatabaseGenerated(DatabaseGeneratedOption.None)]
    public int BlogId { get; set; }

    public string Url { get; set; }
}