Laden...

DataSets aktuell halten / Postgres

Erstellt von alex-abc vor 18 Jahren Letzter Beitrag vor 17 Jahren 4.609 Views
A
alex-abc Themenstarter:in
8 Beiträge seit 2006
vor 18 Jahren
DataSets aktuell halten / Postgres

Hallo,

mein Problem hat Ähnlichkeit mit den Transaktionsproblemen aus der Datenbanktechnologie.

Mein System hat eine zentrale PostgreSQL Datenbank die auf einem zentralen Server läuft. Meine Applikation soll auf mehreren Client-Rechnern installiert werden (sagen wir A und B).

Die Applikation füllt die DataSets aus der Datenbank. Wenn jetzt A Zeile 1 auf der Datenbank ändert, bekommt B davon nichts mit, da der DataSet noch die alten Daten enthält. Wenn B Zeile 1 verändert, wusste er nichts davon, dass die Zeile 1 die er noch im DataSet hatte, bereits verändert wurde.

Letzendlich ist die Frage, ob ich feststellen kann, wenn der alte Wert aus dem DataSet vor der Aktualisierung nicht mit dem derzeitigen Wert aus der Datenbank übereinstimmt?

Mein Primary Key-Feld ist ein Autoincrement-Wert. Zur Zeit lade ich das DataSet immer wieder neu um den aktuellen Wert zu erhalten. Gibt es da was besseres?

Danke für deine Antwort
Alex

3.728 Beiträge seit 2005
vor 18 Jahren
Parallelitätsverletzung

Es ist ein Problem aus der Datenbankwelt. Solche Parallelitätsverletzungen gibt es in jeder Datenbank sobald mehrere Benutzer gleichzeitig arbeiten. Es gibt verschiedene Konzepte, wie sie zu lösen sind.

Zunächst muss Deine Anwendung erkennen, wenn eine Parallelitätsverletzung passiert. Das geht sehr einfach. Du prüfst vor dem speichern eines Datensatzes ob die Feldwerte in der Datenbank den Werten vor der lokalen Änderung entsprechen. Über die DataRowVersion.Original kannst Du die Ursprungsfeldwerte vor der lokalen Änderung der DataRow abrufen. Wenn die Datenbank in min. einem Feld vom Originalwert der DataRow abweicht, muss jemand in der Zwischenzeit eine Änderung gemacht haben. Die .NET CommandBuilder implementieren diese Vorgehensweise standardmäßig. Bei einer Parallelitätsverletzung, wird eine System.Data.DBConcurrencyException geworfen. Du musst dann nur noch die Exception fangen und kannst weitere Schritte zum lösen des Konflikts einleiten.

Ich sehe folgende Möglichkeiten, um auf Parallelitätsverletzungen zu reagieren:
*Wer zuletzt schreibt, gewinnt (Diese Vorgehensweise ist meistens nicht empfehlenswert) *Der Benutzer bekommt eine Fehlermeldung, dass er seine Änderungen nicht speichern kann, da der Datensatz inzwischen geändert wurde. *Dem Benutzer werden die Änderungen der Dritten angezeigt und er muss entscheiden, welche Version gespeichert wird. *Beide Änderungen werden zusammengeführt (merging) und der Benutzer muss sich bei Überschneidungen für eine Version entscheiden

Es ist sinnvoll eine Komponente zu schreiben, die sich zentral um die Lösung von Parallelitätsverletzungen kümmert (z.B. ein standardisierter Dialog).

476 Beiträge seit 2004
vor 18 Jahren

Original von alex-abc
Mein Primary Key-Feld ist ein Autoincrement-Wert. Zur Zeit lade ich das DataSet immer wieder neu um den aktuellen Wert zu erhalten. Gibt es da was besseres?

Ja, da gibt es besseres. Einen auto increment würde ich nicht mehr verwenden. Wenn du noch am Anfang bei der Entwicklung deiner Anwendung bist, würde ich dir raten auf GUID's für den Primärschlüssel zurückgreifen. Die sind aufjedenfall eindeutig.

Andernfalls würde ich das ganze über das "KeyProvider"-Pattern (ob das so heisst oder der Name nur meiner Phantasie entspringt weiß ich nicht ;o) )lösen. Du erstellst in deiner Datenbank eine eigene Tabelle für die Primärschlüssel. Diese enthält dann einen Tabellennamen und den dort zuletzt zugefügten Primärschlüssel. Willst du dann einen neuen Datensatz in die Tabelle einfügen, rufst du erst den Wert aus der KeyProvider-Tabelle ab, erhöhst diesen um 1 und gibst diesen Wert auch zurück. Diesen Primärschlüssel benutzt du dann für den neuen Datensatz in deiner DataTable. Aber nicht vergessen dann das auto increment abzuschalten... ;o)

Beispiel:

War der letzte Primärschlüssel 4123, dann erhöhst diesen Wert um 1 auf 4124, speicherst den als letzten Primärschlüssel in der "KeyProvider"-Tabelle und gibst dann 4124 zurück. Der Abruf selbst lässt sich prima in einer extra Komponente ausgliedern so, dass du sie überall verwenden kannst.

-yellow

Selbst ein Weg von tausend Meilen beginnt mit einem Schritt (chinesisches Sprichwort).

Mein Blog: Yellow's Blog auf sqlgut.de

G
131 Beiträge seit 2005
vor 18 Jahren

Das mit den GUID's als Primärschlüssel ist nach meiner Meinung Ressourcenverschwendung und hat mir im Handling eigentlich immer mehr Nachteile gebracht als Vorteile. Ich nutze immer AutoInc-Felder, da diese auch immer eindeutig sind. GUID's kommen bei mir immer nur zum Einsatz, wenn es um Replikation geht und dann dort als eine extra Spalte.

Seit wann muss du immer die Daten vom Server holen, damit die PK's aktuell sind?Wenn du z.B. ein DataSet hast, würde ich die PK's dort so einstellen, dass sie negativ zählen also -1, -2, -3 .... Die kannst du dann auch als ForeignKey nutzen. Wenn du dann mal speicherst in die DB, gleicht der DataAdapter bzw. das DataSet die Key's automatisch ab.

3.728 Beiträge seit 2005
vor 18 Jahren
Bitte um Aufklärung

Wenn ich in einer Tabelle einen Auto-Inrement Schlüssel habe, und neue DataRows mit negativen Werten für das Schlüsselfeld hinzufügen, ersetzt der DataAdapter beim aufruf von Update diese negativen Werte durch die richtigen Schlüssel, der die Datenbank erzeugt hat??

Das ist mir neu. In der Dokumentation des SqlDataAdapters steht davon nichts (Bitte um korrektur, sollte ich es übersehen haben).

A
alex-abc Themenstarter:in
8 Beiträge seit 2006
vor 18 Jahren

Das mit dem negativen Autoincrement-Werten funktioniert. Hier ein Codebeispiel:

 dataAdapter = new NpgsqlDataAdapter(commandString,connectionString_pg);
dataAdapter.MissingSchemaAction = MissingSchemaAction.AddWithKey;               
dataSet = new DataSet();
dataAdapter.Fill(dataSet,"temp");
dataSet.Tables["temp"].Columns["auto"].AutoIncrement = true;
dataSet.Tables["temp"].Columns["auto"].AutoIncrementSeed = -1;
dataSet.Tables["temp"].Columns["auto"].AutoIncrementStep = -1;

Der negative Wert wird später in der Datenbank ersetzt durch den aktuellen Wert.

Dazu hat Svenson einen guten Link gepostet:
http://www.addison-wesley.de/media_remote/katalog/bsp/3827319978bsp.pdf

Bei mir gibt es nun folgendes Problem.
Ich habe eine Listbox, die die Werte aus der Datenbank enthält. Wird ein Datensatz hinzugefügt wird auch die Listbox neu geladen aus dem DataSet jedoch mit den negativen Autoincrement-Werten. Durch Markierung der Listbox kann man das Feld updaten. Wenn ich nun die neue Zeile updaten möchte, funktioniert die Sache nicht mehr mit der Meldung:
DBConcurrencyException ->Der UpdateCommand hat sich auf 0 Datensätze ausgewirkt. Klar, weil im DataSet -1 als Wert steht und in der Datenbank etwas anderes.

Erst wenn die Listbox aus der Datenbank neugeladen wird, funktionierts...

Weiß jemand, wie man Fehlermeldungen aus der Datenbank abfangen kann? (z. B. foreign-key violation)

Alex-abc

476 Beiträge seit 2004
vor 17 Jahren

hallo allerseits,

ich komme für ein Projekt nicht drumherum und muss mit Tabellen mit AutoIncrement-Schlüsseln arbeiten. Dabei habe ich etwas unschönes beim Verwenden von AutoIncrementSeed festgestellt. Wenn ich jetzt den Code von alex-abc als Beispiel nehme:

Original von alex-abc
Das mit dem negativen Autoincrement-Werten funktioniert. Hier ein Codebeispiel:

 dataAdapter = new NpgsqlDataAdapter(commandString,connectionString_pg);  
dataAdapter.MissingSchemaAction = MissingSchemaAction.AddWithKey;                 
dataSet = new DataSet();  
dataAdapter.Fill(dataSet,"temp");  
dataSet.Tables["temp"].Columns["auto"].AutoIncrement = true;  
dataSet.Tables["temp"].Columns["auto"].AutoIncrementSeed = -1;  
dataSet.Tables["temp"].Columns["auto"].AutoIncrementStep = -1;  

Der negative Wert wird später in der Datenbank ersetzt durch den aktuellen Wert.

Sind in der Tabelle "temp" bereits Datensätze enthalten, welche in der Schlüssel-Spalte "auto" positive Werte aufweisen, wird beim erstellen eines neuen Datensatzes der höchste Schlüssel verwendet und davon -1 abgezogen um den neuen Schlüssel zu erstellen. Sind beispielsweise 5 Datensätze mit den Schlüsseln 1,2,3,4,5 vorhanden und verwende ich dann

DataRow row = dataSet.Tables["temp"].NewRow();
row["spalte1"] =  spaltenwert1;
...
dataSet.Tables["temp"].Rows.Add(row);

bekommt der neue Datensatz in der Schlüsselspalte "auto" den Wert 4, der aber bereits in der Tabelle existiert und somit eine Exception auslöst.

Ist euch das auch aufgefallen und kennt jemand eine Möglichkeit dieses elegant zu umgehen?

-yellow

Selbst ein Weg von tausend Meilen beginnt mit einem Schritt (chinesisches Sprichwort).

Mein Blog: Yellow's Blog auf sqlgut.de

563 Beiträge seit 2004
vor 17 Jahren

wow, super link von Svenson, danke alex-abc dass du Ihn nochmals gepostest hast 🙂

@Yellow: Ich verstehe nicht, warum du nicht einfach autoincrement verwendest. Ob du nun die Keys über deine Tabelle erstellst, oder per Autoincrement spielt keine Rolle und löst das Problem, welches im Link beschrieben wird, nicht.

.unreal

476 Beiträge seit 2004
vor 17 Jahren

Original von .unreal
@Yellow: Ich verstehe nicht, warum du nicht einfach autoincrement verwendest. Ob du nun die Keys über deine Tabelle erstellst, oder per Autoincrement spielt keine Rolle und löst das Problem, welches im Link beschrieben wird, nicht.

hallo .unreal,

ich verwende ja autoincrement (siehe beispiel-code ;o))... nur der neue autoincrement-schlüssel wird, außgehend vom letzten schlüssel um 1 verringert. das ist ja das problem. würde er, wie ich es gerne haben würde, ausgehend von -1 um eins verringert wäre das so wie ich es mir wünschen würde.

-yellow

Selbst ein Weg von tausend Meilen beginnt mit einem Schritt (chinesisches Sprichwort).

Mein Blog: Yellow's Blog auf sqlgut.de

S
8.746 Beiträge seit 2005
vor 17 Jahren

Normalerweise brauchst du das gar nicht selbst setzen. Das macht eigentlich nur Sinn, wenn du die Table frisch erstellst und dann z.B. als XML speicherst.

Für die DB-Nutzung definierst du das Feld in der DB selbst als AutoIncrement und gibst dort direkt den Seed und das Increment an.

Im Code brauchst du dich nicht mehr darum zu kümmern (mit der Update-Ausnahme).

476 Beiträge seit 2004
vor 17 Jahren

hallo svenson,

das ich mich im Code um die Vergabe des AutoIncrements nicht scheren muss ist mir schon klar. Das Unschöne ist, wie bereits oben beschrieben, dass ich nicht automatisch temporäre negative Primärschlüssel erzeugt werden, wenn in der Tabelle bereits Datensätze enthalten sind. Dann zieht er einfach vom höchsten Schlüssel den in AutoIncrementStep gespeicherten Wert ab, der eventuell bereits in Tabelle vorhanden ist und so beim Hinzufügen eine Exception wirft.

Sinn macht es die DataTable mit dem von der DB generierten Primärschlüssel zu aktualisieren, weil ich die Datensätze auch für Master-/Detail Beziehungen weiter benutzen möchte, ohne den Datensatz neu aus der Datenbank abzurufen.

-yellow

Selbst ein Weg von tausend Meilen beginnt mit einem Schritt (chinesisches Sprichwort).

Mein Blog: Yellow's Blog auf sqlgut.de