Oracle SELECT FOR UPDATE in EF Core nutzen

Oracle SELECT FOR UPDATE in EF Core nutzen

13. März 2024 | Gregor Koletzki | 3 Min. Lesezeit

In der Oracle-Datenbankwelt ist die SELECT-FOR-UPDATE-Anweisung ein leistungsstarkes Werkzeug, das bei der Behandlung von Transaktionen und der Vermeidung von Dateninkonsistenzen hilft. Diese Anweisung ermöglicht es, bestimmte Datenzeilen zu sperren, um sicherzustellen, dass sie während einer Transaktion nicht von anderen Benutzern geändert werden können.

Was ist SELECT FOR UPDATE?

Die SELECT-FOR-UPDATE-Anweisung wird verwendet, um Daten zu lesen und sie gleichzeitig zu sperren, um Änderungen an ihnen während einer Transaktion zu verhindern. Dies ist besonders nützlich in Multi-User-Umgebungen, in denen mehrere Benutzer gleichzeitig auf dieselben Daten zugreifen und Änderungen vornehmen könnten.

Wie wird SELECT FOR UPDATE verwendet?

Die Syntax für die Verwendung von SELECT FOR UPDATE in Oracle ist relativ einfach:

SELECT * FROM tabelle WHERE bedingung FOR UPDATE;

Hier wird die Tabelle nach den angegebenen Bedingungen durchsucht, und die gefundenen Datensätze werden gesperrt, um Änderungen durch andere Transaktionen zu verhindern.

Beispiel:

Angenommen, wir haben eine Tabelle "Kunden", und wir möchten einen bestimmten Kunden aktualisieren, während sicherzustellen, dass während dieser Aktualisierung keine anderen Änderungen am Kunden vorgenommen werden:

BEGIN
  -- Transaktion starten
  SELECT * FROM kunden WHERE kunden_id = 12345 FOR UPDATE;
  
  -- Hier können weitere Operationen durchgeführt werden, z.B. Aktualisierung des Kunden
  
  -- Transaktion abschließen
  COMMIT;
END;

In diesem Beispiel werden die Zeilen mit der Kunden-ID 12345 in der Tabelle "Kunden" gesperrt, sodass während der Transaktion keine anderen Benutzer Änderungen an diesen Datensätzen vornehmen können.

Wann sollte SELECT FOR UPDATE verwendet werden?

SELECT FOR UPDATE sollte in Situationen verwendet werden, in denen die Konsistenz von Daten während einer Transaktion gewährleistet werden muss. Dies kann besonders wichtig sein, wenn mehrere Benutzer gleichzeitig auf dieselben Daten zugreifen und Änderungen vornehmen könnten. Es ist jedoch wichtig, SELECT FOR UPDATE mit Vorsicht zu verwenden, da eine zu breite Verwendung die Leistung beeinträchtigen kann, insbesondere in Umgebungen mit vielen gleichzeitigen Benutzerzugriffen.

SELECT FOR UPDATE in EF Core Implementieren

Die EF Core Oracle Implementierung bringt die SELECT FOR UPDATE Funktionalität nicht von sich mit, somit müssen wir uns eines kleinen Tricks behelfen. Wir müssen uns in die Ausführung des DBCommandos einhängen um das SQL welches an die Datenbank geschickt wird zu manipulieren. Interceptoren in EF Core geben uns die Möglichkeit EF Core Vorgänge abzufangen, zu ändern und/oder zu unterdrücken. Wir können den DbCommandInterceptor wie folgt implementieren um SELECT FOR UPDATE zu ermöglichen:

public class SelectForUpdateDbCommandInterceptor : DbCommandInterceptor
{
    public const string SELECT_FOR_UPDATE_TAG = "SELECTFORUPDATE";
    private const string SELECT_FOR_UPDATE_INTERNAL_TAG = "-- " + SELECT_FOR_UPDATE_TAG;
    private const string FOR_UPDATE = " FOR UPDATE";
    private const string REMOVE_FETCH_ROWS = "FETCH FIRST 2 ROWS ONLY";

    public override InterceptionResult<object> ScalarExecuting(DbCommand command, CommandEventData eventData, InterceptionResult<object> result)
    {
        ManipulateCommand(command);
        return base.ScalarExecuting(command, eventData, result);
    }

    public override ValueTask<InterceptionResult<object>> ScalarExecutingAsync(DbCommand command, CommandEventData eventData, InterceptionResult<object> result, CancellationToken cancellationToken = default)
    {
        ManipulateCommand(command);
        return base.ScalarExecutingAsync(command, eventData, result, cancellationToken);
    }

    public override InterceptionResult<DbDataReader> ReaderExecuting(DbCommand command, CommandEventData eventData, InterceptionResult<DbDataReader> result)
    {
        ManipulateCommand(command);
        return base.ReaderExecuting(command, eventData, result);
    }

    public override ValueTask<InterceptionResult<DbDataReader>> ReaderExecutingAsync(DbCommand command, CommandEventData eventData, InterceptionResult<DbDataReader> result, CancellationToken cancellationToken = default)
    {
        ManipulateCommand(command);
        return base.ReaderExecutingAsync(command, eventData, result, cancellationToken);
    }

    private static void ManipulateCommand(IDbCommand command)
    {
        if (command.CommandText.StartsWith(SELECT_FOR_UPDATE_INTERNAL_TAG, StringComparison.Ordinal)) // Ändere das Kommando nur wenn es mit dem Tag versehen ist
        {
            command.CommandText = command.CommandText.Replace(SELECT_FOR_UPDATE_INTERNAL_TAG, string.Empty); // Entferne den Tag aus der SQL Abfrage
            command.CommandText = command.CommandText.Replace(REMOVE_FETCH_ROWS, string.Empty); // Entferne FETCH FIRST X ROWS aus der abfrage 
            command.CommandText += FOR_UPDATE; // Füge FOR UPDATE zur Abfrage hinzu.
        }
    }
}
  1. Der Interceptor schaut in der SQL Abfrage nach ob diese mit "-- SELECTFORUPDATE" anfängt.
  2. Wenn das der Fall ist, wird der Kommentar wieder entfernen.
  3. Es wird geprüft ob im SQL ein FETCH FIRST X ROWS steht. Denn SELECT FOR UPDATE funktioniert nicht auf allen Abfragen. und ein SingleOrDefault() Fügt ein FETCH FIRST 2 ROWS zum SQL hinzu, womit SELECT FOR UPDATE nicht arbeiten kann.
  4. Das FOR UPDATE wird an die SQL Abfrage angehängt.

Den Interceptor müssen wir noch an dem DBContext registrieren:

public class ExampleContext : BlogsContext
{
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        => optionsBuilder.AddInterceptors(new SelectForUpdateDbCommandInterceptor());
}

Wir machen jetzt noch eine Extension Methode an dem IQueryable damit wir Abfragen die ein SELECT FOR UPDATE ausführen sollen kennzeichnen. Dieses können wir mit der TagWith Methode machen.

public static class QueryableExtension
{
    public static IQueryable<T> ForUpdate<T>(this IQueryable<T> set)
    {
        return set.TagWith(SelectForUpdateDbCommandInterceptor.SELECT_FOR_UPDATE_TAG);
    }
}

Somit können wir jetzt unsere Abfragen mit der Methode ForUpdate kennzeichnen, damit EF Core die Abfragen gegen die Datenbank mit dem SELECT FOR UPDATE Befehl abschickt.

Fazit

Die SELECT-FOR-UPDATE-Anweisung in Oracle ist ein leistungsstarkes Werkzeug, um Daten während einer Transaktion zu sperren und damit die Konsistenz der Datenbank sicherzustellen. Durch die gezielte Anwendung kann sie dazu beitragen, Dateninkonsistenzen zu vermeiden und die Integrität der Daten zu gewährleisten.

Kategorien

Kontaktieren Sie uns:

Harksheider Weg 60H,
25451 Quickborn
+49 1520 40 73 253
info@gk-itsolutions.de

Schnellzugriff:

Folgt uns auf: