it-swarm.com.de

Sollte ich überprüfen, ob etwas in der Datenbank vorhanden ist und schnell ausfallen oder auf eine Datenbankausnahme warten?

Zwei Klassen haben:

public class Parent 
{
    public int Id { get; set; }
    public int ChildId { get; set; }
}

public class Child { ... }

Sollte ich beim Zuweisen von ChildId zu Parent zuerst prüfen, ob es in der Datenbank vorhanden ist, oder warten, bis die Datenbank eine Ausnahme auslöst?

Zum Beispiel (mit Entity Framework Core):

[~ # ~] note [~ # ~] Diese Arten von Überprüfungen sind im gesamten Internet auch in offiziellen Microsoft-Dokumenten: https://docs.Microsoft.com/en-us/aspnet/mvc/overview/getting-started/getting-started-with-ef-using- MVC/Handling-Parallelität-mit-dem-Entity-Framework-in-einem-Asp-Net-MVC-Anwendung # Modify-the-Department-Controller , aber es gibt zusätzliche Ausnahmebehandlung für SaveChanges

beachten Sie außerdem, dass die Hauptabsicht dieser Überprüfung darin bestand, dem Benutzer der API eine benutzerfreundliche Nachricht und den bekannten HTTP-Status zurückzugeben und Datenbankausnahmen nicht vollständig zu ignorieren. Und die einzige Ortsausnahme, die ausgelöst wird, befindet sich innerhalb des Aufrufs SaveChanges oder SaveChangesAsync. Es gibt also keine Ausnahme, wenn Sie FindAsync oder Any aufrufen. . Wenn also ein Kind vorhanden ist, aber vor SaveChangesAsync gelöscht wurde, wird eine Parallelitätsausnahme ausgelöst.

Ich tat dies aufgrund einer Tatsache, dass foreign key violation Ausnahme ist viel schwieriger zu formatieren, um anzuzeigen "Kind mit ID {parent.ChildId} konnte nicht gefunden werden."

public async Task<ActionResult<Parent>> CreateParent(Parent parent)
{
    // is this code redundant?
   // NOTE: its probably better to use Any isntead of FindAsync because FindAsync selects *, and Any selects 1
    var child = await _db.Children.FindAsync(parent.ChildId);
    if (child == null)
       return NotFound($"Child with id {parent.ChildId} could not be found.");

    _db.Parents.Add(parent);    
    await _db.SaveChangesAsync();        

    return parent;
}

gegen:

public async Task<ActionResult<Parent>> CreateParent(Parent parent)
{
    _db.Parents.Add(parent);
    await _db.SaveChangesAsync();  // handle exception somewhere globally when child with the specified id doesn't exist...  

    return parent;
}

Das zweite Beispiel in Postgres wirft 23503 foreign_key_violation Fehler: https://www.postgresql.org/docs/9.4/static/errcodes-appendix.html

Der Nachteil der Behandlung von Ausnahmen auf diese Weise in ORM wie EF besteht darin, dass sie nur mit einem bestimmten Datenbank-Backend funktionieren. Wenn Sie jemals zu SQL Server oder etwas anderem wechseln wollten, funktioniert dies nicht mehr, da sich der Fehlercode ändert.

Wenn Sie die Ausnahme für den Endbenutzer nicht richtig formatieren, werden möglicherweise einige Dinge angezeigt, die nur Entwickler sehen sollen.

Verbunden:

https://stackoverflow.com/questions/6171588/preventing-race-condition-of-if-exists-update-else-insert-in-entity-framework

https://stackoverflow.com/questions/4189954/implementing-if-not-exists-insert-using-entity-framework-without-race-conditions

https://stackoverflow.com/questions/308905/should-there-be-a-transaction-for-read-queries

32
Konrad

Eher eine verwirrte Frage, aber JA Sie sollten zuerst prüfen und nicht nur eine DB-Ausnahme behandeln.

Zunächst befinden Sie sich in Ihrem Beispiel auf der Datenebene und verwenden EF direkt in der Datenbank, um SQL auszuführen. Ihr Code entspricht dem Ausführen

select * from children where id = x
//if no results, perform logic
insert into parents (blah)

Die Alternative, die Sie vorschlagen, ist:

insert into parents (blah)
//if exception, perform logic

Die Verwendung der Ausnahme zum Ausführen der bedingten Logik ist langsam und allgemein verpönt.

Sie haben eine Rennbedingung und sollten eine Transaktion verwenden. Dies kann jedoch vollständig im Code erfolgen.

using (var transaction = new TransactionScope())
{
    var child = await _db.Children.FindAsync(parent.ChildId);
    if (child == null) 
    {
       return NotFound($"Child with id {parent.ChildId} could not be found.");
    }

    _db.Parents.Add(parent);    
    await _db.SaveChangesAsync();        
    transaction.Complete();

    return parent;
}

Der Schlüssel ist, sich zu fragen:

"Erwarten Sie diese Situation?"

Wenn nicht, dann sicher einfügen und eine Ausnahme auslösen. Behandeln Sie die Ausnahme jedoch wie jeden anderen Fehler, der auftreten kann.

Wenn Sie damit rechnen, ist dies KEINE Ausnahme. Sie sollten überprüfen, ob das Kind zuerst existiert, und mit der entsprechenden freundlichen Nachricht antworten, wenn dies nicht der Fall ist.

Bearbeiten - Es scheint eine Menge Kontroversen darüber zu geben. Bevor Sie abstimmen, sollten Sie Folgendes berücksichtigen:

A. Was wäre, wenn es zwei FK-Einschränkungen gäbe? Würden Sie befürworten, die Ausnahmemeldung zu analysieren, um herauszufinden, welches Objekt fehlte?

B. Wenn Sie einen Fehler haben, wird nur eine SQL-Anweisung ausgeführt. Es sind nur Treffer, die die zusätzlichen Kosten einer zweiten Abfrage verursachen.

C. Normalerweise ist Id ein Ersatzschlüssel. Es ist schwer vorstellbar, dass Sie einen kennen und nicht sicher sind, ob er sich in der Datenbank befindet. Überprüfen wäre seltsam. Aber was ist, wenn es sich um einen natürlichen Schlüssel handelt, den der Benutzer eingegeben hat? Das könnte eine hohe Wahrscheinlichkeit haben, nicht anwesend zu sein

4
Ewan

Das Überprüfen auf Eindeutigkeit und das anschließende Einstellen ist ein Antimuster. Es kann immer vorkommen, dass die ID gleichzeitig zwischen Prüfzeit und Schreibzeit eingefügt wird. Datenbanken sind so ausgestattet, dass sie dieses Problem durch Mechanismen wie Einschränkungen und Transaktionen lösen können. Die meisten Programmiersprachen sind es nicht. Wenn Sie die Datenkonsistenz schätzen, überlassen Sie sie daher dem Experten (der Datenbank), d. H. Führen Sie die Einfügung durch und fangen Sie eine Ausnahme ab, wenn sie auftritt.

111
Kilian Foth

Ich denke, was Sie "schnell scheitern" nennen und was ich es nenne, ist nicht dasselbe.

Wenn Sie der Datenbank mitteilen, dass sie Änderungen vornehmen und den Fehler beheben soll, ist that schnell. Ihr Weg ist kompliziert, langsam und nicht besonders zuverlässig.

Ihre Technik scheitert nicht schnell, sie ist „Preflighting“. Es gibt manchmal gute Gründe, aber nicht, wenn Sie eine Datenbank verwenden.

38
gnasher729

Dies begann als Kommentar, wurde aber zu groß.

Nein, wie in den anderen Antworten angegeben, sollte dieses Muster nicht verwendet werden. *

Wenn es sich um Systeme handelt, die asynchrone Komponenten verwenden, gibt es immer eine Race-Bedingung, bei der sich die Datenbank (oder das Dateisystem oder ein anderes asynchrones System) zwischen der Prüfung und der Änderung ändern kann. Eine Überprüfung dieses Typs ist einfach kein zuverlässiger Weg, um die Art des Fehlers zu verhindern, den Sie nicht behandeln möchten.
Schlimmer als nicht ausreichend, auf den ersten Blick erweckt es den Eindruck, dass es verhindern sollte, dass der doppelte Datensatzfehler ein falsches Sicherheitsgefühl vermittelt .

Sie benötigen die Fehlerbehandlung trotzdem.

In Kommentaren haben Sie gefragt, was passiert, wenn Sie Daten aus mehreren Quellen benötigen.
Immer noch nein.

Das grundlegende Problem verschwindet nicht, wenn das, was Sie überprüfen möchten, komplexer wird.

Sie benötigen die Fehlerbehandlung trotzdem.

Selbst wenn diese Überprüfung ein zuverlässiger Weg wäre, um den bestimmten Fehler zu verhindern, gegen den Sie sich schützen möchten, können dennoch andere Fehler auftreten. Was passiert, wenn Sie die Verbindung zur Datenbank verlieren oder der Speicherplatz knapp wird oder?

Sie benötigen höchstwahrscheinlich noch andere datenbankbezogene Fehlerbehandlungen. Die Behandlung dieses speziellen Fehlers sollte wahrscheinlich ein kleiner Teil davon sein.

Wenn Sie Daten benötigen, um zu bestimmen, was geändert werden soll, müssen Sie diese natürlich von irgendwoher sammeln. (Je nachdem, welche Tools Sie verwenden, gibt es wahrscheinlich bessere Möglichkeiten als separate Abfragen, um sie zu erfassen.) Wenn Sie bei der Prüfung der von Ihnen gesammelten Daten feststellen, dass Sie die Änderung schließlich nicht vornehmen müssen, großartig, nehmen Sie die nicht vor Veränderung. Diese Bestimmung ist völlig unabhängig von Bedenken hinsichtlich der Fehlerbehandlung.

Sie benötigen die Fehlerbehandlung trotzdem.

Ich weiß, dass ich mich wiederhole, aber ich halte es für wichtig, dies klar zu machen. Ich habe dieses Chaos schon einmal aufgeräumt.

Es wird irgendwann scheitern. Wenn es fehlschlägt, wird es schwierig und zeitaufwändig sein, dem auf den Grund zu gehen. Es ist schwierig, Probleme zu lösen, die sich aus den Rennbedingungen ergeben. Sie kommen nicht konsequent vor, daher ist es schwierig oder sogar unmöglich, sie isoliert zu reproduzieren. Sie haben anfangs nicht die richtige Fehlerbehandlung durchgeführt, sodass Sie wahrscheinlich nicht viel zu tun haben: Vielleicht ein Bericht eines Endbenutzers über einen kryptischen Text (den Sie eigentlich gar nicht sehen wollten). Vielleicht sollte eine Stapelverfolgung, die auf diese Funktion verweist, die, wenn Sie sie sich ansehen, den Fehler sogar leugnet, sogar möglich sein.

* Es kann gültige geschäftliche Gründe für die Durchführung dieser vorhandenen Überprüfungen geben, z. B. um zu verhindern, dass die Anwendung teure Arbeiten dupliziert. Dies ist jedoch kein geeigneter Ersatz für eine ordnungsgemäße Fehlerbehandlung.

15
Mr.Mindor

Ich denke, eine sekundäre Sache, die Sie hier beachten sollten - einer der Gründe, warum Sie dies wünschen, ist, dass Sie eine Fehlermeldung formatieren können, die der Benutzer sehen kann.

Ich würde Ihnen wärmstens empfehlen:

a) Zeigen Sie dem Endbenutzer dieselbe generische Fehlermeldung für jeden Fehler, der auftritt.

b) Protokollieren Sie die tatsächliche Ausnahme an einem Ort, auf den nur die Entwickler zugreifen können (wenn sie sich auf einem Server befinden) oder an einem Ort, der von Tools zur Fehlerberichterstattung an Sie gesendet werden kann (wenn der Client bereitgestellt wurde).

c) Versuchen Sie nicht, die von Ihnen protokollierten Fehlerausnahmedetails zu formatieren, es sei denn, Sie können weitere nützliche Informationen hinzufügen. Sie möchten nicht versehentlich die eine nützliche Information "formatiert" haben, mit der Sie ein Problem aufspüren könnten.


Kurz gesagt - Ausnahmen sind voller sehr nützlicher technischer Informationen. Nichts davon sollte für den Endbenutzer sein und Sie verlieren diese Informationen auf eigene Gefahr.

2
Paddy