Discussion:
SqlDataAdapter nach INSERT neue Zeile vom SQL Server zurücklesen
(zu alt für eine Antwort)
Lutz Elßner
2006-02-01 22:22:30 UTC
Permalink
VB.NET 1.1
Mit SqlDataAdapter und SqlCommandBuilder füge ich u.a. neue Datensätze auf
dem SQL Server hinzu.
Nach Update leere ich die DataTable und rufe noch einmal Fill(DataTable) auf
um die gerade angelegten Datensätze wieder einzulesen. Dann stehen die Werte
in der DataTable, die der SQL Server automatisch erzeugt hat (PrimaryKey und
Default Werte).

Ist dieses Vorgehen richtig oder ist die Aktualisierung der DataTable
irgendwie automatisch möglich?

Muss auch alles neu geladen werden, wenn nur der neue PrimaryKey gebraucht
wird? Das SelectCommand ändern (Spaltennamen löschen aber die Parameter
lassen) kann wohl nicht die Lösung sein...

Lutz
Elmar Boye
2006-02-02 09:27:00 UTC
Permalink
Hallo Lutz,

Lutz Elßner <www.vb-hosting.de> schrieb ...
Post by Lutz Elßner
VB.NET 1.1
Mit SqlDataAdapter und SqlCommandBuilder füge ich u.a. neue
Datensätze auf dem SQL Server hinzu.
Nach Update leere ich die DataTable und rufe noch einmal
Fill(DataTable) auf um die gerade angelegten Datensätze wieder
einzulesen. Dann stehen die Werte in der DataTable, die der SQL
Server automatisch erzeugt hat (PrimaryKey und Default Werte).
Ist dieses Vorgehen richtig oder ist die Aktualisierung der DataTable
irgendwie automatisch möglich?
Ein komplettes Neubefüllen ist nicht in allen Fällen notwendig.
Die DataAdapter sind in der Lage bestehende Datenzeilen anhand
des Primary Keys zu erkennen und zu aktualisieren.

Was allerdings nur direkt funktioniert, wenn man keine Identity
Werte verwendet. Dort müsste beim einem INSERT ein
SELECT id -- Spaltenname wie Identity PK
-- und ggf. weitere Spalten wie Timestamp uam.
FROM dbo.Tabelle
WHERE id = SCOPE_IDENTITY()
angehängt werden, da der SqlCommandBuilder das nicht kann.
Will man dessen automatische generierten Befehle nutzen,
müsste man das im RowUpdated Event erledigen, wie u. a.
gezeigt in:
http://groups.google.de/group/microsoft.public.de.german.entwickler.dotnet.datenbank/msg/3a73a5127ea7639e?hl=en&

Wird nur ein UPDATE vorgenommen, der den PK nicht ändert,
muss das nicht gemacht werden.

Zwischenzeitlich gelöschte Zeilen können naturgemäss vom
DataAdapter nicht entfernt werden.
Post by Lutz Elßner
Muss auch alles neu geladen werden, wenn nur der neue PrimaryKey
gebraucht wird?
Im Interesse einer späteren Änderungsfreundlichkeit sollte
man alle Spalten, die durch einen DEFAULT, einen Trigger usw.
vom Server geändert werden könnten (heute oder in Zukunft)
abrufen. Ob da eine Spalte mehr oder weniger abgerufen wird,
macht den Kohl nicht fett.
Post by Lutz Elßner
Das SelectCommand ändern (Spaltennamen löschen aber
die Parameter lassen) kann wohl nicht die Lösung sein...
Nein das eher nicht. Wenn schon ein neues SqlCommand wie
oben gezeigt.

Gruss
Elmar
Lutz Elßner
2006-02-02 16:55:50 UTC
Permalink
Post by Elmar Boye
Ein komplettes Neubefüllen ist nicht in allen Fällen notwendig.
Die DataAdapter sind in der Lage bestehende Datenzeilen anhand
des Primary Keys zu erkennen und zu aktualisieren.
Na gut dass ich gefragt habe..
Das Geheimnis ist FillSchema vor Fill aufzurufen. Dann werden einige Column
Eigenschaften mit vom SQL Server geholt. Vielleicht sogar die Information
welche Columns den PrimaryKey bilden *)
SchemaType.Source oder Mapped scheint keine Bedeutung zu haben wenn die
DataTable vorher leer ist. Der TableName der SQL Tabelle wird leider nicht
der DataTable zugewiesen.

Das Problem war, nach NewRow und Update die neue ID (nach INSERT in die SQL
Tabelle) zurück zu bekommen. Das funktioniert nun meistens automatisch. Nach
Update steht sie in der DataRow.
Aber
Es funktioniert allerdings nicht wenn das SelectCommand vom SqlDataAdapter
keine DataRows findet und die NewRow (die den WHERE Bedingungen entspricht)
erst erzeugt wird. Dann steht nach Update 0 im ID Column. Anschließendes
Fill (mit gleichem Select Command) holt dann die richtige (neue) DataRow mit
der neuen ID.

Mache ich noch was falsch? Der DataTable.PrimaryKey entspricht dem der SQL
Tabelle (int identity 1,1) und wird auch ohne Rows (bei FillSchema) richtig
zugewiesen. Wenn es "Zufall" ist ob ID nach Update zurückgeholt wird, dann
nutzt es ja nichts. Und gerade beim hinzufügen einer Row wird es
gebraucht...


*) Als PrimaryKey Columns wird nicht unbedingt die "Identität" bzw
"Primärschlüssel" Spalte der Sql Tabelle gewählt. Bei einer anderen Tabelle
wird ein Unique Index aus 2 Spalten zum PrimaryKey gemacht obwohl ein
normaler Primärschlüssel (int identity 1,1) existiert.
Peter Fleischer
2006-02-02 17:18:32 UTC
Permalink
Post by Lutz Elßner
Post by Elmar Boye
Ein komplettes Neubefüllen ist nicht in allen Fällen notwendig.
Die DataAdapter sind in der Lage bestehende Datenzeilen anhand
des Primary Keys zu erkennen und zu aktualisieren.
Na gut dass ich gefragt habe..
Das Geheimnis ist FillSchema vor Fill aufzurufen.
Lutz,
das ist nicht notwendig. Wenn keine Tabelle im DataSet vorhanden ist bzw das
DataTable-Objekt leer ist, dann holt sich der DataAdapter die Spalten.
Post by Lutz Elßner
Dann werden einige
Column Eigenschaften mit vom SQL Server geholt. Vielleicht sogar die
Information welche Columns den PrimaryKey bilden *)
Schau die mal an:

myDataAdapter.MissingSchemaAction = MissingSchemaAction.AddWithKey
Post by Lutz Elßner
SchemaType.Source oder Mapped scheint keine Bedeutung zu haben wenn
die DataTable vorher leer ist. Der TableName der SQL Tabelle wird
leider nicht der DataTable zugewiesen.
Das geht auch nicht, da die SELECT-Anweisung das alles im Normalfall
verfälscht.
Post by Lutz Elßner
Das Problem war, nach NewRow und Update die neue ID (nach INSERT in
die SQL Tabelle) zurück zu bekommen. Das funktioniert nun meistens
automatisch. Nach Update steht sie in der DataRow.
Wenn die entsprechenden Bedingungen eingestellt sind, funktioniert das
automatisch. Dazu ist:

1. An das INSERT ist ein SELECT ID WHERE ID = Scope_Identity anzufügen

2. Das InsertCommand-Objekt muss das Rücklesen des vergebenen Autowertes
unterstützen mit
myUpdfateCommand.UpdatedRowSource = UpdateRowSource.FirstReturnedRecord
Post by Lutz Elßner
Aber
Es funktioniert allerdings nicht wenn das SelectCommand vom
SqlDataAdapter keine DataRows findet und die NewRow (die den WHERE
Bedingungen entspricht) erst erzeugt wird.
Das stimmt nicht. Probier es mal!
Post by Lutz Elßner
Dann steht nach Update 0
im ID Column.
Wie sieht denn der CommandText im UpdateCommand aus?
Post by Lutz Elßner
Anschließendes Fill (mit gleichem Select Command) holt
dann die richtige (neue) DataRow mit der neuen ID.
Dieser Umweg bleibt immer noch offen:-)
Post by Lutz Elßner
Mache ich noch was falsch? Der DataTable.PrimaryKey entspricht dem
der SQL Tabelle (int identity 1,1) und wird auch ohne Rows (bei
FillSchema) richtig zugewiesen. Wenn es "Zufall" ist ob ID nach
Update zurückgeholt wird, dann nutzt es ja nichts. Und gerade beim
hinzufügen einer Row wird es gebraucht...
Schau dir mal das genutzte UpdateCommand-Objekt an. Da muss das SELECT als
Anweisung nach dem UPDATE enthalten sein und auch UpdatedRowSource
entsprechend eingestellt sein.
Post by Lutz Elßner
*) Als PrimaryKey Columns wird nicht unbedingt die "Identität" bzw
"Primärschlüssel" Spalte der Sql Tabelle gewählt. Bei einer anderen
Tabelle wird ein Unique Index aus 2 Spalten zum PrimaryKey gemacht
obwohl ein normaler Primärschlüssel (int identity 1,1) existiert.
Da wird wohl eine Identity-Spalte da sein, die aber nicht Schlüsselspalte
ist. Prüfe mal die Constraints in der Datenbank.

Peter
Lutz Elßner
2006-02-02 21:46:43 UTC
Permalink
Testfunktion ganz unten
SQL Tabelle:
[ID] [int] IDENTITY (1, 1) NOT NULL ,
[A] [varchar] (50) NULL
=================================
Ausgabe wenn bei Fill kein Row geladen wird:

Fill 0
PrimaryKey.Length 1

INSERT INTO tTest( A ) VALUES ( @p1 )
FirstReturnedRecord

UPDATE tTest SET A = @p1 WHERE ( (ID = @p2) AND ((@p3 = 1 AND A IS NULL) OR
(A = @p4)) )
FirstReturnedRecord

Update 1
ID 0 <=== PrimaryKey ist 0
Rows.Count 1
32 <=== das ist der PrimaryKey im SQL Server

=================================
Ausgabe wenn bei Fill 1 Row (oder mehrere) geladen wird:

Fill 1
PrimaryKey.Length 1

INSERT INTO tTest( A ) VALUES ( @p1 )
FirstReturnedRecord

UPDATE tTest SET A = @p1 WHERE ( (ID = @p2) AND ((@p3 = 1 AND A IS NULL) OR
(A = @p4)) )
FirstReturnedRecord

Update 1
ID 33 <=== PrimaryKey ist richtig
Rows.Count 2
32 <=== hier unwichtig (falsche row)


=================================

Wo soll in folgendem Code SELECT ID WHERE ID = Scope_Identity eingefügt
werden?
=================================

Function test() As String
Dim value As String = "a9"
Dim log As New StringWriter
Dim dt As DataTable, dr As DataRow, i As Integer
Dim command As New SqlCommand("SELECT * FROM tTest WHERE A = @A",
Me.mSqlConnection)
command.Parameters.Add("@A", value)

With New SqlCommandBuilder(New SqlDataAdapter(command))
.DataAdapter.MissingSchemaAction =
MissingSchemaAction.AddWithKey

dt = New DataTable
'.DataAdapter.FillSchema(dt, SchemaType.Source)
i = .DataAdapter.Fill(dt)
log.WriteLine("Fill " & i)
log.WriteLine("PrimaryKey.Length " & dt.PrimaryKey.Length)

dr = dt.NewRow()
dr.Item("A") = value
dt.Rows.Add(dr)

log.WriteLine()
With .GetInsertCommand
.UpdatedRowSource = UpdateRowSource.FirstReturnedRecord
log.WriteLine(.CommandText)
log.WriteLine(.UpdatedRowSource.ToString)
log.WriteLine()
End With
With .GetUpdateCommand
.UpdatedRowSource = UpdateRowSource.FirstReturnedRecord
log.WriteLine(.CommandText)
log.WriteLine(.UpdatedRowSource.ToString)
log.WriteLine()
End With

i = .DataAdapter.Update(dt)
log.WriteLine("Update " & i)

' PrimaryKey nach Update lesen
log.Write("ID ")
log.WriteLine(dr.Item("ID"))

' PrimaryKey nach Fill lesen
dt.Clear() : dt.AcceptChanges()
.DataAdapter.Fill(dt)
log.WriteLine("Rows.Count " & dt.Rows.Count)
log.WriteLine(dt.Rows(0).Item("ID"))
End With
Return log.ToString
End Function
Peter Fleischer
2006-02-03 05:31:01 UTC
Permalink
Lutz Elßner wrote:
...
Post by Lutz Elßner
Wo soll in folgendem Code SELECT ID WHERE ID = Scope_Identity
eingefügt werden?
Lutz,
verzichte mal auf den CommandBuilder.

Peter
Lutz Elßner
2006-02-02 21:33:05 UTC
Permalink
VB.NET 1.1
Post by Peter Fleischer
Post by Lutz Elßner
Na gut dass ich gefragt habe..
Das Geheimnis ist FillSchema vor Fill aufzurufen.
Lutz,
das ist nicht notwendig. Wenn keine Tabelle im DataSet vorhanden ist bzw das
DataTable-Objekt leer ist, dann holt sich der DataAdapter die Spalten.
myDataAdapter.MissingSchemaAction = MissingSchemaAction.AddWithKey
Ja, AddWithKey bewirkt das selbe wie FillSchema.
Post by Peter Fleischer
Post by Lutz Elßner
Das Problem war, nach NewRow und Update die neue ID (nach INSERT in
die SQL Tabelle) zurück zu bekommen. Das funktioniert nun meistens
automatisch. Nach Update steht sie in der DataRow.
Wenn die entsprechenden Bedingungen eingestellt sind, funktioniert das
1. An das INSERT ist ein SELECT ID WHERE ID = Scope_Identity anzufügen
2. Das InsertCommand-Objekt muss das Rücklesen des vergebenen Autowertes
unterstützen mit
myUpdfateCommand.UpdatedRowSource = UpdateRowSource.FirstReturnedRecord
1. verstehe ich nicht. Wie kann an INSERT ein SELECT angefügt werden? Der
SqlCommandBuilder macht doch die SqlCommands automatisch. (Es geht um VB.NET
1.1)

2. Wenn ich FirstReturnedRecord einstelle ändert sich nichts, wahrscheinlich
weil 1. fehlt.
Post by Peter Fleischer
Post by Lutz Elßner
Aber
Es funktioniert allerdings nicht wenn das SelectCommand vom
SqlDataAdapter keine DataRows findet und die NewRow (die den WHERE
Bedingungen entspricht) erst erzeugt wird.
Das stimmt nicht. Probier es mal!
Post by Lutz Elßner
Dann steht nach Update 0
im ID Column.
Der PrimaryKey ist immer noch 0 wenn SELECT 0 Rows geliefert hat, wenn
mindestens 1 Row da war, dann stimmt nach hinzufügen der 2. 3. 4. der neue
PrimaryKey mit dem im SQL Server überein...
Post by Peter Fleischer
Wie sieht denn der CommandText im UpdateCommand aus?
Siehe 2. Posting, aber ich denke Update brauchen wir gar nicht. Es ist doch
nur Insert nötig?
Post by Peter Fleischer
Post by Lutz Elßner
Anschließendes Fill (mit gleichem Select Command) holt
dann die richtige (neue) DataRow mit der neuen ID.
Dieser Umweg bleibt immer noch offen:-)
Ist im Beispiel demonstriert. Damit gehts.
Post by Peter Fleischer
Schau dir mal das genutzte UpdateCommand-Objekt an. Da muss das SELECT als
Anweisung nach dem UPDATE enthalten sein und auch UpdatedRowSource
entsprechend eingestellt sein.
Im Update Command ein SELECT ?!?!
Peter Fleischer
2006-02-03 05:28:30 UTC
Permalink
Lutz Elßner wrote:
...
Post by Lutz Elßner
1. verstehe ich nicht. Wie kann an INSERT ein SELECT angefügt werden?
Der SqlCommandBuilder macht doch die SqlCommands automatisch. (Es
geht um VB.NET
1.1)
Lutz,
Wenn du das vom SqlCommandBuilder erzeugte Command-Objekt nutzt, dann geht
das nicht. Du musst das selbst codieren:

With myAdapter
.InsertCommand = New System.Data.SqlClient.SqlCommand
With .InsertCommand
.Connection = myConnection
.CommandText = "INSERT INTO Tab(F1, F2) VALUES (@p1, @p2);"
.CommandText &= "SELECT ID FROM Tab WHERE ID = SCOPE_IDENTITY()"
.CommandType = System.Data.CommandType.Text
.Parameters.Add(New System.Data.SqlClient.SqlParameter("@p1",
System.Data.SqlDbType.Image, 0, System.Data.ParameterDirection.Input, 0, 0,
"F1", System.Data.DataRowVersion.Current, false, Nothing, "", "", ""))
.Parameters.Add(New System.Data.SqlClient.SqlParameter("@p2",
System.Data.SqlDbType.NVarChar, 0, System.Data.ParameterDirection.Input, 0,
0, "F2", System.Data.DataRowVersion.Current, false, Nothing, "", "", ""))
End With
End With


...
Post by Lutz Elßner
Post by Peter Fleischer
Schau dir mal das genutzte UpdateCommand-Objekt an. Da muss das
SELECT als Anweisung nach dem UPDATE enthalten sein und auch
UpdatedRowSource entsprechend eingestellt sein.
Im Update Command ein SELECT ?!?!
s. oben.

Peter
Lutz Elßner
2006-02-03 21:00:05 UTC
Permalink
Post by Peter Fleischer
Lutz,
Wenn du das vom SqlCommandBuilder erzeugte Command-Objekt nutzt, dann geht
With myAdapter
.InsertCommand = New System.Data.SqlClient.SqlCommand
With .InsertCommand
.Connection = myConnection
.CommandText &= "SELECT ID FROM Tab WHERE ID = SCOPE_IDENTITY()"
.CommandType = System.Data.CommandType.Text
System.Data.SqlDbType.Image, 0, System.Data.ParameterDirection.Input, 0, 0,
"F1", System.Data.DataRowVersion.Current, false, Nothing, "", "", ""))
System.Data.SqlDbType.NVarChar, 0, System.Data.ParameterDirection.Input, 0,
0, "F2", System.Data.DataRowVersion.Current, false, Nothing, "", "", ""))
End With
End With
Eine akzeptable Lösung habe ich immer noch nicht. Wenn ich das Insert
Command nach obigem Beispiel selber baue, funktioniert es offensichtlich.
Die Änderungen
1. an das INSERT Command &= ;SELECT.. hinten anhängen
2. UpdateRowSource.Both oder FirstReturnedRecord setzen
sind durchaus auch an dem automatischen .GetInsertCommand Object möglich und
lassen sich vom DataAdapter auch zurück lesen. Aber damit gehts nicht!

Aber auch ohne diese Maßnahmen wird doch der richtige PrimaryKey in die
Datarow zurück geschrieben
!__wenn DataAdapter.Fill mindestens 1 Row geliefert hat__!
Also muss er wohl standardmäßig an Insert auch ein Select anhängen um an den
Key zu kommen.

Das Rücklesen geht nur dann nicht, wenn bereits das erste Select (bei Fill)
keine Rows geliefert hat.

Unsere bisherige Diskussion habe ich so verstanden, dass das Rücklesen ohne
verändertes Insert Command nie funktionieren würde. Es geht aber ohne alle
Manipulationen - wenn es nicht die erste Row ist.
So kann es sein dass das Problem ganz wo anders liegt...

Trotzdem vielen Dank für die Denkanstöße.
Lutz
Peter Fleischer
2006-02-03 23:44:18 UTC
Permalink
Lutz Elßner wrote:
...
Post by Lutz Elßner
Eine akzeptable Lösung habe ich immer noch nicht. Wenn ich das Insert
Command nach obigem Beispiel selber baue, funktioniert es
offensichtlich.
Lutz,
bei mir funktioniert es so auch problemlos.
Post by Lutz Elßner
Die Änderungen
1. an das INSERT Command &= ;SELECT.. hinten anhängen
2. UpdateRowSource.Both oder FirstReturnedRecord setzen
sind durchaus auch an dem automatischen .GetInsertCommand Object
möglich und lassen sich vom DataAdapter auch zurück lesen. Aber damit
gehts nicht!
Weil das eben ohne tiefe Kopie des vom CommandBuilder erzeugten
Command-Objektes nicht geht. Ich vermute mal, dass du keine tiefe Kopie
erzeugt hast.
Post by Lutz Elßner
Aber auch ohne diese Maßnahmen wird doch der richtige PrimaryKey in
die Datarow zurück geschrieben
!__wenn DataAdapter.Fill mindestens 1 Row geliefert hat__!
Es ist nicht erforderlich, dass Fill eine Row liefert.
Post by Lutz Elßner
Also muss er wohl standardmäßig an Insert auch ein Select anhängen um
an den Key zu kommen.
Es gibt auch noch die Möglichkeit über OnRowUpdated.
Post by Lutz Elßner
Das Rücklesen geht nur dann nicht, wenn bereits das erste Select (bei
Fill) keine Rows geliefert hat.
Unsinn!
Post by Lutz Elßner
Unsere bisherige Diskussion habe ich so verstanden, dass das
Rücklesen ohne verändertes Insert Command nie funktionieren würde.
Du kannst es auch vom Designer generieren lassen.
Post by Lutz Elßner
Es
geht aber ohne alle Manipulationen - wenn es nicht die erste Row ist.
So kann es sein dass das Problem ganz wo anders liegt...
Das denke ich auch:-)

Mein Beispiel habe ich mal auf meiner Homepage bereitgestellt unter:

http://www.informtools.de/it_tt.htm -> Suchbegriff: 201

Peter
Lutz Elßner
2006-02-04 11:00:55 UTC
Permalink
Hallo Peter
Post by Peter Fleischer
bei mir funktioniert es so auch problemlos.
Ich glaube dass dein Beispiel funktioniert. Aber es verwendet keinen
CommandBuilder und die SELECT Anweisung hat keinen Parameter.
Post by Peter Fleischer
Weil das eben ohne tiefe Kopie des vom CommandBuilder erzeugten
Command-Objektes nicht geht. Ich vermute mal, dass du keine tiefe Kopie
erzeugt hast.
Ohne irgendwas an irgend einem Object zu machen liest mein Beispiel mit
CommandBuilder den Key immer dann zurück wenn vorher Fill mindestens einen
Datensatz gefunden hatte!!!
Post by Peter Fleischer
Post by Lutz Elßner
Aber auch ohne diese Maßnahmen wird doch der richtige PrimaryKey in
die Datarow zurück geschrieben
!__wenn DataAdapter.Fill mindestens 1 Row geliefert hat__!
Es ist nicht erforderlich, dass Fill eine Row liefert.
Ja theoretisch, aber irgend was ist anders...
Post by Peter Fleischer
Post by Lutz Elßner
Also muss er wohl standardmäßig an Insert auch ein Select anhängen um
an den Key zu kommen.
Das Rücklesen geht nur dann nicht, wenn bereits das erste Select (bei
Fill) keine Rows geliefert hat.
Unsinn!
Hast du mal meinen Code vom 02.02. laufen lassen?
Post by Peter Fleischer
Post by Lutz Elßner
Unsere bisherige Diskussion habe ich so verstanden, dass das
Rücklesen ohne verändertes Insert Command nie funktionieren würde.
Du kannst es auch vom Designer generieren lassen.
Ich habe nicht vor für jede Tabelle eine eigene Klasse zu machen. Der
CommandBuilder macht seine Arbeit zu 99,9 % ordentlich, er kann den
vergebenen key zurück lesen, nur bei Insert der 1. Row, die einer Where
Bedingung entspricht, irrt er sich mal..
Post by Peter Fleischer
Post by Lutz Elßner
Es
geht aber ohne alle Manipulationen - wenn es nicht die erste Row ist.
So kann es sein dass das Problem ganz wo anders liegt...
Das denke ich auch:-)
http://www.informtools.de/it_tt.htm -> Suchbegriff: 201
Ergänze mal die SELECT Anweisung um einen Parameter, der den hinzuzufügenden
Value testet.
Lass die Insert Anweisung vom CommandBuilder (ohne angehängtes SELECT)
generieren.
Dann wird er den key auch zurück lesen ab der 2. Row mit dem gleichen Wert
in col1.

Lutz
Lutz Elßner
2006-02-04 16:06:35 UTC
Permalink
Jetzt ist das Problem geklärt:

Weil ich DataAdapter.MissingSchemaAction.AddWithKey eingestellt hatte, hat
die neue leere DataTable die Eigenschaft AutoIncrement für den PrimaryKey
bekommen. Wenn bei Fill keine Rows gefüllt werden, beginnt die erste
hinzugefügte Row bei 0 zu zählen. Wenn schon Rows gelesen wurden, wird der
nächst höhere Wert in der lokalen DataTable erzeugt. Dieser stimmt nur
zufällig mit dem nächsten Identity Wert vom SQL Server überein - wenn dort
zwischendurch nichts passiert. Die beiden Keys zählen also unabhängig
voneinander.
DataAdapter.InsertCommand liest einen neuen Identity Wert standardmäßig
nicht zurück!

Also muss das InsertCommand manipuliert werden. Das jedoch selbst schreiben
kommt nicht in Frage, wozu gibt es den CommandBuilder. Dessen
GetInsertCommand kann zwar mit den erforderlichen Änderungen versehen
werden, aber das hat keine Wirkung:

With DataAdapter
.InsertCommand = CommandBuilder.GetInsertCommand
.InsertCommand.CommandText &= ";SELECT ID FROM Tab1 WHERE ID =
SCOPE_IDENTITY()"
.InsertCommand.UpdatedRowSource = UpdateRowSource.Both
End With

Das eigene InsertCommand immer zu erzeugen obwohl es selten gebraucht wird,
soll vermieden werden. Der CommandBuilder macht das auch nur auf
Anforderung. Er lässt sich allerdings nicht überschreiben..

Wir erfahren ob ein Insert demnächst zu erwarten ist in folgendem Event:

Dim cb As New SqlClient.SqlCommandBuilder(dadp)
Dim WithEvents dt As DataTable("Tab1") ' muss dem Name vom SQL Server
entsprechen

Private Sub dt_RowChanged(ByVal sender As Object, ByVal e As
System.Data.DataRowChangeEventArgs) Handles dt.RowChanged

If e.Action = DataRowAction.Add AndAlso dadp.InsertCommand Is
Nothing Then
'
With cb.DataAdapter
Dim cbInsert As SqlClient.SqlCommand = cb.GetInsertCommand

.InsertCommand = New SqlClient.SqlCommand
.InsertCommand.Connection = cbInsert.Connection
.InsertCommand.CommandText = cbInsert.CommandText & ";SELECT
IDENTITYCOL FROM " & e.Row.Table.TableName & " WHERE IDENTITYCOL =
SCOPE_IDENTITY()"
For Each cbPar As SqlClient.SqlParameter In
cbInsert.Parameters
With .InsertCommand.Parameters.Add(New
SqlClient.SqlParameter)
.ParameterName = cbPar.ParameterName
.SourceColumn = cbPar.SourceColumn
' evtl. weitere Properties abschreiben
End With
Next
End With
'MsgBox(dadp.InsertCommand.CommandText)
End If
End Sub

Es wird also nur beim Hinzufügen einer neuen DataRow ein InsertCommand für
den DataAdapter erzeugt. Und wenn dieser einmal eins hat, lässt er vom
CommandBuilder keins mehr generieren.

Als Vorlage wird das vom CommandBuilder automatisch erzeugte InsertCommand
benutzt. Der CommandText ist schon richtig und wird nur um die SELECT
Anweisung verlängert. Diese ist allgemein und passt immer. Vorausgesetzt der
TableName entspricht dem auf dem SQL Server.

Die Parameters werden auch nachgebaut. Hier müssen wahrscheinlich noch mehr
Properties (Datentypen) abgeschrieben werden. Das Beispiel funktioniert mit
varchar.

Das Beispiel darf gerne getestet und irgendwo veröffentlicht werden..

Lutz Elßner
Peter Fleischer
2006-02-04 17:41:36 UTC
Permalink
Post by Lutz Elßner
Weil ich DataAdapter.MissingSchemaAction.AddWithKey eingestellt
hatte, hat die neue leere DataTable die Eigenschaft AutoIncrement für
den PrimaryKey bekommen. Wenn bei Fill keine Rows gefüllt werden,
beginnt die erste hinzugefügte Row bei 0 zu zählen. Wenn schon Rows
gelesen wurden, wird der nächst höhere Wert in der lokalen DataTable
erzeugt. Dieser stimmt nur zufällig mit dem nächsten Identity Wert
vom SQL Server überein - wenn dort zwischendurch nichts passiert. Die
beiden Keys zählen also unabhängig voneinander.
DataAdapter.InsertCommand liest einen neuen Identity Wert
standardmäßig nicht zurück!
Lutz,
deshalb sollte auch eijn Command-Objekt mit angefügten SELECT genutzt
werden.
Post by Lutz Elßner
Also muss das InsertCommand manipuliert werden. Das jedoch selbst
schreiben kommt nicht in Frage, wozu gibt es den CommandBuilder.
Dessen GetInsertCommand kann zwar mit den erforderlichen Änderungen
An der falschen Stelle hat das keine Wirkung. Im OnRowUpdating funktioniert
das problemlos.
Post by Lutz Elßner
With DataAdapter
.InsertCommand = CommandBuilder.GetInsertCommand
.InsertCommand.CommandText &= ";SELECT ID FROM Tab1 WHERE ID =
SCOPE_IDENTITY()"
.InsertCommand.UpdatedRowSource = UpdateRowSource.Both
End With
Das eigene InsertCommand immer zu erzeugen obwohl es selten gebraucht
wird, soll vermieden werden. Der CommandBuilder macht das auch nur auf
Anforderung. Er lässt sich allerdings nicht überschreiben..
Na klar lässt der sich nutzen. Du musst nur eine tiefe Kopie erstellen.
Post by Lutz Elßner
Dim cb As New SqlClient.SqlCommandBuilder(dadp)
Dim WithEvents dt As DataTable("Tab1") ' muss dem Name vom SQL
Server entsprechen
Private Sub dt_RowChanged(ByVal sender As Object, ByVal e As
System.Data.DataRowChangeEventArgs) Handles dt.RowChanged
If e.Action = DataRowAction.Add AndAlso dadp.InsertCommand Is
Nothing Then
'
With cb.DataAdapter
Dim cbInsert As SqlClient.SqlCommand =
cb.GetInsertCommand
.InsertCommand = New SqlClient.SqlCommand
.InsertCommand.Connection = cbInsert.Connection
.InsertCommand.CommandText = cbInsert.CommandText &
";SELECT IDENTITYCOL FROM " & e.Row.Table.TableName & " WHERE
IDENTITYCOL = SCOPE_IDENTITY()"
For Each cbPar As SqlClient.SqlParameter In
cbInsert.Parameters
With .InsertCommand.Parameters.Add(New
SqlClient.SqlParameter)
.ParameterName = cbPar.ParameterName
.SourceColumn = cbPar.SourceColumn
' evtl. weitere Properties abschreiben
End With
Next
End With
'MsgBox(dadp.InsertCommand.CommandText)
End If
End Sub
Es wird also nur beim Hinzufügen einer neuen DataRow ein
InsertCommand für den DataAdapter erzeugt. Und wenn dieser einmal
eins hat, lässt er vom CommandBuilder keins mehr generieren.
Der CommandBuilder weist ein eigenes Command-Objekt nur zu, wenn es noch
keine Zuweisung gibt.
Post by Lutz Elßner
Als Vorlage wird das vom CommandBuilder automatisch erzeugte
InsertCommand benutzt. Der CommandText ist schon richtig und wird nur
um die SELECT Anweisung verlängert. Diese ist allgemein und passt
immer. Vorausgesetzt der TableName entspricht dem auf dem SQL Server.
Die Parameters werden auch nachgebaut. Hier müssen wahrscheinlich
noch mehr Properties (Datentypen) abgeschrieben werden. Das Beispiel
funktioniert mit varchar.
Erzeuge doch besser eine tiefe Kopie.
Post by Lutz Elßner
Das Beispiel darf gerne getestet und irgendwo veröffentlicht werden..
Das Grundprinzip steht auf meiner Homepage in dem bereits geposteten Link.

Peter
Lutz Elßner
2006-02-04 21:42:08 UTC
Permalink
Post by Peter Fleischer
Lutz,
deshalb sollte auch eijn Command-Objekt mit angefügten SELECT genutzt
werden.
An der falschen Stelle hat das keine Wirkung. Im OnRowUpdating
funktioniert
Post by Peter Fleischer
das problemlos.
Wenn ich in DataAdapter.RowUpdating e.Command mit einem New SqlCommand
belege, wird anschließend bei Update bemängelt, dass der Parameter fehlen
würde. Das ist unklar.
Wenn ich dadp.InsertCommand mit einem New SqlCommand belege, wird das in
diesem Event noch nicht benutzt und es bleibt beim ersten Aufruf
wirkungslos. Das ist denkbar.
Getestet mit deinem Beispiel.


Private Sub dadp_RowUpdating(ByVal sender As Object, ByVal e As
System.Data.SqlClient.SqlRowUpdatingEventArgs) Handles dadp.RowUpdating
Try
MsgBox((dadp.InsertCommand Is Nothing) & " " & (e.Command Is
Nothing) & " " & e.StatementType.ToString & " " & e.Status.ToString)
If e.StatementType = StatementType.Insert AndAlso e.Command Is
Nothing Then
e.Command = New System.Data.SqlClient.SqlCommand
With e.Command
.Connection = cn
.CommandText = "INSERT INTO Tab1(col1) VALUES (@q1);"
.CommandText &= "SELECT ID FROM Tab1 WHERE ID =
SCOPE_IDENTITY()"
.CommandType = System.Data.CommandType.Text

.Parameters.Add(New SqlParameter("@q1", _
SqlDbType.NVarChar, 0, ParameterDirection.Input, _
True, 0, 0, "col1", DataRowVersion.Current, Nothing))

'.Parameters.Add(New
System.Data.SqlClient.SqlParameter("@p1", _
' System.Data.SqlDbType.NVarChar, 0,
System.Data.ParameterDirection.Input, 0, _
' 0, "col1", System.Data.DataRowVersion.Current, False,
Nothing, "", "", ""))
End With

dadp.InsertCommand = e.Command
End If

Catch ex As Exception
MsgBox(ex.ToString)
End Try
End Sub
Post by Peter Fleischer
Na klar lässt der sich nutzen. Du musst nur eine tiefe Kopie erstellen.
In dem Beispiel oben wird kein CommandBuilder genutzt.
Aber wie macht man eine tiefe Kopie?
Post by Peter Fleischer
Erzeuge doch besser eine tiefe Kopie.
Lutz
Peter Fleischer
2006-02-05 08:24:43 UTC
Permalink
Lutz Elßner wrote:
...
Post by Lutz Elßner
Wenn ich in DataAdapter.RowUpdating e.Command mit einem New SqlCommand
belege, wird anschließend bei Update bemängelt, dass der Parameter
fehlen würde. Das ist unklar.
Lutz,
hast du denn wirklich alle Parameter-Objekte erzeugt und an das
Command-Objekt gehängt?
Post by Lutz Elßner
Wenn ich dadp.InsertCommand mit einem New SqlCommand belege, wird das
in diesem Event noch nicht benutzt und es bleibt beim ersten Aufruf
wirkungslos. Das ist denkbar.
Nein! Da wird bei dir ein anderer Fehler sein.
Post by Lutz Elßner
Getestet mit deinem Beispiel.
Private Sub dadp_RowUpdating(ByVal sender As Object, ByVal e As
System.Data.SqlClient.SqlRowUpdatingEventArgs) Handles
dadp.RowUpdating Try
MsgBox((dadp.InsertCommand Is Nothing) & " " & (e.Command
Is Nothing) & " " & e.StatementType.ToString & " " &
e.Status.ToString) If e.StatementType =
StatementType.Insert AndAlso e.Command Is Nothing Then
e.Command = New System.Data.SqlClient.SqlCommand
With e.Command
.Connection = cn
.CommandText = "INSERT INTO Tab1(col1) VALUES
ID = SCOPE_IDENTITY()"
.CommandType = System.Data.CommandType.Text
SqlDbType.NVarChar, 0, ParameterDirection.Input, _
True, 0, 0, "col1", DataRowVersion.Current,
Nothing))
Hier fehlt aber noch die Zuweisung des Parameter-Wertes:

.Parameters.Item("@q1").Value = e.Row.Item("col1")

...
Post by Lutz Elßner
In dem Beispiel oben wird kein CommandBuilder genutzt.
Aber wie macht man eine tiefe Kopie?
Schau dir mal die Grundlagen zu "deep copy" an. Eine tiefe Kopie bedeutet,
dass nicht nur das eigentliche Objekt kopiert wird, sondern auch alle
Objekte, auf die im zu kopierenden Objekt verwiesen wird. Und das wird über
die gesamte Verweishierarchie durchgeführt.

Im konkreten Fall bedeutet das, dass das vom CommandBuilder bereitgestellte
Command-Objekt und alle dazugehörigen Parameter-Objekte kopiert werden und
die Zeiger entsprechend neu gesetzt werden.

Peter

Peter Fleischer
2006-02-04 17:13:53 UTC
Permalink
Lutz Elßner wrote:
...
Post by Lutz Elßner
Post by Peter Fleischer
bei mir funktioniert es so auch problemlos.
Ich glaube dass dein Beispiel funktioniert. Aber es verwendet keinen
CommandBuilder und die SELECT Anweisung hat keinen Parameter.
Lutz,
wie bereits geschrieben habe, kannst du für das Rücklesen den CommandBuilder
rnicht nutzen.

...
Post by Lutz Elßner
Post by Peter Fleischer
Weil das eben ohne tiefe Kopie des vom CommandBuilder erzeugten
Command-Objektes nicht geht. Ich vermute mal, dass du keine tiefe
Kopie erzeugt hast.
Ohne irgendwas an irgend einem Object zu machen liest mein Beispiel
mit CommandBuilder den Key immer dann zurück wenn vorher Fill
mindestens einen Datensatz gefunden hatte!!!
Das kann ich nicht bestätigen.
Post by Lutz Elßner
Post by Peter Fleischer
Post by Lutz Elßner
Aber auch ohne diese Maßnahmen wird doch der richtige PrimaryKey in
die Datarow zurück geschrieben
!__wenn DataAdapter.Fill mindestens 1 Row geliefert hat__!
Es ist nicht erforderlich, dass Fill eine Row liefert.
Ja theoretisch, aber irgend was ist anders...
Post by Peter Fleischer
Post by Lutz Elßner
Also muss er wohl standardmäßig an Insert auch ein Select anhängen
um an den Key zu kommen.
Das Rücklesen geht nur dann nicht, wenn bereits das erste Select
(bei Fill) keine Rows geliefert hat.
Unsinn!
Hast du mal meinen Code vom 02.02. laufen lassen?
Der Code bringt eine Warnung:

Warning 1 'Public Function Add(parameterName As String, value As Object) As
System.Data.SqlClient.SqlParameter' is obsolete: 'Add(String parameterName,
Object value) has been deprecated. Use AddWithValue(String parameterName,
Object value). http://go.microsoft.com/fwlink/?linkid=14202'
C:\Me\VBNET20\WindowsApplication1\WindowsApplication1\Form1.vb 210 5
WindowsApplication1

Außerdem solltest du besser mit Option Strict On arbeiten.
Post by Lutz Elßner
Post by Peter Fleischer
Post by Lutz Elßner
Unsere bisherige Diskussion habe ich so verstanden, dass das
Rücklesen ohne verändertes Insert Command nie funktionieren würde.
Du kannst es auch vom Designer generieren lassen.
Ich habe nicht vor für jede Tabelle eine eigene Klasse zu machen. Der
CommandBuilder macht seine Arbeit zu 99,9 % ordentlich, er kann den
vergebenen key zurück lesen, nur bei Insert der 1. Row, die einer
Where Bedingung entspricht, irrt er sich mal..
Das wäre mir neu, dass sich der CommandBuilder im Rahmen seiner Fähigkeiten
irrt. Wenn du ihn falsch anwendest, kann natürlich das von dir erwartetet
Ergebneis vom realen Ergebnis abweichen.
Post by Lutz Elßner
Post by Peter Fleischer
Post by Lutz Elßner
Es
geht aber ohne alle Manipulationen - wenn es nicht die erste Row
ist. So kann es sein dass das Problem ganz wo anders liegt...
Das denke ich auch:-)
http://www.informtools.de/it_tt.htm -> Suchbegriff: 201
Ergänze mal die SELECT Anweisung um einen Parameter, der den
hinzuzufügenden Value testet.
Lass die Insert Anweisung vom CommandBuilder (ohne angehängtes SELECT)
generieren.
Dann wird er den key auch zurück lesen ab der 2. Row mit dem gleichen
Wert in col1.
Nein, das kann ich nicht reproduzieren. Es wird im Client ein Autowert
genereriert, der in einer Ein-Nutzer-Anwendung zufällig mit dem später
generierten Autowert übereinstimmen kann. Starte das Programm zwei Mal und
arbeite wechselseitig und du wirst erkennen, dass sich der im Client
generierte Autowert von dem später in der Datenbank eingetragenen Autowert
abweicht.

Peter
Loading...