none
DatagridView, SQL-Tabelle über BindingSource dargestellt: berechnete Datenfelder (SQL-Tab.) werden nicht angezeigt RRS feed

  • Frage

  • Hallo zusammen,
    ich habe eine SQL-Tabelle mit drei berechneten Spalten, also Datenfelder, die mittels einer Formel durch SQL selbst berechnet werden, wenn sich ein Wert in der Formel ändert.
    Auch diese Felder werden im DGV mittels BindingSource (gefilter) dargestellt. Das funktioniert beim ersten Anzeigen des DGV. Wenn ich aber nun neue Datensätze hinzufüge (übers DGV) und sie über DatAdapter update, werden diese berechneten Werte nicht sofort angezeigt, sondern nur nach erneutem Einlesen der Tabelle und verbinden mit dem DGV.

    Wie kann man erreichen, dass die berechneten Spalten, also deren Werte, bei neuem Datensatz gleich gezeigt werden?

    Grüße-


    Dietrich

    Mittwoch, 29. November 2017 08:22

Antworten

  • Hi Dietrich,
    die Idee ist folgende. Du hängst an das RowUpdated-Ereignis des DataAdapters eine Methode, die das Zurücklesen ausführt:

    AddHandler <DataAdapter>.RowUpdated, AddressOf Rowupdated

    Die Routine wird für jeden Datensatz aufgerufen, der in die Datenbank geschrieben wird (INSERT oder UPDATE). In der Routine liest Du einfach den Datensatz zurück und überschreibst die gewünschten Feldinhalte

      Private Shared Sub Rowupdated(ByVal sender As Object, _
          ByVal e As SqlRowUpdatedEventArgs)
        If e.StatementType = StatementType.Insert Then
          Dim cmd As New SqlCommand("SELECT * FROM Tab1 WHERE ID = SCOPE_IDENTITY()", cn)
          cmd.Transaction = e.Command.Transaction
          Dim rdr = cmd.ExecuteReader
          if rdr.Read Then
            e.Row("Feldname") = rdr("Feldname")
        End If
      End Sub

    Eine andere Möglichkeit besteht im Anhängen des Rücklesens an das INSERT:

     Private Shared da1 As System.Data.SqlClient.SqlDataAdapter
      Private Shared ds As New DataSet
    
      Public Shared Sub LoadData()
        cn = New System.Data.SqlClient.SqlConnection(ConnectionString)
        Dim cmd1 As New System.Data.SqlClient.SqlCommand("", cn)
        cmd1.CommandText = "SELECT *, Cast(Convert(nchar(8), Datum, 101)" & _
          " + ' ' + Tageszeit As datetime) As Zeit FROM xTab1"
        da1 = New System.Data.SqlClient.SqlDataAdapter(cmd1)
        With da1
          .MissingSchemaAction = MissingSchemaAction.AddWithKey
          .FillLoadOption = LoadOption.Upsert
          .Fill(ds, "Tab1")
        End With
        With ds.Tables("Tab1").Columns("ID")
          .AutoIncrement = True
          .AutoIncrementStep = -1
          .AutoIncrementSeed = -1
        End With
        With bs1
          .DataSource = ds
          .DataMember = "Tab1"
        End With
      End Sub
    
      Private Shared bs1 As New BindingSource
    
      Public Shared Function GetBindindSource1() As BindingSource
        Return bs1
      End Function
    
      Public Shared Sub UpdateData()
        Dim cb1 As New System.Data.SqlClient.SqlCommandBuilder(da1)
    
        bs1.EndEdit()
    
        Dim r() As DataRow
    
        Try
          ' New Row's
    
          r = ds.Tables("Tab1").Select(Nothing, Nothing, DataViewRowState.Added)
          If r.Length > 0 Then
            With da1
              .InsertCommand = cb1.GetInsertCommand.Clone
              With .InsertCommand
                .UpdatedRowSource = UpdateRowSource.FirstReturnedRecord
                .CommandText &= ";SELECT * FROM xTab1 WHERE (ID = SCOPE_IDENTITY())"
              End With
              .Update(r)
            End With
          End If
    
          da1.Update(ds.Tables("Tab1"))
    
        Catch ex As Exception
          Trace.WriteLine(ex.ToString)
        End Try
    
      End Sub
    


    --
    Viele Grüsse
    Peter Fleischer (ehem. MVP)
    Meine Homepage mit Tipps und Tricks

    • Als Antwort markiert dherrmann Donnerstag, 30. November 2017 16:27
    Mittwoch, 29. November 2017 20:42
  • Hallo Peter, nochmals vielen Dank für die Tipps!

    Mittlerweile verwende ich die von dir gezeigte 2. Methode. Ich habe dazu folgende Funktion in Anwendung:

    	''' <summary>
    	''' 	 Updaten Dataset SQL (inkl. Re-Read-Funktion)
    	''' </summary>
    	''' <param name="dSet">das Dataset</param>
    	''' <param name="bs">die BindingSource</param>
    	''' <param name="da">der SQLDataAdapter</param>
    	''' <param name="n">der Tabellenname</param>
    	''' <param name="idName">der Name der Schlüsselspalte, wenn Re-Read gewünscht</param>
    	Public Sub dataUpdate(dSet As DataSet, bs As BindingSource, da As SqlDataAdapter, n As String,
    								 Optional idName As String = "")
    		bs.EndEdit()
    		Try
    			' neue Zeilen
    			If idName <> "" Then ' nur angegeben für Tabellen mit Re-Read-Funktion
    				Dim ccb As New SqlCommandBuilder(da)
    				Dim r() As DataRow
    				r = dSet.Tables(n).Select(Nothing, Nothing, DataViewRowState.Added)
    				If r.Length > 0 Then
    					With da
    						.InsertCommand = ccb.GetInsertCommand.Clone
    						With .InsertCommand
    							.UpdatedRowSource = UpdateRowSource.FirstReturnedRecord
    							.CommandText += ";SELECT * FROM " + n _
    							+ " WHERE (" + idName + " = SCOPE_IDENTITY())"
    						End With
    						.Update(r)
    					End With
    				End If
    			End If
    			da.Update(dSet.Tables(n))
    		Catch ex As Exception
    			bs.CancelEdit()
    			MessageBox.Show(ex.Message, "RE-READ")
    		End Try
    	End Sub

    Funktioniert!

    Viele Grüße-


    Dietrich

    • Als Antwort markiert dherrmann Donnerstag, 30. November 2017 16:27
    Donnerstag, 30. November 2017 16:27

Alle Antworten

  • Hi Dietrich,
    da der SQL Server die Feldinhalte berechnet, musst Du bei Neuanlage eines Datensatzes diesen zurücklesen. Das kannst direkt nach den INSERT machen oder auch ein AddWithKey nutzen. Am einfachsten ist es sich in das entsprechende Ereignis des DataAdapters zu hängen und nach dem Schreiben eines neuen Datensatzes diesen zurücklesen und damit den neu erfassten Datensatz überschreiben.


    --
    Viele Grüsse
    Peter Fleischer (ehem. MVP)
    Meine Homepage mit Tipps und Tricks

    Mittwoch, 29. November 2017 18:04
  • Danke, Peter, für die schnelle Antwort!

    was du vorschlägst, würde ich gern ausprobieren... Doch vielleicht kannst du mir noch ein wenig auf die Sprünge helfen.
    Ich mache bspw. vor dem Füllen des DataSets mit der Tabelle

    datadapter.MissingSchemaAction = MissingSchemaAction.AddWithKey  (SQLDataAdapter))

    Welches DataAdapter-Ereignis meinst du? Ich update  mit
    dataadapter.Update(dataset, tabellenname)
    meinst du, dass ich nach dieser Aktion das Zurücklesen machen soll? Allerdings: mit welchem VB-Befehl?

    Danke im Voraus!
    Grüße-


    Dietrich


    • Bearbeitet dherrmann Mittwoch, 29. November 2017 20:26
    Mittwoch, 29. November 2017 19:57
  • Hi Dietrich,
    die Idee ist folgende. Du hängst an das RowUpdated-Ereignis des DataAdapters eine Methode, die das Zurücklesen ausführt:

    AddHandler <DataAdapter>.RowUpdated, AddressOf Rowupdated

    Die Routine wird für jeden Datensatz aufgerufen, der in die Datenbank geschrieben wird (INSERT oder UPDATE). In der Routine liest Du einfach den Datensatz zurück und überschreibst die gewünschten Feldinhalte

      Private Shared Sub Rowupdated(ByVal sender As Object, _
          ByVal e As SqlRowUpdatedEventArgs)
        If e.StatementType = StatementType.Insert Then
          Dim cmd As New SqlCommand("SELECT * FROM Tab1 WHERE ID = SCOPE_IDENTITY()", cn)
          cmd.Transaction = e.Command.Transaction
          Dim rdr = cmd.ExecuteReader
          if rdr.Read Then
            e.Row("Feldname") = rdr("Feldname")
        End If
      End Sub

    Eine andere Möglichkeit besteht im Anhängen des Rücklesens an das INSERT:

     Private Shared da1 As System.Data.SqlClient.SqlDataAdapter
      Private Shared ds As New DataSet
    
      Public Shared Sub LoadData()
        cn = New System.Data.SqlClient.SqlConnection(ConnectionString)
        Dim cmd1 As New System.Data.SqlClient.SqlCommand("", cn)
        cmd1.CommandText = "SELECT *, Cast(Convert(nchar(8), Datum, 101)" & _
          " + ' ' + Tageszeit As datetime) As Zeit FROM xTab1"
        da1 = New System.Data.SqlClient.SqlDataAdapter(cmd1)
        With da1
          .MissingSchemaAction = MissingSchemaAction.AddWithKey
          .FillLoadOption = LoadOption.Upsert
          .Fill(ds, "Tab1")
        End With
        With ds.Tables("Tab1").Columns("ID")
          .AutoIncrement = True
          .AutoIncrementStep = -1
          .AutoIncrementSeed = -1
        End With
        With bs1
          .DataSource = ds
          .DataMember = "Tab1"
        End With
      End Sub
    
      Private Shared bs1 As New BindingSource
    
      Public Shared Function GetBindindSource1() As BindingSource
        Return bs1
      End Function
    
      Public Shared Sub UpdateData()
        Dim cb1 As New System.Data.SqlClient.SqlCommandBuilder(da1)
    
        bs1.EndEdit()
    
        Dim r() As DataRow
    
        Try
          ' New Row's
    
          r = ds.Tables("Tab1").Select(Nothing, Nothing, DataViewRowState.Added)
          If r.Length > 0 Then
            With da1
              .InsertCommand = cb1.GetInsertCommand.Clone
              With .InsertCommand
                .UpdatedRowSource = UpdateRowSource.FirstReturnedRecord
                .CommandText &= ";SELECT * FROM xTab1 WHERE (ID = SCOPE_IDENTITY())"
              End With
              .Update(r)
            End With
          End If
    
          da1.Update(ds.Tables("Tab1"))
    
        Catch ex As Exception
          Trace.WriteLine(ex.ToString)
        End Try
    
      End Sub
    


    --
    Viele Grüsse
    Peter Fleischer (ehem. MVP)
    Meine Homepage mit Tipps und Tricks

    • Als Antwort markiert dherrmann Donnerstag, 30. November 2017 16:27
    Mittwoch, 29. November 2017 20:42
  • Zunächst herzlichen Dank für die Tipps, Peter!

    Bis zum AddHandler für OnRowUpdated bin ich schon gekommen, aber dann wusste ich nicht so recht weiter.

    Vorab: cn ist die Connection?

    Feldname ist der Name des berechneten Feldes, oder? Und wenn es mehrere berechnete Felder sind?

    Deinen Code werde ich Morgen ausprobieren und melde mich danach hier...

    Beste Grüße-


    Dietrich


    • Bearbeitet dherrmann Mittwoch, 29. November 2017 21:16
    Mittwoch, 29. November 2017 21:06
  • Hi Dietrich,
    cn ist die Verweisvariable auf das Connection-Objekt,

    Feldname ist der Name der Spalte, die zurückzulesen ist. Wenn es mehrere sind, dann muss die Anweisung für jeden Feldnamen im If-Block ausgeführt werden.


    --
    Viele Grüsse
    Peter Fleischer (ehem. MVP)
    Meine Homepage mit Tipps und Tricks

    Donnerstag, 30. November 2017 06:48
  • Hallo Peter,
    beim ersten Test verwende ich folgenden Code:

    Private Sub daReis_RowUpdated(sender As Object, e As SqlRowUpdatedEventArgs) _
    		Handles daReis.RowUpdated
    		If e.StatementType = StatementType.Insert Then
    			Dim cmd As New SqlCommand("SELECT * FROM Reisekosten WHERE ReisID = SCOPE_IDENTITY()", connection)
    			cmd.Transaction = e.Command.Transaction
    			Dim rdr = cmd.ExecuteReader
    			If rdr.Read Then
    				e.Row("StundenGesamt") = rdr("StundenGesamt")
    				e.Row("Tage") = rdr("Tage")
    				e.Row("Taggeld") = rdr("Taggeld")
    			End If
    		End If
    	End Sub

    Beim Debuggen sehe ich, dass cmd.Transaction nach Ausführung von cmd.Transaction = cmd.ExecuteReader gleich Nothing ist. Dann geht natürlich nichts weiter.
    Übrigens ist ReisID der Primärschlüssel der Tabelle.
    Was kann da falsch sein?

    Grüße-


    Dietrich


    • Bearbeitet dherrmann Donnerstag, 30. November 2017 10:01
    Donnerstag, 30. November 2017 09:59
  • Es liegt zunächst am

    SCOPE_IDENTITY()

    Wenn ich die letzte Schlüsselnummer direkt einsteuere, kommt es zumindest zum Lesen des Datensatzes.

    Allerdings taucht dann ein weiteres Problem auf:
    Die Spalte "StundenGesamt" ist schreibgeschützt wird mitgeteilt! Was logisch ist, denn es ist ja eine berechnete Spalte. Dies gilt auch für die anderen beiden Spalten.Jetzt weiß ich nicht weiter...

    Grüße-


    Dietrich

    Donnerstag, 30. November 2017 11:59
  • Hallo Peter, nochmals vielen Dank für die Tipps!

    Mittlerweile verwende ich die von dir gezeigte 2. Methode. Ich habe dazu folgende Funktion in Anwendung:

    	''' <summary>
    	''' 	 Updaten Dataset SQL (inkl. Re-Read-Funktion)
    	''' </summary>
    	''' <param name="dSet">das Dataset</param>
    	''' <param name="bs">die BindingSource</param>
    	''' <param name="da">der SQLDataAdapter</param>
    	''' <param name="n">der Tabellenname</param>
    	''' <param name="idName">der Name der Schlüsselspalte, wenn Re-Read gewünscht</param>
    	Public Sub dataUpdate(dSet As DataSet, bs As BindingSource, da As SqlDataAdapter, n As String,
    								 Optional idName As String = "")
    		bs.EndEdit()
    		Try
    			' neue Zeilen
    			If idName <> "" Then ' nur angegeben für Tabellen mit Re-Read-Funktion
    				Dim ccb As New SqlCommandBuilder(da)
    				Dim r() As DataRow
    				r = dSet.Tables(n).Select(Nothing, Nothing, DataViewRowState.Added)
    				If r.Length > 0 Then
    					With da
    						.InsertCommand = ccb.GetInsertCommand.Clone
    						With .InsertCommand
    							.UpdatedRowSource = UpdateRowSource.FirstReturnedRecord
    							.CommandText += ";SELECT * FROM " + n _
    							+ " WHERE (" + idName + " = SCOPE_IDENTITY())"
    						End With
    						.Update(r)
    					End With
    				End If
    			End If
    			da.Update(dSet.Tables(n))
    		Catch ex As Exception
    			bs.CancelEdit()
    			MessageBox.Show(ex.Message, "RE-READ")
    		End Try
    	End Sub

    Funktioniert!

    Viele Grüße-


    Dietrich

    • Als Antwort markiert dherrmann Donnerstag, 30. November 2017 16:27
    Donnerstag, 30. November 2017 16:27