none
Datenverbindung in der ganzen Applikation ansprechbar machen RRS feed

  • Frage

  • Hallo

    Als Umsteiger von Access bin ich noch ganz am Anfang mit dem Einstieg in VB (MS Visual Studio 2013). Ich habe einen SQL 2008 R2 Server, von welchem ich die Daten beziehe. Das sieht dann etwa so aus:

        Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
            Try
                Dim ConnetionString As String = "Data Source=MeinServer;Initial Catalog=ITCTools;Integrated Security=True"
                Dim cnn As SqlConnection = New SqlConnection(ConnetionString)
    
    ...
    
    MeinSuperCode
    
    ...
    
                cnn.Close()
            End Try
        End Sub
    
    

    Soweit kann ich meine Felder meiner Form mal beim Start mit Daten füllen.

    Die Datenverbindung brauche ich aber ja immer wieder, einerseits um Daten zu ändern, andererseits um, in einer weiteren Form, Daten einzugeben und dann in der SQL-DB zu speichern.

    In Access würde ich da einfach eine globale Variable machen. Aber wie mach ich das hier?


    Danke und Gruss Thomas

    Mittwoch, 19. Oktober 2016 13:22

Antworten

  • Hallo Thomas,

    bitte niemals den Return Wert für Rückgaben  benutzen. Der sollte für den Fehlerstatus reserviert bleiben; ausserdem kann er nur Integer Werte liefern.

    Geändert in eine Ausgabe-Variable:

    ALTER PROCEDURE [dbo].[sp_CheckUserID1]
        @UserAbr nvarchar(40), -- etwas üppiger sollte schon sein ;)
        @UserPrint int = NULL OUTPUT -- Ausgabe
    AS
        SET NOCOUNT ON;
        
        SELECT @UserPrint = u.pUser  
        FROM dbo.tblUser u
        WHERE u.UserAbr = @UserAbr;
    	IF @@ROWCOUNT = 1
    		RETURN 0;
    	ELSE
    		RETURN 1;
    GO
    
    -- Zum Testen:
    DECLARE @UserPrint int;
    DECLARE @rc int;
    EXEC @rc = dbo.sp_CheckUserID1 'eboye', @UserPrint OUTPUT;
    SELECT @rc, @UserPrint;
    
    

    Dazu der Aufruf:

        Public Shared Sub TestGetUserID()
            For Each userName In New String() {"eboye", Nothing, "elmar"}
                Dim userID = GetUserID(userName)
                Console.WriteLine("UserName '{0}' is UserID {1}", userName, userID)
            Next
        End Sub
    
        Public Shared Function GetUserID(Optional userName As String = Nothing) As Integer
            If String.IsNullOrEmpty(userName) Then
                userName = Environment.UserName
            End If
            Using connection As New SqlConnection(My.Settings.NorthwindConnectionString)
                connection.Open()
                Using command As New SqlCommand("sp_CheckUserID1", connection) _
                    With {.CommandType = CommandType.StoredProcedure}
    
                    command.CommandType = CommandType.StoredProcedure
    
                    command.Parameters.AddWithValue("@UserAbr", userName)
    
                    ' als Ausgabeparameter
                    Dim userPrint = New SqlParameter("@UserPrint", SqlDbType.Int) _
                        With {.Direction = ParameterDirection.Output}
                    command.Parameters.Add(userPrint)
    
                    command.ExecuteNonQuery()
    
                    Dim userID As Object = userPrint.Value
                    ' NULL (DBnull) wenn nicht gefunden
                    If TypeOf userID Is DBNull Then
                        Return -1
                    Else
                        Return CInt(userID)
                    End If
                End Using
            End Using
        End Function

    Ich habe eine Funktion daraus gemacht, die kann man besser einbinden, siehe die kurze Testschleife.

    Wobei ich Dir empfehlen würde, gleiche Benamsungen zu verwenden (und auf Abkürzungen weitgehend zu verzichten).

    Die SqlConnection sollte lokal bleiben - d. h. keine sqlCon auf Klassenebene definieren. Denn die wäre bereits entsorgt, wollte sie jemand benutzen gäbe es eh nur Fehler.

    Gruß Elmar

    • Als Antwort markiert Alphawolfi Freitag, 21. Oktober 2016 14:53
    Freitag, 21. Oktober 2016 14:18
    Beantworter

Alle Antworten

  • Hi Thomas,
    im Gegensatz zu Access wird bei der Arbeit einer .NET-Anwendung mit dem SQL Server nur mit einem clientseitigen Cursor gearbeitet. Das bedeutet, dass die zu verarbeitenden (anzuzeigenden) Daten innerhalb eines Verbindungsaufbaus in einen im Client befindlichen Datenpuffer geladen werden (z.B. DataSet). Danach wird die Verbindung getrennt und die Daten werden lokal verarbeitet (geändert, gelöscht, hinzugefügt). Wenn dann gespeichert werden soll, wird wieder eine Verbindung aufgebaute und die lokalen geänderten und gelöschten Datensätze werden anhand der ID (Primärschlüssel) mit den Daten im Datenbankserver abgeglichen. Neue Datensätze werden in diesem Schritt im Datenbankserver hinzugefügt.

    Da es bei Parallelarbeit zu Konflikten kommen kann (2 Mitarbeiter haben den gleichen Datensatz geändert), muss man sich für eine Konfliktlösungsstrategie entscheiden, die dann im Programm zu implementieren ist. Die einfachste Strategie ist "Der Letzte gewinnt".

    Damit zum Zeitpunkt der nächsten Verbindungsaufnahme nicht wieder viel Zeit benötigt wird (Prüfung der Anmeldung usw.), werden die einmal im Programm genutzten Verbindungsobjekte im ConnectionPooling nach dem Trennen zwischengepuffert.

    Es gibt dann noch eine Vielzahl weiterer Besonderheiten, die von der genutzten Technologie abhängen, wie zwingende ID für das Rückschreiben, negative Autowerte, RowState, RowVersion usw.


    --
    Viele Grüsse
    Peter Fleischer (MVP Reconnect, Partner)
    Meine Homepage mit Tipps und Tricks



    Mittwoch, 19. Oktober 2016 13:57
  • Hallo Peter

    Vielen Dank für Deine Ausführungen. Das ist mir logisch und ich denke ich habe das verstanden.

    Allerdings backe ich momentan noch ganz kleine Brötchen. Mein Ziel ist ein kleines, einfaches, abteilungsinternes Chatprogramm. In einem ersten Schritt nur 1 DataGridView für die Daten und ein paar Felder (Titel und Body und Send-Knopf) für einen neuen Chateintrag.

    So, jetzt habe ich, wie oben beschrieben, bei From1_load die Datenverbindung. Jetzt möchte ich den Inhalt des Titel- und Body-Textfeldes via btnSend an eine StoredProcedure vom SQL-Server zurückgeben. Und genau hier stehe ich an, weil ich nicht weiss, wie ich diese Verbindung wieder aufbaue und Parameter an eine StoredProcedure übergebe.

        Private Sub btnSend_Click(sender As Object, e As EventArgs) Handles btnSend.Click
    
        End Sub


    Danke und Gruss Thomas

    Mittwoch, 19. Oktober 2016 14:49
  • Hallo Thomas,

    zuerst lege die Verbindungszeichenfolge in den Anwenungseinstellungen fest, so kannst Du sie über My.Settings überall im Programm ansprechen. Der Zugriff sieht quasi immer so aus:

            Using connection As New SqlConnection(My.Settings.NorthwindConnectionString)
                connection.Open()
    
                Using executeCommand = New SqlCommand("[dbo].[CustOrderHist]", connection)
                    executeCommand.CommandType = CommandType.StoredProcedure
    
                    Dim customerParameter = executeCommand.Parameters.Add("@CustomerID", SqlDbType.NChar, 5)
                    customerParameter.Value = "ALFKI"
    
                    Using reader = executeCommand.ExecuteReader
    
                        For index = 0 To reader.FieldCount - 1
                            Console.Write("{0};", reader.GetName(index))
                        Next
                        Console.WriteLine()
    
                        Dim values = New Object(reader.FieldCount - 1) {}
                        Do While reader.Read()
                            reader.GetValues(values)
                            For Each value In values
                                Console.Write("{0};", value)
                            Next
                            Console.WriteLine()
                        Loop
                    End Using
                End Using
            End Using

    Hier für eine gespeicherte Prozedur die über einen DataReader abgerufen wird. Will man etwas speichern, so würde man ExecuteNonQuery verwenden, anstatt den DataReader

    Bei Table-/DataAdapter und Co. kannst Du ebenso auf die Verbindungszeichenfolge zurückgreifen.

    Gruß Elmar


    Mittwoch, 19. Oktober 2016 17:44
    Beantworter
  • Hallo Thomas,
    der Kern ist das so ein öffnen und schließen der Connection nichts schlimmes ist. Im Gegenteil: Eine große Anzahl offener Connections ist für den SQL Server oder jede andere dahinterliegende Datenbank nicht hilfreich.

    Deine Connection kannst Du, wenn Du das wo möchtest, in eine eigene Klasse packen. Du könntest auch ein Formular als Basisklasse erstellen bei dem diese Connection "vorhanden" ist.
    Aber schließen solltest Du sie immer. Elmar hat Dir das mit dem Using ja aufgezeigt.
    Das Using sorgt für das aufräumen, z.B. dem Schließen der Connection.

    Wenn Du diese Option hast: Überlege ob Du nach c# wechselst.
    Du hast es beim suchen als Beginner deutlich leichter wenn Du nach c# Quelltext suchst.

    Die Sprachunterschiede sind nicht so gravierend und Übersetzungsseiten gibt es auch reichlich wenn es nicht alles sofort klappt in c#.

    Grüße Alexander

    Donnerstag, 20. Oktober 2016 06:25
  • Hi Alexander,
    ein Wechsel von einer bekannten Sprachsyntax zu einer kryptischen Sprachsyntax, wenn man erst dabei ist, sich in die Grundlagen des Frameworks einzuarbeiten, ist kein guter Rat. In der MSDN sind fast alle Beispiele sowohl in VB.NET als auch in C#.NET vorhanden. Bei entsprechender Formulierung der Suchanfrage findet man im Internet zu fast allen komplexeren Themen auch passende VB.NET-Beispiele. Mir ist bisher nur ein Fall bekannt, wo ich bisher keine VB.NET-Lösung gefunden habe.

    --
    Viele Grüsse
    Peter Fleischer (MVP Reconnect, Partner)
    Meine Homepage mit Tipps und Tricks

    Donnerstag, 20. Oktober 2016 07:34
  • Hallo Alexander,

    die Programmiersprache macht überhaupt keinen Unterschied. Der von mir gezeigte Code sieht - abseits der Syntax -  in C# genauso aus. Und zu Datenbanken findet man reichlich ebenso in Visual Basic.

    Richtig ist, dass Verbindungen über einen Pool gemanagt werden und man sie tunlichst nach Gebruach wie möglich freigeben sollte, was nämliches using sicherstellt.

    Gruß Elmar

    Donnerstag, 20. Oktober 2016 10:16
    Beantworter
  • Hallo Elmar,
    Inhaltlich macht das natürlich keinen Unterschied. Als Anfänger tut man sich evtl. leichter, auch wenn von VBA kommend, wenn man c# schreibt.
    Die Beispiele von MS werden meistens in beiden Sprachen verfasst. Sucht man unter Google sieht es schon ein bisschen anders aus.
    Was MS vor hat ist nicht klar. Aktuell wird bei VS 15 VB doch etwas vernachlässigt. 
    Xamarin Forms wäre ein Ableger, der aus meiner Sicht aktuell mit c# und nicht mit VB angegangen werden sollte, aber das war gerade ja nicht das Thema.

    Grüße Alexander

     
    Donnerstag, 20. Oktober 2016 10:45
  • Hallo Alexander,

    soweit man seine Brötchen mit .NET länger verdient, sollte man C# und Visual Basic gleichermaßen gut beherrschen (und ggf. auch etwas mehr)[1].

    Für Entwickler wie Thomas, die aus der Access Ecke kommen, ist Visual Basic in Verbindung mit Windows Forms (oder WPF) eine ebenso gute Wahl wie C#.

    Was Plattformen wie Xamarin angeht: Solange man daran kein Interesse hat, braucht man sich damit nicht abgeben. Wie Du schon schreibst: Vieles von dem, was Microsoft derzeit den Entwicklern andient, ist nicht gerade für jedermann und was morgen existiert ziemlich ungewiss.

    Gruß Elmar

    [1] von C++ und UWP/Runtime würde ich abraten, nachdem ich Stunden für Kleinkram verplempern musste ;)

    Donnerstag, 20. Oktober 2016 12:00
    Beantworter
  • Hallo Elmar

    Du hast mir auch schon bei Access-Problemen geholfen :o)

    Vielen Dank, dein Code funktioniert natürlich sehr gut.

    Noch 2 Fragen zum Verständnis:

    Bei mir funktioniert die Verbindung auch ohne connection.open(). Steht das nur zur sauberen Programmierung oder muss das sein?

    Wenn ich Alexander richtig verstanden habe wird bei 'End Using' die Verbindung auch wieder geschlossen. Ist das richtig?


    Danke und Gruss Thomas

    Donnerstag, 20. Oktober 2016 13:00
  • Hallo Thomas,

    Open muss beim Einsatz eines DataReader sein, sonst gibt es eine Ausnahme beim ersten Zugriff. Ein Close hingegen kann man beim Einsatz der Using Anweisung weglassen, da es implizit durch Dispose - was intern Using ausführt - erledigt wird.

    Verwendet man einen (Table- oder) DataAdapter so öffnet der selbständig die Verbindung (und schließt sie auch wieder). Wenn man mehrere DataAdapter hintereinander verwendet (oder eine Transaktion darum haben möchte), so sollte man die Verbindung vorher explizit öffnen.

    Ein Try Catch sollte man in einer realen Anwendung immer darum haben, um Fehler (wie einen nicht reagierenden Server) abzufangen.

    Gruß Elmar

    Donnerstag, 20. Oktober 2016 13:49
    Beantworter
  • Hallo Elmar und alle anderen

    Soweit habe ich alles verstanden mit der Verbindung

    Was ich noch nicht hingekrigt habe:

    Ich versuche so, einen einzelnen Wert von einer StoredProcedure zurückzugeben:

    StoredProcedure

    ALTER procedure [dbo].[sp_CheckUserID1]
    @UserAbr nvarchar(4)
    as
     declare @UserPrint int
     select @UserPrint = u.pUser
     from dbo.tblUser u
     where u.UserAbr = @UserAbr
     return @userprint

    vb

        Private Sub subGetUserID()
    
                sqlCon = New SqlConnection(strConn)
    
                Using (sqlCon)
    
                    sqlCon.Open()
                    Using cmd As New SqlClient.SqlCommand("sp_CheckUserID1", sqlCon)
                        cmd.CommandType = CommandType.StoredProcedure
                        cmd.Parameters.AddWithValue("UserAbr", Environment.UserName.ToString)
    
                        Dim myReturnValue As Integer = cmd.ExecuteScalar
                        MessageBox.Show(myReturnValue)
                    End Using
                End Using
        End Sub

    Ich erhalte als Rückgabe immer 0. Was mache ich falsch?


    Danke und Gruss Thomas


    • Bearbeitet Alphawolfi Freitag, 21. Oktober 2016 13:30
    Freitag, 21. Oktober 2016 13:28
  • Hallo Thomas,

    bitte niemals den Return Wert für Rückgaben  benutzen. Der sollte für den Fehlerstatus reserviert bleiben; ausserdem kann er nur Integer Werte liefern.

    Geändert in eine Ausgabe-Variable:

    ALTER PROCEDURE [dbo].[sp_CheckUserID1]
        @UserAbr nvarchar(40), -- etwas üppiger sollte schon sein ;)
        @UserPrint int = NULL OUTPUT -- Ausgabe
    AS
        SET NOCOUNT ON;
        
        SELECT @UserPrint = u.pUser  
        FROM dbo.tblUser u
        WHERE u.UserAbr = @UserAbr;
    	IF @@ROWCOUNT = 1
    		RETURN 0;
    	ELSE
    		RETURN 1;
    GO
    
    -- Zum Testen:
    DECLARE @UserPrint int;
    DECLARE @rc int;
    EXEC @rc = dbo.sp_CheckUserID1 'eboye', @UserPrint OUTPUT;
    SELECT @rc, @UserPrint;
    
    

    Dazu der Aufruf:

        Public Shared Sub TestGetUserID()
            For Each userName In New String() {"eboye", Nothing, "elmar"}
                Dim userID = GetUserID(userName)
                Console.WriteLine("UserName '{0}' is UserID {1}", userName, userID)
            Next
        End Sub
    
        Public Shared Function GetUserID(Optional userName As String = Nothing) As Integer
            If String.IsNullOrEmpty(userName) Then
                userName = Environment.UserName
            End If
            Using connection As New SqlConnection(My.Settings.NorthwindConnectionString)
                connection.Open()
                Using command As New SqlCommand("sp_CheckUserID1", connection) _
                    With {.CommandType = CommandType.StoredProcedure}
    
                    command.CommandType = CommandType.StoredProcedure
    
                    command.Parameters.AddWithValue("@UserAbr", userName)
    
                    ' als Ausgabeparameter
                    Dim userPrint = New SqlParameter("@UserPrint", SqlDbType.Int) _
                        With {.Direction = ParameterDirection.Output}
                    command.Parameters.Add(userPrint)
    
                    command.ExecuteNonQuery()
    
                    Dim userID As Object = userPrint.Value
                    ' NULL (DBnull) wenn nicht gefunden
                    If TypeOf userID Is DBNull Then
                        Return -1
                    Else
                        Return CInt(userID)
                    End If
                End Using
            End Using
        End Function

    Ich habe eine Funktion daraus gemacht, die kann man besser einbinden, siehe die kurze Testschleife.

    Wobei ich Dir empfehlen würde, gleiche Benamsungen zu verwenden (und auf Abkürzungen weitgehend zu verzichten).

    Die SqlConnection sollte lokal bleiben - d. h. keine sqlCon auf Klassenebene definieren. Denn die wäre bereits entsorgt, wollte sie jemand benutzen gäbe es eh nur Fehler.

    Gruß Elmar

    • Als Antwort markiert Alphawolfi Freitag, 21. Oktober 2016 14:53
    Freitag, 21. Oktober 2016 14:18
    Beantworter
  • Hallo Elmar

    Ganz herzlichen Dank. Funktioniert alles super


    Danke und Gruss Thomas

    Freitag, 21. Oktober 2016 14:51