Benutzer mit den meisten Antworten
DataTable auf Datenbank übertragen und Primary Key von der Datenbank erhalten

Frage
-
Hallo zusammen,
wie geht man vor wenn man hinzugefügte Datensätze in einem DataTable auf die Datenbank (MS SQL-Server) übertragen möchte und gleichzeitig die (bisher temporär gesetzte) Primary-Key Spalte aktualisieren will?
Ich habe hier ein Buch liegen da gehen die darauf irgendwie überhaupt nicht ein. Es wird nur beschrieben wie man mittels "@@IDENTITY" die ID des letzen Datensatzes abruft, aber die ID-Spalte im DataTable ist ja Schreibgeschützt.
In einem anderen Beispiel wird das Update durchgeführt, danach die DataTable gelöscht (DataTable.Clear) und dann neu befüllt - aber das kann doch nicht Zweckmäßig sein...Public Sub UpdateDatabase(ByVal dt As DataTable, ByVal TableName As String) As Integer Dim conn As New SqlConnection(ConnectionString) conn.Open() Dim sqlDataadapter As New SqlDataAdapter("SELECT * FROM " & TableName, conn) Dim cb As New SqlCommandBuilder(sqlDataadapter) ''Neue Zeilen einzeln hinzufügen und ID auslesen ' -> geht nicht, da ID Schreibgeschützt! 'For Each dr As DataRow In From _dr As DataRow In dt Where _dr.RowState = DataRowState.Added ' sqlDataadapter.Update({dr}) ' dr(dt.PrimaryKey(0)) = New SqlCommand("SELECT @@IDENTITY FROM " & TableName, conn) ' dr.AcceptChanges() 'Next sqlDataadapter.Update(dt) sqlDataadapter.Fill(dt) cb.Dispose() sqlDataadapter.Dispose() conn.Dispose() End Sub
Versteh ich da was Grundsätzliches falsch?
Vielen Dank und viele Grüße
Mike
Antworten
-
Hi Mike,
die ID automatisch mit 1 beginnend zu vergeben, ist keine gute Idee. Das funktioniert zwar, aber nur solange keine Fremdschlüssel damit verbunden sind (z.B. Master-Child). Besser ist es, im Programm negative Werte zu nutzen. Da kann es keine Konflikte geben.Der CommandBuilder (Verweisvariable cb) braucht nicht genutzt zu werden, solange die Standardeinstellungen zur Generierung der SQL Anweisungen ausreichend sind. Wenn aber der "Der Letzte gewinnt" einzustellen ist, ist "cb" für die Einstellung zu nutzen. Mit der Übergabe des Verweises auf den DataAdapter im Konstruktor wird die Beziehung zwischen CommandBuilder und DataAdapter hergestellt und der DataAdapter fragt bei Bedarf den Commandbuilder, dass dieser ihm auf Basis des SelectCommands eine SQL Anweisung bereitstellt. Der Bedarf liegt dann vor, wenn dem DataAdapter kein Command-Objekt für Update, Delete, Insert zugewiesen wurde.
Für das Einfügen und Rücklesen kann die folgende SQL Anweisung genutzt werden:
INSERT INTO [dbo].[Tab1] ([info]) VALUES (@info); SELECT Id, info FROM Tab1 WHERE (Id = SCOPE_IDENTITY())
Den gesamten Ablauf kann man so demonstrieren:
Debug.Print("---") Using da As New SqlDataAdapter("SELECT ID,info FROM Tab1", My.Settings.Database1ConnectionString) Dim dt As New DataTable With dt.Columns With .Add("ID", GetType(Integer)) .AutoIncrement = True .AutoIncrementSeed = -1 .AutoIncrementStep = -1 End With End With da.Fill(dt) Dim row = dt.NewRow row(1) = "Neue Zeile" dt.Rows.Add(row) Debug.Print("Vergebene ID: {0}", row(0)) Dim cmd As New SqlCommand("INSERT INTO [dbo].[Tab1] ([info]) VALUES (@info); SELECT Id, info FROM Tab1 WHERE (Id = SCOPE_IDENTITY())", da.SelectCommand.Connection) Dim par As New SqlParameter With par .ParameterName = "@info" .SourceVersion = DataRowVersion.Current .SourceColumn = "info" End With cmd.Parameters.Add(par) da.InsertCommand = cmd Debug.Print("SQL Anweisung für Insert: {0}", da.InsertCommand.CommandText) da.Update(dt) Debug.Print("ID nach Update: {0}", row(0)) End Using
Parallelitätsverletzung kommt, wenn die Originalversion im Client (=Programm) nicht mit dem Inhalt in der Datenbank übereinstimmt. Das kann der Fall sein, wenn AcceptChanges falsch angewandt wurde oder ein Datensatz nach dem Update in der Datenbank nicht zurückgelesen wurde, damit wieder beide Inhalte übereinstimmen. Auch kann eine Parallelitätsverletzung entstehen, wenn im Mehrnutzerbetrieb positive Autowerte vergeben werden.
--
Viele Grüsse
Peter Fleischer (MVP, Partner)
Meine Homepage mit Tipps und Tricks
Kommas richtig setzen!
Schüler sagen, Lehrer haben es gut.
Schüler, sagen Lehrer, haben es gut
- Bearbeitet Peter Fleischer Samstag, 30. Januar 2016 20:37
- Als Antwort markiert Mike0680 Montag, 1. Februar 2016 18:40
Alle Antworten
-
Hallo Mike,
ich glaube da wirfst du etwas durcheinander.
1. schalte OPTION EXPLICIT und STRICT auf ON.
2. Möchtest du eine SUB oder FUNCTION erstellen? Du schreibst SUB und diese soll ein INTEGER liefern.Ansonsten hab ich dir mal ein bisschen Code zusammengestellt wo das Einfügen in die DB funktionieren sollte und dein SKALAR zurück in die übergebene Tabelle geschrieben wird.
Public Function UpdateDatabase(ByVal dt As DataTable, ByVal TableName As String) As DataTable Using con As New SqlConnection("connectionstring") 'Command vorbereiten zum eintragen von neuen Datensätzen Dim cmd As New SqlCommand("INSERT INTO TabelleXYZ (Wert1, Wert2, Wert3) VALUES (@Wert1, @Wert2, @Wert3);SELECT CAST(scope_identity() AS int)", con) 'Parameter definieren cmd.Parameters.Add("@Wert1", SqlDbType.VarChar) cmd.Parameters.Add("@Wert2", SqlDbType.VarChar) cmd.Parameters.Add("@Wert3", SqlDbType.VarChar) 'Übergebene Tabelle Zeile für Zeile durchlaufen For Each rw As DataRow In dt.Rows 'Parameter mit Informationen aus der übergebenen Tabelle zuweisen cmd.Parameters("@Wert1").Value = rw.Field(Of String)("Name") cmd.Parameters("@Wert2").Value = rw.Field(Of String)("Vorname") cmd.Parameters("@Wert3").Value = rw.Field(Of String)("Adresse") 'Feuer! Und der abgerufene SCALAR soll in die übergebene Tabelle zurückgeschrieben werden. rw("ID") = DirectCast(cmd.ExecuteScalar(), Integer) Next End Using 'Übergebene Tabelle jetzt mit den IDs zurück Return dt End Function
Hoffe du kannst das auf dein Vorhaben umsetzen.
-
Hi Mike,
um konfliktfrei in Mehrnutzerumgebungen zu arbeiten, werden üblicherweise neue Datensätze im Programm erst einmal mit negativem Autowert belegt. Beim Schreiben in die Datenbank (INSERT) wird dann der endgültige Autowert vergeben. @@IDENTITY liefert den zuletzt vergebenen Autowert zurück. Wenn in der Datenbank aber Trigger aktiv sind, die auch neue Datensätze erzeugen (z.B. Protokollierung der Inserts), dann liefert @@IDENTITY den falschen Wert. Es sollte deshalb immer Scope_Identity genutzt werden. Außerdem können im Datenbankserver hinterlegte Regeln für Standardwerte oder auch Tripper den neu hinzugefügten Datensatz verändert ablegen. Deshalb sollte nach dem INSERT der gesamte Datensatz zur Scope_Identity zurückgelesen werden und den im Programm vorhandenen Datensatz überschreiben. Die DataAdapter.Update-Methode bietet da entsprechende Einstellungen und Ereignisse, wo man das machen kann. Außerdem bietet der SQL Server die Möglichkeit, an das Update gleich nach das Rücklesen des hinzugefügten Datensatzes zu hängen.--
Viele Grüsse
Peter Fleischer (MVP, Partner)
Meine Homepage mit Tipps und Tricks
Kommas richtig setzen!
Schüler sagen, Lehrer haben es gut.
Schüler, sagen Lehrer, haben es gut -
Hallo Mike,
alternativ zu Davids Lösung kann man bei SQL Server 2005 und später die OUTPUT-Klausel (Transact-SQL) nutzen, um sich hier die Identität, aber auch andere Werte liefern zu lassen. Die Klausel funktioniert auch bei UPDATE und DELETE und ist insofern universeller einsetzbar.
Für ein (C#) Beispiel siehe: SqlCommand und Output
Gruß Elmar
-
Hallo Mike,
es ist auch möglich temporär den Schreibschutz aufzuheben.
Du kannst SET IDENTITY_INSERT nutzen, um den PrimaryKey zu ändern / setzen.
SET IDENTITY_INSERT deineTabelle ON -- dein UPDATE SET IDENTITY_INSERT deineTabelle OFF
Mehr Informationen: https://msdn.microsoft.com/de-de/library/ms188059(v=sql.120).aspx
Ist natürlich nicht das schönste, aber es funktioniert ;).
VG JSX
-
Es kommt halt immer darauf an was man erreichen will. Will ich eine Tabelle auf einen anderen Server migrieren und die Schlüssel exakt so haben, dann ist das eine durchaus denkbare Lösung. Wenn ich in mehrere Tabellen schreiben möchte, welche zusammenhängen, dann habe ich ggf. Fremdschlüssel mit ON UPDATE CASCADE?
Es gibt zu viele unklare Faktoren in der Frage. Allgemein ist es ja nicht angedacht einen automatischen PK nach belieben upzudaten, das sollte ja nur ein Ausnahmefall sein oder man hat Etwas im Datenkonzept nicht richtig gemacht.
-
Hallo zusammen und vielen Dank für Eure Antworten!
@David: 'As Integer' war ein 'Copy-Paste-Fehler' von mir. Ich hatte ursprünglich mal eine Funktion daraus gemacht und die anzahl der Betroffenen Datensätze zurückgegeben.
Option Explicit/Strict habe ich auf ON gestellt...
Prinzipiell Beantwortet mir Dein Beispiel die Frage schon mal insofern dass anscheinend keine Möglichkeit gibt ein DataTable mit einem Ein-Zeiler (SqlDataAdapter.Update) upzudaten und gleichzeitig die ID´s zu aktualisieren.
Die ID vergibt das DataTable aktuell automatisch, wenn ich eine neue Zeile hinzufüge (beginnend bei 1). Jedoch stoße ich beim Versuch die ID nach dem Update zu ändern an den Schreibschutz der ID-Spalte.
Was ich auch nicht verstehe: In meinem Beispiel (aus dem Buch) wird der 'CommandBuilder' verwendet: Dim cb As New CommandBuilder. Aber die erstellte Variable 'cb' wird dann nirgends mehr verwendet. Ist das die Vorgehensweise zur Verwendung des CommandBuilders?! Im Internet habe ich einige Beispiele gefunden wo man das CommandBuilder.GetInsert/Update/DeleteCommand dem SqlDatadapter zugewiesen hat. Hat der CommandBuilder in meinem Beispiel überhaupt eine Funktion?!
Wenn ich Peters Ausführung richtig verstanden habe würde das bedeuten
1.) eine Zeile des DT in Datenbank
2.) mittels Scope_Identity die ID auslesen
3.) mittels der ID die ganze Zeile auslesen und mit der Zeile des DataTables vergleichen?!Grundsätzliches zu meinem Vorhaben: eine Anwendung Loggt diverse Vorkommnisse in einer SQL-Datenbank (SQL-Express). Dazu werden diese Vorkommnisse mit einigen Informationen und einem Zeitstempel bei Auftreten in das DataTable geschrieben und direkt auf die Datenbank übertragen. Sobald der Nutzer das Vorkommnis bestätigt hat, kommt ein weiterer Zeitstempel dazu und das Ganze wird wieder auf die Datenbank übertragen.
Aktuell habe ich in beiden Fällen obiges beispiel verwenden wollen. Das erste Eintragen in die Datenbank geht auch noch, beim Update (2.Zeitstempel) erhalte ich eine Parallelitätsverletzung (ich vermute, weil die ID meiner DataTable nicht mit der ID in der Datenbank übereinstimmt?!)
Viele Grüße
Mike -
Hallo
aufgrund der Beschreibung gehe ich davon aus, dass es sich um eine "Single-User"-Anwendung handelt. Ein zweiter Client würde so ja nichts von den neuen Log-Datensätzen des anderen Clients mitbekommen.
Grundsätzlich kann man die Verwendung eines "Autokeys" breit diskutieren. Ich mache deshalb auch nur mal einen alternativen Vorschlag. Man könnte als Key einen GUID-Wert verwenden den man lokal generiert und überlässt das also nicht dem DBS. Beim Insert wird also der Key verwendet, den der Client sendet. Dieser weiß somit schon vorher, wie der Key heißt und kann den für Updates benutzen ohne dass noch mal was geändert werden müsste.
Die Verwendung der CommandBuilder-Klasse ist hier beschrieben. Ob das Sinn macht, hängt wohl von der Situation ab. Eine Verwendung von "cb" in deinem Codeschnipsel kann ich auch nicht erkennen.
Gruß
-
Hi Mike,
die ID automatisch mit 1 beginnend zu vergeben, ist keine gute Idee. Das funktioniert zwar, aber nur solange keine Fremdschlüssel damit verbunden sind (z.B. Master-Child). Besser ist es, im Programm negative Werte zu nutzen. Da kann es keine Konflikte geben.Der CommandBuilder (Verweisvariable cb) braucht nicht genutzt zu werden, solange die Standardeinstellungen zur Generierung der SQL Anweisungen ausreichend sind. Wenn aber der "Der Letzte gewinnt" einzustellen ist, ist "cb" für die Einstellung zu nutzen. Mit der Übergabe des Verweises auf den DataAdapter im Konstruktor wird die Beziehung zwischen CommandBuilder und DataAdapter hergestellt und der DataAdapter fragt bei Bedarf den Commandbuilder, dass dieser ihm auf Basis des SelectCommands eine SQL Anweisung bereitstellt. Der Bedarf liegt dann vor, wenn dem DataAdapter kein Command-Objekt für Update, Delete, Insert zugewiesen wurde.
Für das Einfügen und Rücklesen kann die folgende SQL Anweisung genutzt werden:
INSERT INTO [dbo].[Tab1] ([info]) VALUES (@info); SELECT Id, info FROM Tab1 WHERE (Id = SCOPE_IDENTITY())
Den gesamten Ablauf kann man so demonstrieren:
Debug.Print("---") Using da As New SqlDataAdapter("SELECT ID,info FROM Tab1", My.Settings.Database1ConnectionString) Dim dt As New DataTable With dt.Columns With .Add("ID", GetType(Integer)) .AutoIncrement = True .AutoIncrementSeed = -1 .AutoIncrementStep = -1 End With End With da.Fill(dt) Dim row = dt.NewRow row(1) = "Neue Zeile" dt.Rows.Add(row) Debug.Print("Vergebene ID: {0}", row(0)) Dim cmd As New SqlCommand("INSERT INTO [dbo].[Tab1] ([info]) VALUES (@info); SELECT Id, info FROM Tab1 WHERE (Id = SCOPE_IDENTITY())", da.SelectCommand.Connection) Dim par As New SqlParameter With par .ParameterName = "@info" .SourceVersion = DataRowVersion.Current .SourceColumn = "info" End With cmd.Parameters.Add(par) da.InsertCommand = cmd Debug.Print("SQL Anweisung für Insert: {0}", da.InsertCommand.CommandText) da.Update(dt) Debug.Print("ID nach Update: {0}", row(0)) End Using
Parallelitätsverletzung kommt, wenn die Originalversion im Client (=Programm) nicht mit dem Inhalt in der Datenbank übereinstimmt. Das kann der Fall sein, wenn AcceptChanges falsch angewandt wurde oder ein Datensatz nach dem Update in der Datenbank nicht zurückgelesen wurde, damit wieder beide Inhalte übereinstimmen. Auch kann eine Parallelitätsverletzung entstehen, wenn im Mehrnutzerbetrieb positive Autowerte vergeben werden.
--
Viele Grüsse
Peter Fleischer (MVP, Partner)
Meine Homepage mit Tipps und Tricks
Kommas richtig setzen!
Schüler sagen, Lehrer haben es gut.
Schüler, sagen Lehrer, haben es gut
- Bearbeitet Peter Fleischer Samstag, 30. Januar 2016 20:37
- Als Antwort markiert Mike0680 Montag, 1. Februar 2016 18:40
-
Hallo Peter hallo K.,
vielen Dank Euch für die Ausführlichen Erklärungen. Die Angelegenheit scheint mir jetzt schon etwas verständlicher und ich werde mein Programm entsprechend etwas umgestalten.
Bis dahin - viele Grüße und nochmals herzlichen Dank!
Mike