Nehmen Sie ein System mit mehreren gleichzeitigen Produzenten an, die jeweils bestrebt sind, einen Graphen von Objekten mit den folgenden gemeinsamen Entitäten zu erhalten, die durch ihre Namen eindeutig identifizierbar sind:
%Vor% Beispiel: Erzeuger A speichert einige CommonEntityMeeting
s, während Erzeuger B CommonEntitySet
s speichert. Jeder von ihnen muss CommonEntity
s beibehalten, die sich auf die jeweiligen Elemente beziehen.
Grundsätzlich sind die wichtigsten Punkte:
Name
und zugehörige gemeinsame Entität, falls vorhanden (z. B. CommonEntity
ist eindeutig von CommonEntity.Name
+ CommonEntityGroup.Name
)). CommonEntities
- sie geben normalerweise nur DTOs mit Names
(einmalig) von diesen CommonEntities
und zugehörigen Informationen weiter. Also muss Common(Group)Entity
von Name
. Also, mit Entity Framework (Datenbank zuerst, obwohl es wahrscheinlich keine Rolle spielt) als DAL und SQL Server als Lagerung Was ist ein effizienter und zuverlässiger Weg, um sicherzustellen, dass alle diese Hersteller erfolgreich ihre überschneidenden Objekt Graphen gleichzeitig bestehen bleiben?
Wenn Sie berücksichtigen, dass UNIQUE INDEX
bereits dafür sorgt, dass es keine doppelte CommonEntities
gibt (Name, GroupName-Paar ist eindeutig), kann ich folgende Lösungen sehen:
In einem solchen Fall, wenn SaveChanges
für verwandte Entitäten aufgerufen wird, gibt es keine Indexverstöße aufgrund der Tatsache, dass andere Produzenten die gleichen Entitäten einen Moment vorher erstellen.
Um es zu erreichen, werde ich einige
haben %Vor%Bei diesem Ansatz wird es mehrere Aufrufe von SaveChanges geben und jede CommonEntity wird ihre eigene Art von Repository haben, obwohl dies die zuverlässigste Lösung zu sein scheint.
Ein bisschen hässlich und ineffizient (mit 10 CommonEntities
müssen wir es vielleicht 10 mal wiederholen), aber einfach und mehr oder weniger zuverlässig.
Nicht sicher, ob es eine einfache und zuverlässige Möglichkeit gibt, doppelte Einträge in mehr oder weniger komplexen Objektdiagrammen zu ersetzen, obwohl sowohl fallspezifische als auch allgemeinere reflexionsbasierte Lösungen implementiert werden können.
Dennoch, wie bei einer vorherigen Lösung, können mehrere Wiederholungen erforderlich sein.
Bedenken Sie, dass es einfacher ist, innerhalb der gespeicherten Prozedur zu arbeiten. Es werden die gleichen optimistischen oder pessimistischen Ansätze sein, die gerade auf der Datenbankseite implementiert wurden.
Obwohl es möglicherweise eine bessere Leistung bietet (in diesem Fall kein Problem) und die Einfügelogik an einem gemeinsamen Ort ablegt.
Ich würde wahrscheinlich die erste Lösung versuchen, aber vielleicht gibt es bessere Alternativen oder einige mögliche Fallstricke.
Tabelle bewertete Parameter
Eine Option besteht darin, table valued parameters
anstelle von einzelnen Aufrufen für die Datenbank zu verwenden.
Beispielprozedur mit einem Tabellenwertparameter:
%Vor%Tabelle Wert Parameterreferenz:
Ich empfehle merge
nicht, es sei denn, es gibt ein überzeugendes Argument dafür. Diese Situation betrachtet nur das Einfügen, so scheint es wie Overkill.
Beispiel merge
version mit Tabellenwertparameter:
merge
reference:
MERGE
-Anweisung von SQL Server - Aaron Bertrand
ignore_dup_key
Code-Kommentar:
// Überprüfen Sie, ob es sich um einen Namensindex-Verstoß handelte (vielleicht machen Sie die Indizes IGNORE_DUP_KEY)
ignore_dup_key
wird serializable
hinter dem die Szenen ; potenziell kostspieliger Overhead für nicht gruppierte Indizes ; und selbst wenn der Index gruppiert ist, kann kostet abhängig von der Anzahl der Duplikate .
Dies kann in den gespeicherten Prozeduren unter Verwendung von Sam Saffrons Upsert (Update / Einfügen) Muster
Die Wahl des Ansatzes hängt sicherlich von der Art der Funktionalität ab. Datenmenge, die beide Prozeduren verwenden.
Wenn wir mit dem ersten Ansatz fortfahren, wird das Entity Framework für jeden SaveChanges () -Aufruf eine Transaktion anlegen. Dies könnte die Leistung im Falle einer großen Anzahl von Datensätzen ein wenig reduzieren.
Wenn es eine beträchtliche Anzahl von Datensätzen gibt, die eingefügt / aktualisiert werden müssen, dann werde ich sicherlich mit dem Stored Procedure basierten Ansatz gehen. Mit diesem Ansatz haben Sie eine vollständige Kontrolle über die Datenbank & amp; Die Abfrage nach dem Datensatz, um zu prüfen, ob er existiert, wird sehr einfach sein (obwohl hier einige Feinabstimmungen erforderlich sein können). Ich sehe nicht, ob es irgendwelche Herausforderungen geben würde, dasselbe mit gespeicherten Prozeduren zu implementieren. Mit wenigen Implementierungsoptimierungen, wie dem Laden der Daten in temporäre Tabellen (nicht sql temporäre Tabellen, sondern physische Tabellen, die zum vorübergehenden Speichern von Daten verwendet werden können), kann dies weiter verbessert werden, um vollständige Informationen zu erhalten, die die gespeicherte Prozedur verarbeitet hat.
Basierend auf Ihrem letzten wichtigen Punkt besteht eine andere Lösung darin, Ihre Erstellungslogik auf einen zentralen Anwendungsserver / -dienst zu verschieben (siehe Update 2) , der eine Warteschlange zum Hinzufügen von Datensätzen verwenden kann .
Da die meisten Ihrer Datensätze bereits existieren, sollten Sie, wenn Sie eine Art Caching verwenden, dies sehr effizient machen können
Nun, über die Anzahl der Datensätze.
Man muss bedenken, dass die EF nicht dafür ausgelegt ist, "Massen" -Operationen zu unterstützen, daher wird die Erstellung von Tausenden von Datensätzen (wirklich, wirklich) langsam sein.
Ich habe 2 Lösungen verwendet, die Ihnen und einer großen Anzahl von Datensätzen sehr schnell helfen
1) EntityFramework.BulkInsert
2) SqlBulkCopy
Beide sind extrem einfach zu bedienen
Ich hoffe auch, dass Sie den schnellsten Weg zum Einfügen in Entity gesehen haben Framework
Aktualisieren
Unten ist eine andere Lösung, die ich kürzlich zweimal benutzt habe
Speichern Sie Ihren Datensatz nicht, wenn ein Benutzer einen "Speichern" -Aufruf ausführt, sondern legen Sie fest, dass er X Sekunden später ausgeführt wird.
Wenn in der Zwischenzeit ein anderer Benutzer versucht, den gleichen Datensatz zu speichern, "schieben" Sie einfach das geplante Datum.
Unten sehen Sie einen Beispielcode, der versucht, den gleichen Datensatz 10 Mal (zur gleichen Zeit) zu speichern, aber die tatsächliche Speicherung geschieht nur einmal.
Das tatsächliche Ergebnis kann hier gesehen werden:
%Vor%Update 2 Da Sie den "creation" -Prozess in Ihrer WebAPI-App steuern können, sollten Sie in der Lage sein, das Duplizieren mit einer Art Cache zu vermeiden, wie im folgenden Pseudocode
%Vor%Hersteller wissen / interessieren sich nicht für IDs dieser CommonEntities - sie in der Regel nur DTOs mit Namen (eindeutig) von diesen CommonEntities und verwandte Informationen. Also muss jede gemeinsame (Gruppen-) Entität sein gefunden / erstellt von Name.
Ich gehe davon aus, dass Tabellen, in denen Ihre Objekte gespeichert sind, auf CommonEntity
mit ID
und nicht auf Name
verweisen.
Ich nehme an, dass die Tabellendefinition des Objekts ungefähr so aussieht:
%Vor% Gleichzeitig hat die Funktion SaveSomeObject
auf hoher Ebene CommonEntity.Name
und CommonEntityGroup.Name
(nicht ID
) als Parameter. Es bedeutet, dass die Funktion irgendwo die Name
der Entität nachschlagen und ihre entsprechende ID
finden muss.
Somit kann die Funktion SaveSomeObject
auf hoher Ebene mit den Parametern (ObjectName, CommonEntityName, CommonEntityGroupName)
in zwei Schritten implementiert werden:
GetCommonEntityID
ist eine Hilfsfunktion / gespeicherte Prozedur, die die Entität ID
nach ihrer Name
sucht und bei Bedarf eine Entität erstellt (generiert ID
).
Hier extrahieren wir diesen Schritt explizit in eine separate dedizierte Funktion. Nur diese Funktion muss mit Nebenläufigkeitsproblemen umgehen. Es kann unter Verwendung von optimistischem Concurrency-Ansatz oder pessimistisch implementiert werden. Dem Benutzer dieser Funktion ist es egal, welche Magie er verwendet, um eine gültige ID
zurückzugeben, aber der Benutzer kann sicher sein, dass er die zurückgegebene ID
verwenden kann, um den Rest des Objekts zu erhalten.
Pessimistischer Concurrency-Ansatz
Der pessimistische Nebenläufigkeitsansatz ist einfach. Stellen Sie sicher, dass nur eine Instanz von
GetCommonEntityID
kann ausgeführt werden. Ich würde sp_getapplock
dafür verwenden (anstelle von SERIALIZABLE Transaktionsisolationslevel oder Tabellenhinweisen) . sp_getapplock
ist im Wesentlichen ein Mutex und sobald eine Sperre erreicht ist, können wir sicher sein, dass keine andere Instanz dieser gespeicherten Prozedur parallel läuft. Dies macht die Logik einfach - versuchen Sie, die ID
und INSERT
die neue Zeile zu lesen, wenn sie nicht gefunden wird.
Optimistischer Concurrency-Ansatz
Versuchen Sie nicht, etwas zu verriegeln. Handeln Sie optimistisch und schauen Sie sich ID
an. Wenn nicht gefunden, versuchen Sie INSERT
den neuen Wert und versuchen Sie es erneut, wenn eine eindeutige Indexverletzung vorliegt.
Bei beiden Ansätzen sollten Sie die Logik wiederholen. Der optimistische Ansatz ist im Allgemeinen besser, wenn Sie erwarten, dass die Namen bereits in der Entitätentabelle vorhanden sind und die Wahrscheinlichkeit von Wiederholungen gering ist (wie in Ihrem Fall in der Frage beschrieben). Der pessimistische Ansatz ist im Allgemeinen besser, wenn Sie erwarten, dass viele konkurrierende Prozesse den gleichen Namen einfügen. Sie sind wahrscheinlich besser dran, wenn Sie Inserts serialisieren.
Tags und Links sql-server c# entity-framework sql-insert optimistic-concurrency