none
Problem mit LINQtoSQL RRS feed

  • Frage

  • Hallo liebe Gemeinde,

    ich habe gerade ein kleines Problem und kann den Fehler nicht finden: ich habe eine Form mit einem DataGridView. Ich frage über LINQ Daten aus der Datenbank ab und zeige sie im Grid an. Jetzt möchte ich dem Nutzer die Möglichkeit bieten, einen Datensatz zu kopieren/auszuschneiden und wieder einzufügen. Dazu habe ich folgenden Code geschrieben:

    Public Class frmBenutzerList
      ' Deklaration der Benutzerklasse (LINQ)
      Dim b As Benutzer
    
      ''' <summary>
      ''' Legt eine Kopie des Datensatzes an (Instanz der Benutzerklasse)
      ''' </summary>
      ''' <param name="sender"></param>
      ''' <param name="e"></param>
      ''' <remarks></remarks>
      Private Sub CopyToClipboard(ByVal sender As System.Object, ByVal e As System.EventArgs)
        ' Referenzieren des Elternfensters
        Dim frmParent = TryCast(Me.MdiParent, frmMain)
        If frmParent IsNot Nothing Then
          ' Prüfen, ob von b schon eine Instanz existiert, sonst ...
          If b Is Nothing Then
            ' ... Anlegen des neuen Benutzers
            b = New Benutzer()
    
          End If
          ' Einfüllen der Daten (evtl. überschreiben)
          For Each r As DataGridViewRow In ergebnisDataGridView.SelectedRows
            'b.ID = CInt(r.Cells(0).Value)
            b.Benutzername = r.Cells(1).Value.ToString()
            b.Passwort = r.Cells(2).Value.ToString()
            b.Vorname = r.Cells(3).Value.ToString()
            b.Nachname = r.Cells(4).Value.ToString()
            b.aktiv = CBool(r.Cells(5).Value)
    
          Next
          ' Einfügen-Buttons aktivieren
          frmParent.EinfuegenToolStripButton.Enabled = True
          frmParent.EinfuegenToolStripMenuItem.Enabled = True
    
        End If
    
      End Sub
    
      ''' <summary>
      ''' Ausschneiden des ausgewählten Benutzers
      ''' </summary>
      ''' <param name="sender"></param>
      ''' <param name="e"></param>
      ''' <remarks></remarks>
      Private Sub CutToClipBoard(ByVal sender As System.Object, ByVal e As System.EventArgs)
        ' Kopierfunktion nutzen, um eine Kopie des Benutzers anzulegen
        CopyToClipboard(sender, e)
        ' Instanz des DataContexts
        Dim db As New basarDataContext(My.Settings.basarConnectionString)
        ' Variable zum Speichern der ID
        Dim ii As Integer = 0
        For Each r As DataGridViewRow In ergebnisDataGridView.SelectedRows
          ' Die ID aus dem Grid ermitteln
          ii = CInt(r.Cells(0).Value)
    
        Next
        ' Abfragen des richtigen Datensatzes
        Dim q = (From b In db.Benutzers _
             Where b.ID = ii _
             Select b).Single()
        ' Benutzer inaktiv setzen
        q.aktiv = False
        ' Änderungen an die Datenbank senden
        db.SubmitChanges()
        ' Aufräumen
        db.Dispose()
        ' Daten neu laden (Refresh)
        LadeDaten()
    
      End Sub
    
      ''' <summary>
      ''' Fügt die vorher kopierte/ausgeschnittene Benutzerinstanz in die Datenbank ein.
      ''' </summary>
      ''' <param name="sender"></param>
      ''' <param name="e"></param>
      ''' <remarks></remarks>
      Private Sub PasteToDatabase(ByVal sender As System.Object, ByVal e As System.EventArgs)
        ' Referenzieren des Elternfensters
        Dim frmParent = TryCast(Me.MdiParent, frmMain)
        If frmParent IsNot Nothing Then
          ' Instanz des DataContexts
          Dim db As New basarDataContext(My.Settings.basarConnectionString)
          ' Anhängen des Benutzers an den Context
          db.Benutzers.Attach(b)
          ' Den Benutzer in die Datenbank schreiben
          db.SubmitChanges()
          ' Aufräumen
          db.Dispose()
          ' Daten neu laden
          LadeDaten()
    
        End If
    
      End Sub
    
      ''' <summary>
      ''' Lädt die Benutzerliste in das Grid.
      ''' </summary>
      ''' <remarks></remarks>
      Friend Sub LadeDaten()
        ' Referenzieren des Elternfensters
        Dim frmParent = TryCast(Me.MdiParent, frmMain)
        If frmParent IsNot Nothing Then
          ' Instanz des DataContexts
          Dim db As New basarDataContext(My.Settings.basarConnectionString)
          ' Abfragen der Daten
          Dim q = From b In db.Benutzers _
              Where b.aktiv = True _
              Select b
          ' Die abgefragten Daten an das Grid binden
          Me.ergebnisDataGridView.DataSource = q.ToList()
          ' Aufräumen
          db.Dispose()
          q = Nothing
    
        End If
    
      End Sub
    
    End Class
    

    Die Instanz von b wird sowohl bei der Kopierfunktion, als auch bei der Ausschneidenfunktion gefüllt. Aber das Einfügen klappt nicht (PasteToDatabase()). Könnt Ihr den Fehler erkennen? Tausend Dank im Voraus für Eure Hilfe!

    Gruß
    Marcus 


    Der erste Tag, an dem ich nichts Neues lerne, wird der Tag sein, an dem sich der Deckel über mir schließt...
    Donnerstag, 4. August 2011 04:58

Antworten

  • Hallo Marcus,

    Kurzfassung: Hier wäre InsertOnSubmit anstatt Attach zu verwenden,
    siehe Vorgehensweise: Einfügen von Zeilen in die Datenbank (LINQ to SQL)

    Einige Anmerkungen / Vorschläge zu Deinem Code:
    Anstatt sich mit den Cells rumzuschlagen, verwende das DataBoundItem
    Dann hast Du eine typisierte Version und der Code wird klarer und auch nicht so anfällig füreine andere Spaltenreihenfolge uam.

    Auf solche Dinge wie das Weiternuten eines Benutzers würde ich verzichten,
    eine neue Instanz tut nicht weh, eher sie hier nach dem Einfügen weiterleben zu lassen.

    Im übrigen würde ich Dir empfehlen, den Datenzugriff auszulagern.-
    Schon bei mir hat das Nachverfolgen um einiges länger gedauert, Dir dürfte es nach einige Wochen / Monaten nicht anders gehen.
    Besser ist es man vermischt nie Oberfläche und Datenzugriff, so kann man den Datenzugriff unabhängig prüfen und testen
    und erfahrungsgemäss entfällt damit auch einiger doppelt geschriebener / ähnlicher Code (der sich ansonsten in diversen Formularen versteckt).

    Als Vorschlag:

    Public Class BenutzerList
      Dim benutzerService As New BenutzerDataService()
    
      ' Aktuelle Kopie
      Dim benutzerClipboard As Benutzer
    
      ''' <summary>Legt eine Kopie des Datensatzes an (Instanz der Benutzerklasse)</summary>
      Friend Sub CopyToClipboard(ByVal sender As System.Object, ByVal e As System.EventArgs)
        ' Referenzieren des Elternfensters
        If CanCutOrCopy() Then
          Dim benutzerAuswahl = TryCast(ergebnisDataGridView.SelectedRows(0).DataBoundItem, Benutzer)
    
          benutzerClipboard = benutzerService.BenutzerKopieren(benutzerAuswahl)
    
          ' TODO: Sollte auch deaktiviert werden
          ' Einfügen-Buttons aktivieren
          'frmParent.EinfuegenToolStripButton.Enabled = True
          'frmParent.EinfuegenToolStripMenuItem.Enabled = True
        End If
      End Sub
    
      ''' <summary>Ausschneiden des ausgewählten Benutzers</summary>
      Friend Sub CutToClipBoard(ByVal sender As System.Object, ByVal e As System.EventArgs)
        If CanCutOrCopy() Then
          Dim benutzerAuswahl = TryCast(ergebnisDataGridView.SelectedRows(0).DataBoundItem, Benutzer)
          benutzerClipboard = benutzerService.BenutzerKopieren(benutzerAuswahl)
    
          benutzerService.SetzeBenutzerInaktiv(benutzerAuswahl.ID)
          LadeDaten()
        End If
      End Sub
    
      ''' <summary>Fügt die vorher kopierte/ausgeschnittene Benutzerinstanz in die Datenbank ein.</summary>
      Friend Sub PasteToDatabase(ByVal sender As System.Object, ByVal e As System.EventArgs)
        Dim frmParent = TryCast(Me.MdiParent, frmMain)
    
        ' Hier Benutzervariable prüfen
        If frmParent IsNot Nothing AndAlso benutzerClipboard IsNot Nothing Then
          benutzerService.BenutzerEinfügen(benutzerClipboard)
    
          ' Sonst gibt sie womöglich später doppelt
          benutzerClipboard = Nothing
          LadeDaten()
        End If
      End Sub
    
      ''' <summary>Lädt die Benutzerliste in das Grid.</summary>
      Friend Sub LadeDaten()
        ' Referenzieren des Elternfensters
        Dim frmParent = TryCast(Me.MdiParent, frmMain)
        If frmParent IsNot Nothing Then
          Me.ergebnisDataGridView.DataSource = benutzerService.LadeAktiveBenutzer()
        End If
      End Sub
    
      ''' <summary>Liefert True, wenn kopiert oder ausgeschnitten werden kann</summary>
      Private Function CanCutOrCopy() As Boolean
        Dim frmParent = TryCast(Me.MdiParent, frmMain)
        ' Parent und gültige Auswahl (Kein Multi Select)
        If frmParent IsNot Nothing AndAlso ergebnisDataGridView.SelectedRows.Count = 1 Then
          Return True
        End If
        Return False
      End Function
    End Class
    
    ''' <summary>Führt die Operationen für Benutzer aus.</summary>
    Public Class BenutzerDataService
    
      ' Durch basarDataContext (und Verbindungszeichenfolge ersetzen)
      Private Function GetContext() As NorthwindDataContext
        Return New NorthwindDataContext()
      End Function
    
      ''' <summary>Lädt alle aktiven Benutzer</summary>
      Public Function LadeAktiveBenutzer() As IList(Of Benutzer)
        Using db = GetContext()
          Dim q = From b In db.Benutzers _
            Where b.Aktiv = True _
            Select b
          Return q.ToList()
        End Using
      End Function
    
      ''' <summary>Fügt einen neuen Benutzer ein.</summary>
      Public Sub BenutzerEinfügen(entity As Benutzer)
        Using db = GetContext()
          db.Benutzers.InsertOnSubmit(entity)
          db.SubmitChanges()
        End Using
      End Sub
    
      ''' <summary>Setzt den Benutzer mit der ID auf Inaktiv</summary>
      ''' <remarks>Wenn beide Richtungen mit boolschen Parameter</remarks>
      Public Sub SetzeBenutzerInaktiv(id As Integer)
        Using db = GetContext()
          Dim benutzer = (From b In db.Benutzers _
             Where b.ID = id
             Select b).SingleOrDefault()
          If benutzer IsNot Nothing Then
            benutzer.Aktiv = False
            db.SubmitChanges()
          End If
        End Using
      End Sub
    
      ''' <summary>Erstellt eine Kopie eines Benutzer (ohne Primärschlüssel)</summary>
      Public Function BenutzerKopieren(entity As Benutzer) As Benutzer
        Dim b = New Benutzer()
        b.Benutzername = entity.Benutzername & " Neu"
        b.Passwort = entity.Passwort
        b.Vorname = entity.Vorname
        b.Nachname = entity.Nachname
        b.Aktiv = entity.Aktiv
        Return b
      End Function
    End Class
    

    Nicht über den NorthwindDataContext wundern, den hatte ich der Kürze halber verwendet,
    und durch Deinen Context austauschen (ist jetzt nur noch die eine zeile).

    LadeDaten müsste nicht immer aufgerufen werden, man kann auch den Benutzer aus der Auflistung rauswerfen.
    Da es aber das Verhalten ändern würde, habe ich es so belassen.

    Gruß Elmar



    • Als Antwort markiert mjanz Donnerstag, 4. August 2011 18:57
    Donnerstag, 4. August 2011 17:09

Alle Antworten

  • Hallo Marcus,

    Kurzfassung: Hier wäre InsertOnSubmit anstatt Attach zu verwenden,
    siehe Vorgehensweise: Einfügen von Zeilen in die Datenbank (LINQ to SQL)

    Einige Anmerkungen / Vorschläge zu Deinem Code:
    Anstatt sich mit den Cells rumzuschlagen, verwende das DataBoundItem
    Dann hast Du eine typisierte Version und der Code wird klarer und auch nicht so anfällig füreine andere Spaltenreihenfolge uam.

    Auf solche Dinge wie das Weiternuten eines Benutzers würde ich verzichten,
    eine neue Instanz tut nicht weh, eher sie hier nach dem Einfügen weiterleben zu lassen.

    Im übrigen würde ich Dir empfehlen, den Datenzugriff auszulagern.-
    Schon bei mir hat das Nachverfolgen um einiges länger gedauert, Dir dürfte es nach einige Wochen / Monaten nicht anders gehen.
    Besser ist es man vermischt nie Oberfläche und Datenzugriff, so kann man den Datenzugriff unabhängig prüfen und testen
    und erfahrungsgemäss entfällt damit auch einiger doppelt geschriebener / ähnlicher Code (der sich ansonsten in diversen Formularen versteckt).

    Als Vorschlag:

    Public Class BenutzerList
      Dim benutzerService As New BenutzerDataService()
    
      ' Aktuelle Kopie
      Dim benutzerClipboard As Benutzer
    
      ''' <summary>Legt eine Kopie des Datensatzes an (Instanz der Benutzerklasse)</summary>
      Friend Sub CopyToClipboard(ByVal sender As System.Object, ByVal e As System.EventArgs)
        ' Referenzieren des Elternfensters
        If CanCutOrCopy() Then
          Dim benutzerAuswahl = TryCast(ergebnisDataGridView.SelectedRows(0).DataBoundItem, Benutzer)
    
          benutzerClipboard = benutzerService.BenutzerKopieren(benutzerAuswahl)
    
          ' TODO: Sollte auch deaktiviert werden
          ' Einfügen-Buttons aktivieren
          'frmParent.EinfuegenToolStripButton.Enabled = True
          'frmParent.EinfuegenToolStripMenuItem.Enabled = True
        End If
      End Sub
    
      ''' <summary>Ausschneiden des ausgewählten Benutzers</summary>
      Friend Sub CutToClipBoard(ByVal sender As System.Object, ByVal e As System.EventArgs)
        If CanCutOrCopy() Then
          Dim benutzerAuswahl = TryCast(ergebnisDataGridView.SelectedRows(0).DataBoundItem, Benutzer)
          benutzerClipboard = benutzerService.BenutzerKopieren(benutzerAuswahl)
    
          benutzerService.SetzeBenutzerInaktiv(benutzerAuswahl.ID)
          LadeDaten()
        End If
      End Sub
    
      ''' <summary>Fügt die vorher kopierte/ausgeschnittene Benutzerinstanz in die Datenbank ein.</summary>
      Friend Sub PasteToDatabase(ByVal sender As System.Object, ByVal e As System.EventArgs)
        Dim frmParent = TryCast(Me.MdiParent, frmMain)
    
        ' Hier Benutzervariable prüfen
        If frmParent IsNot Nothing AndAlso benutzerClipboard IsNot Nothing Then
          benutzerService.BenutzerEinfügen(benutzerClipboard)
    
          ' Sonst gibt sie womöglich später doppelt
          benutzerClipboard = Nothing
          LadeDaten()
        End If
      End Sub
    
      ''' <summary>Lädt die Benutzerliste in das Grid.</summary>
      Friend Sub LadeDaten()
        ' Referenzieren des Elternfensters
        Dim frmParent = TryCast(Me.MdiParent, frmMain)
        If frmParent IsNot Nothing Then
          Me.ergebnisDataGridView.DataSource = benutzerService.LadeAktiveBenutzer()
        End If
      End Sub
    
      ''' <summary>Liefert True, wenn kopiert oder ausgeschnitten werden kann</summary>
      Private Function CanCutOrCopy() As Boolean
        Dim frmParent = TryCast(Me.MdiParent, frmMain)
        ' Parent und gültige Auswahl (Kein Multi Select)
        If frmParent IsNot Nothing AndAlso ergebnisDataGridView.SelectedRows.Count = 1 Then
          Return True
        End If
        Return False
      End Function
    End Class
    
    ''' <summary>Führt die Operationen für Benutzer aus.</summary>
    Public Class BenutzerDataService
    
      ' Durch basarDataContext (und Verbindungszeichenfolge ersetzen)
      Private Function GetContext() As NorthwindDataContext
        Return New NorthwindDataContext()
      End Function
    
      ''' <summary>Lädt alle aktiven Benutzer</summary>
      Public Function LadeAktiveBenutzer() As IList(Of Benutzer)
        Using db = GetContext()
          Dim q = From b In db.Benutzers _
            Where b.Aktiv = True _
            Select b
          Return q.ToList()
        End Using
      End Function
    
      ''' <summary>Fügt einen neuen Benutzer ein.</summary>
      Public Sub BenutzerEinfügen(entity As Benutzer)
        Using db = GetContext()
          db.Benutzers.InsertOnSubmit(entity)
          db.SubmitChanges()
        End Using
      End Sub
    
      ''' <summary>Setzt den Benutzer mit der ID auf Inaktiv</summary>
      ''' <remarks>Wenn beide Richtungen mit boolschen Parameter</remarks>
      Public Sub SetzeBenutzerInaktiv(id As Integer)
        Using db = GetContext()
          Dim benutzer = (From b In db.Benutzers _
             Where b.ID = id
             Select b).SingleOrDefault()
          If benutzer IsNot Nothing Then
            benutzer.Aktiv = False
            db.SubmitChanges()
          End If
        End Using
      End Sub
    
      ''' <summary>Erstellt eine Kopie eines Benutzer (ohne Primärschlüssel)</summary>
      Public Function BenutzerKopieren(entity As Benutzer) As Benutzer
        Dim b = New Benutzer()
        b.Benutzername = entity.Benutzername & " Neu"
        b.Passwort = entity.Passwort
        b.Vorname = entity.Vorname
        b.Nachname = entity.Nachname
        b.Aktiv = entity.Aktiv
        Return b
      End Function
    End Class
    

    Nicht über den NorthwindDataContext wundern, den hatte ich der Kürze halber verwendet,
    und durch Deinen Context austauschen (ist jetzt nur noch die eine zeile).

    LadeDaten müsste nicht immer aufgerufen werden, man kann auch den Benutzer aus der Auflistung rauswerfen.
    Da es aber das Verhalten ändern würde, habe ich es so belassen.

    Gruß Elmar



    • Als Antwort markiert mjanz Donnerstag, 4. August 2011 18:57
    Donnerstag, 4. August 2011 17:09
  • Hallo Elmar,

    erstmal tausend Dank für Deine Antwort. Wie immer klasse erklärt! Muss ich mich eigentlich schämen, dass ich nicht auf so etwas komme? ;-) Danke nochmal!

    Gruß
    Marcus 
    Der erste Tag, an dem ich nichts Neues lerne, wird der Tag sein, an dem sich der Deckel über mir schließt...
    Donnerstag, 4. August 2011 18:57