Benutzer mit den meisten Antworten
Datenverbindung in der ganzen Applikation ansprechbar machen

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
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
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
- Bearbeitet Peter Fleischer Mittwoch, 19. Oktober 2016 13:59
-
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
-
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
- Bearbeitet Elmar BoyeEditor Mittwoch, 19. Oktober 2016 17:46
-
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
-
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 -
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
-
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
-
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 ;)
-
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
-
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
-
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
vbPrivate 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
-
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