Using / End Using und der GC

  • VB.NET
  • .NET (FX) 3.0–3.5

Es gibt 45 Antworten in diesem Thema. Der letzte Beitrag () ist von hirnwunde.

    hirnwunde schrieb:

    schon nicht aufgebaut werden kann
    Wo macht er denn weiter, wenn .Open() fehlschlägt?
    Du müsstest den Zugriff mit .IsOpen oder so absichern.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!

    hirnwunde schrieb:

    Ich ging davon aus, dass er dann die Prozedur abbricht.
    aufgrund welchen Codes?
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!

    hirnwunde schrieb:

    ErfinderDesRades schrieb:

    wo der User zB 3mal was falsches eingeben kann

    Nunja ... explizit dieser Fall ist ja eher Aufgabe des Server. mMn ...
    Der Server kann doch im Client kein Form aufpoppen lassen, wo der User dann nochmal versuchen kann.

    Neinnein, das darfst du alles selbst schön hinprogrammieren.

    Und bevor das nicht absolut wasserdicht fertig und getestet ist, darfst du das auch nicht einbauen, denn wenn du dadrin einen Fehler hast, entstehen nochmal neue Folgefehler oder gar Sicherheitslücken.
    Ein Problem ist ja auch, dass man sowas einbaut und dann nie mehr anguckt - auch nicht nach umfangreichen Änderungen am Gesamtsystem - bis evtl. am Ende mal ein Kunde dumm guckt.
    Das der Server kein Form aufbauen kann, ist klar. Ich sprach da eher vom sperren des Benutzers.

    Ich sollte vielleicht erwähnen, dass ich nicht mal im Ansatz daran denken würde, mit meinem rudimentären Wissen ein Programm für einen Kunden zu schreiben.

    Das, was ich schreibe ist entweder für mich privat oder bleibt intern in meiner Firma.
    also ich kann da noch bischen drauf rumhacken, dasses nicht soo schlecht ist, auch für den Eigenbedarf solide zu programmieren - kann ich mir aber auch sparen.

    Wie sieht denn nun deine Lösung aus? - zu 80% fürchte ich ja, dass du vom lauter drüber reden es noch immer nicht über dich gebracht hast, den TryCatch zu entfernen.

    Oder bist du schon fertig mit dem Retry, hast schon getestet und eingebaut?

    Weil wie gesagt: solange der Retry nicht wasserdicht ist, muss der TryCatch weg - dafür brauchts auch keine weiter großen Programmier-Kenntnisse.

    ErfinderDesRades schrieb:

    drauf rumhacken, dasses nicht soo schlecht ist, auch für den Eigenbedarf solide zu programmieren

    Das ist Dein gutes Recht.
    Aber zu meiner Verteidigung: ;)
    Ich habe in der Firma eigentlich andere Aufgaben, als das programmieren von Tools, die mir das Leben in der täglichen Arbeit erleichtern.
    Ich muss alle paar Tage erklären, warum das, was ich mache, so viel Zeit in Anspruch nimmt.
    Da ich im Laufe des programmierens immer wieder von Dir und Rod Sachen erklärt bekomme, wie ich Sachen besser zu machen habe,
    musste ich irgendwann einen Strich ziehen und aufhören, bei jeder neu erlernter Technik diese rückwirkend auf den ganzen Code anzuwenden.
    Parallel muss ich aber die Tools fertig stellen.
    Deswegen sieht der aktuelle Code wirklich unschön aus. Das weiss ich auch!
    Ebenso weiss ich aber, dass es neue Versionen der Tools geben wird.
    Die Sachen, die ihr mir eingetrichtert habt, sind für mich notiert und werden (auch bei meinen privaten Sachen) angewand.


    ErfinderDesRades schrieb:

    es noch immer nicht über dich gebracht hast, den TryCatch zu entfernen

    Doch doch;)
    Ich hatte vorhin auf Arbeit eher mit dem Code aus Post 12 gespielt und dem bewusst Fehler untergejubelt um zu sehen, was passiert.

    Aktuell kann ich aber erst wieder an dem rumbasteln, wenn die Kids im Bett sind ;-) Kids im Bett

    Das wird bald der Fall sein und dann widme ich ist jetzt der Fall und ich widme mich mit meinem vorhin gewonnenen Einsichten nochmal Deinem TryCatch-Tut ;)

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „hirnwunde“ ()

    RodFromGermany schrieb:

    aufgrund welchen Codes?


    Also ... ich habe mal ein bisserl gespielt ... die folgende Funktion galt mir als Spielwiese:

    VB.NET-Quellcode

    1. Public Function getBoxNr(ByVal constr As String, ByVal txlcode As String) As Integer
    2. Dim iReturn As Integer = -1
    3. Using conn As New MySqlConnection(constr), cmd As New MySqlCommand
    4. cmd.Connection = conn
    5. cmd.CommandText = "SELECT * FROM `InterneBoxen` WHERE " _
    6. + "p1 = @txlcode OR p2 = @txlcode OR " _
    7. + "p3 = @txlcode OR p4 = @txlcode OR " _
    8. + "p5 = @txlcode OR p6 = @txlcode OR " _
    9. + "p7 = @txlcode OR p8 = @txlcode OR " _
    10. + "p9 = @txlcode;"
    11. cmd.CommandType = CommandType.Text
    12. cmd.Parameters.AddWithValue("@txlcode", txlcode)
    13. conn.Open()
    14. MessageBox.Show("gerade wurde conn.Open() aufgerufen")
    15. Dim reader As MySqlDataReader = cmd.ExecuteReader()
    16. reader.Read()
    17. MessageBox.Show("gerade wurde reader.Read() aufgerufen")
    18. If reader.HasRows Then
    19. iReturn = CInt(reader.Item("BoxNrIntern"))
    20. Else
    21. iReturn = 0
    22. End If
    23. conn.Close()
    24. End Using
    25. Return iReturn
    26. End Function


    Das nicht der Debuger von VS anspringt, habe ich das Projekt erstellt und extern gestartet.

    Wenn alles glatt läuft, dann wird entweder 0 oder eben die BoxNr ausgegeben.

    Ich habe dann den ConnectionString bewusst verfälscht.
    Wenn dann der JIT-Debuger anspringt und ich auf [weiter] klicke, passiert ... nichts.
    Keine Messagebox, die mir anzeigt, dass das conn.open() oder reader.Read() aufgerufen wurde.
    Keine Messagebox, die ich eigentlich mittels MessageBox.Show(MySQLSachen.getBoxNr(tb_constr.Text, tb_txlcode.Text).ToString) anzeigen lasse.

    Also wird nicht in jedem Fall der Wert von iReturn auf 0 gesetzt und der Code vorzeitig verlassen.

    Oder hab ich wieder etwas falsch verstanden/angewand?

    hirnwunde schrieb:

    und der Code vorzeitig verlassen.
    Rufst Du das ganze von der Form_Load aus auf? Die verschluckt Exceptions :!:
    Ruf das mal auf aus einer Button_Click.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    nein nein ... das Load-Event ist bei mir schon seit geraumer Zeit Passé.
    Wenn ich etwas am Start des Programmes machen will, läuft das im Shown-Event.

    Aber in diesem Fall ist es schon ein Button.Click-Event, da ich ja die Fehler einstreuen will, was in meinem Fall mittels einer TextBox geschieht, die den ConnectionString beinhaltet.

    Edit:
    Ich habe mal das bereinigte Projekt angehangen.
    Auch wenn Du keine Datenbank hasst, kannst Du die Logik des Ganzen betrachten.
    Keine Ahnung, ob das uncool wird ... aber ich hab hier die VS 2015 RC, da ich mittlerweile mit Windows 10 hantiere.
    Dateien

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „hirnwunde“ ()

    hirnwunde schrieb:

    das bereinigte Projekt
    lässt sich nicht compilieren.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!

    ErfinderDesRades schrieb:

    Und der Catch ist schlimmer noch als überflüssig


    Alt:

    VB.NET-Quellcode

    1. Public Function get_boxinhalt_old(ByVal type As String, ByVal boxnr As Integer) As List(Of String)
    2. Dim parts As New List(Of String)
    3. Dim SQLString As String = "SELECT * FROM InterneBoxen WHERE `Typ` = @type AND `BoxNrIntern` = @boxnr;"
    4. Try
    5. Using conn As New MySqlConnection(connstring), cmd As New MySqlCommand(SQLString, conn)
    6. cmd.Parameters.AddWithValue("@type", type)
    7. cmd.Parameters.AddWithValue("@boxnr", boxnr)
    8. conn.Open()
    9. Dim reader As MySqlDataReader = cmd.ExecuteReader
    10. If reader.HasRows Then
    11. reader.Read()
    12. For fieldcnt As Integer = 2 To 10
    13. If Not reader.IsDBNull(fieldcnt) Then
    14. parts.Add(reader.GetString(fieldcnt))
    15. Else
    16. parts.Clear()
    17. Return parts
    18. End If
    19. Next
    20. End If
    21. reader.Close()
    22. End Using
    23. Catch ex As Exception
    24. Hilfssachen.ShowError(funcinfo.GetCurrentMethod.Name, ex.Message)
    25. End Try
    26. Return parts
    27. End Function


    Neu:

    VB.NET-Quellcode

    1. Public Function get_boxinhalt(ByVal type As String, ByVal boxnr As Integer) As List(Of String)
    2. Dim parts As New List(Of String)
    3. Dim SQLString As String = "SELECT * FROM InterneBoxen WHERE `Typ` = @type AND `BoxNrIntern` = @boxnr;"
    4. Using conn As New MySqlConnection(connstring), cmd As New MySqlCommand(SQLString, conn)
    5. cmd.Parameters.AddWithValue("@type", type)
    6. cmd.Parameters.AddWithValue("@boxnr", boxnr)
    7. conn.Open()
    8. If conn.State = ConnectionState.Open Then
    9. Dim reader As MySqlDataReader = cmd.ExecuteReader
    10. If reader.HasRows Then
    11. reader.Read()
    12. For fieldcnt As Integer = 2 To 10
    13. If Not reader.IsDBNull(fieldcnt) Then
    14. parts.Add(reader.GetString(fieldcnt))
    15. Else
    16. parts.Clear()
    17. Return parts
    18. End If
    19. Next
    20. End If
    21. reader.Close()
    22. End If
    23. End Using
    24. Return parts
    25. End Function


    Habe ich das besser, sogar vielleicht richtig, umgesetzt? ;)

    hirnwunde schrieb:

    VB.NET-Quellcode

    1. For fieldcnt As Integer = 2 To 10
    2. ' ...
    3. Next
    machst Du

    VB.NET-Quellcode

    1. For fieldcnt As Integer = 2 To 10
    2. If reader.IsDBNull(fieldcnt) Then
    3. parts.Clear()
    4. reader.Close()
    5. Return parts
    6. End If
    7. parts.Add(reader.GetString(fieldcnt))
    8. Next

    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    ja, gut ... vor dem Return hab ich vergessen, den reader zu schliessen. :whistling:

    hat eine pruefung auf TRUE einen Performancevorteil gegenueber FALSE?


    Nachtrag:
    Ich hab' ein reader.Dispose() draus gemacht.
    Das sollte ja nicht verkehrt sein, oder? ;)

    Nachtrag 2:
    Hui ... das war mein 100ster ;)

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „hirnwunde“ ()

    hirnwunde schrieb:

    VB.NET-Quellcode

    1. reader.Dispose()
    Erzeug den Rerader in einem Using-Block.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    ich hab hier mal 3 Varianten produziert:

    VB.NET-Quellcode

    1. Imports System.Data.Common
    2. #If False Then
    3. #ElseIf True Then
    4. 'Variante#1
    5. Public Function get_boxinhalt(ByVal type As String, ByVal boxnr As Integer) As List(Of String)
    6. Dim parts As New List(Of String)
    7. Dim SQLString As String = "SELECT * FROM InterneBoxen WHERE `Typ` = @type AND `BoxNrIntern` = @boxnr;"
    8. Using conn As New OleDbConnection(connstring), cmd As New OleDbCommand(SQLString, conn)
    9. cmd.Parameters.AddWithValue("@type", type)
    10. cmd.Parameters.AddWithValue("@boxnr", boxnr)
    11. conn.Open()
    12. Using reader As OleDbDataReader = cmd.ExecuteReader
    13. While reader.Read
    14. For fieldcnt As Integer = 2 To 10
    15. parts.Add(reader.GetString(fieldcnt))
    16. Next
    17. End While
    18. End Using
    19. End Using
    20. Return parts
    21. End Function
    22. #ElseIf True Then 'oder
    23. 'Variante#2
    24. Public Function get_boxinhalt(ByVal type As String, ByVal boxnr As Integer) As List(Of String)
    25. Dim parts As New List(Of String)
    26. Dim SQLString As String = "SELECT * FROM InterneBoxen WHERE `Typ` = @type AND `BoxNrIntern` = @boxnr;"
    27. Using conn As New OleDbConnection(connstring), cmd As New OleDbCommand(SQLString, conn)
    28. cmd.Parameters.AddWithValue("@type", type)
    29. cmd.Parameters.AddWithValue("@boxnr", boxnr)
    30. conn.Open()
    31. Using reader As OleDbDataReader = cmd.ExecuteReader
    32. For Each rc As DbDataRecord In reader
    33. For i As Integer = 2 To 10
    34. If rc.IsDBNull(i) Then Return New List(Of String)
    35. parts.Add(rc.GetString(i))
    36. Next
    37. Next
    38. End Using
    39. End Using
    40. Return parts
    41. End Function
    42. #ElseIf True Then 'oder
    43. 'Variante#3
    44. Public Function get_boxinhalt(ByVal type As String, ByVal boxnr As Integer) As List(Of String)
    45. Dim SQLString As String = "SELECT * FROM InterneBoxen WHERE `Typ` = @type AND `BoxNrIntern` = @boxnr;"
    46. Using conn As New OleDbConnection(connstring), cmd As New OleDbCommand(SQLString, conn)
    47. cmd.Parameters.AddWithValue("@type", type)
    48. cmd.Parameters.AddWithValue("@boxnr", boxnr)
    49. conn.Open()
    50. Using reader As OleDbDataReader = cmd.ExecuteReader
    51. Return reader.Cast(Of DbDataRecord).SelectMany(Function(rc) Enumerable.Range(2, 9).Select(Function(i) rc.GetString(i))).ToList
    52. End Using
    53. End Using
    54. End Function
    55. #End If
    Ich fands interessant, dass man einen DBDataReader ganz normal enumerieren kann (ForEach).
    Weiters hab ich alle unnötigen Ifse entfernt:
    1. Du muss nicht den Connection.State auf Open testen, wenn du eine Zeile drüber die Connection geöffnet hast. Falls das Öffnen nicht klappt kriegst du eh einen Fehler, und zwar bevor der Test ühaupt erreicht wird. (bitte jetzt kein "Huch! - Fehler! - Trycatch" anfangen ;) )
    2. Du musst nicht auf Reader.HasRows testen, wenn du eh den Reader enumerieren willst: Hat er keine Rows, so wird die Schleife halt nicht durchlaufen.
    3. Du musst nicht auf IsDbNull() testen, wenn die Daten eh nur gültig sind, wenn IsDbNull nicht zutrifft. Stattdessen sei froh, wenn du den Fehler kriegst, sobald ungültige Daten auftauchen - wenn sie denn auftauchen.
      Auf jeden Fall scheint es mir eine eigentümliche Reaktion, beim Auftreten von IsDbNull in nur einem Feld in nur einem Datensatz daraufhin gleich die ganze Liste zu entleeren. Soll das eine Art TryCatch-Ersatz darstellen, bei dem, wenn die Daten korrupt sind, dann eben eine leere Liste returnt? - Naja, kann man machen - s. Variante#2
      Hier zeigt sich auch das nützliche, dass der Reader im Using-Block ist: die Methode kann einfach verlassen werden - der Using-Block kümmert sich ums vorherige aufräumen.

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „ErfinderDesRades“ ()

    Uhh ... danke schonmal für Deine Mühe!

    Nun zu meinen Fragen bezüglich Deines Codes ;)
    Hat es einen Grund, dass Du anstatt der MySQL-API OleDB nutzt oder alt das nur der Veranschaulichung?
    Was hat es mit den Ifs in den Zeilen 2, 3, 21 und 40 auf sich? Vorallem, wie kann es sein, dass die drei ElseIfs in den Zeilen 3, 21 und 40 einen Unterschied darstellen?
    Das ist mal wieder eine Technik, die ich weder verstehe noch kenne.

    Und Zeile 48 ist ja mal sowas von zu weit weg von meinem Verständnis :huh:
    Auch wenn Du mir sie erklären würdest, hab ich von der Technik selbst keine Ahnung und könnte sie (zumindest jetzt noch) nicht sinnvoll einsetzen.

    Als ich Deinen Code laß und meine Gedanken dazu sortierte, warum ich das eigentlich so machen wollte, viel mir auf, dass ich eigentlich garnicht auf IsNull prüfen muss.
    Die Tabelle InterneBoxen wird von einem anderen Programm befüllt.
    In diesem sind aber schon alle möglichen Fehler ausgeschlossen, sodass es eigentlich nie zu einem inkonsistenten Datensatz kommen kann. (wenn man Murphy's Law mal unberücksichtigt lässt, was man natürlich nie tun sollte)

    Eine Zeile in der Tabelle sieht folgendermassen aus:

    Demnach wäre Variante 1 (Zeile 4-20) wohl die Funktion der Wahl.

    Was habe ich für einen Vorteil mit einem RecordSet? (Zeile 30)
    Die If-Klausel in Zeile 32 ist wahrlich elegenater als meine.
    Auch wenn sie nach meinen (oben beschriebenen) Überlegungen wohl überflüssig ist.

    Zu der dritten Variante hatte ich ja schon oben geschrieben, das ich da nicht durchblicke ;)

    Zu Deinen Bemerkungen/Erklärungen:

    1.
    Klar. Das ist dumm von mir. Selbst in einem TryCatch würde es nciht zu der überprüfung des ConnectionStates kommen ...

    2.
    Wieder klar. Es kann sogar garnicht dazu kommen, das er keine Rows hat.
    Die Listbox, in der die Boxennummern aufgelistet werden, wird von folgender Liste befüllt

    VB.NET-Quellcode

    1. Public Function get_lieferboxen(ByVal tmptype As String, Optional ByVal schongelieferte As Boolean = False) As List(Of String)
    2. Dim iReturn As New List(Of String)
    3. Dim SQLString As String = "SELECT * FROM InterneBoxen WHERE `Typ` = @type and `schongeliefert`= 0 ORDER BY 'BoxNrIntern';"
    4. If schongelieferte Then
    5. SQLString = "SELECT * FROM InterneBoxen WHERE `Typ` = @type ORDER BY 'BoxNrIntern';"
    6. End If
    7. Using conn As New MySqlConnection(connstring), cmd As New MySqlCommand(SQLString, conn)
    8. cmd.Parameters.AddWithValue("@type", tmptype)
    9. conn.Open()
    10. Dim reader As MySqlDataReader = cmd.ExecuteReader
    11. Do While reader.Read : iReturn.Add(reader.Item("BoxNrIntern").ToString) : Loop
    12. End Using
    13. Return iReturn
    14. End Function

    Wie wir sehen, gibt die Funktion nur Boxennummern zurück, die sich in der Tabelle 'InterneBoxen' befinden.
    Und da brauch ja ich keine .HasRows-Überprüfung mehr machen ... denn die Zeilen für die Box, die ich anwähle, ist definitiv vorhanden.

    Zu guter Letzt:

    ErfinderDesRades schrieb:

    wenn die Daten korrupt sind, dann eben eine leere Liste returnt?

    Genau das war mein Plan.
    In der aufrufenden Funktion wird dann der Abbruch vollzogen, wenn eien leere Liste zurück kommt. Denn mit inkonsistenten Daten kann ich ja nicht weiter arbeiten.
    Aber das IsDBNull eher Nonsense ist, hast Du ja schon festgestellt und ich rückblickend ebenso erachtet ;)

    Ansonsten nochmals dicken Dank für die Mühe!!
    der Trick mit der #if-Compiler-Direktive ermöglicht mir nur, dieselbe Methode mehrmals zu schreiben.
    Und die #ElsiIf-Kette weist den Compiler an, nur das erste True zu kompilieren - alles annere ist quasi auskommentiert.
    Nu kann ich einfach im Editor eine annere TestMethode per Drag&Drop an erste Stelle setzen, wenn ich eine Variante testen will.
    Also wenn du Fragen hast - die Varianten sind numeriert, und jede Variante macht ziemlich genau dasselbe, was dein Code gemacht hat.
    Und da der Datenlieferant der Bösewicht ist, wenn Daten korrupt sind, lösche in V#2 am besten einfach die entsprechende Abfrage.
    Ich denke, V#2 wird dir auch am einfachsten verständlich sein.
    Kannst ja auch alles annere weglöschen, oder auskommentieren oder whatever.
    Allerdings mussich sagen, ich denke, V#2 ist sowas von selbsterklärend gecodet, da sollte eiglich gar keine Frage auftreten - oder täusche ich mich?

    Ausserdem hast du ja schon Variante#1 übernommen - die ist eiglich auch völlig ok.
    Nur wäre glaub reader.GetString("BoxNrIntern") angemessener.
    Ach, und Using-Block wäre bisserl sauberer.
    Also das wird nix anrichten, dass das vergessen ist, aber wie gesagt: schadet und kostet nix, sauber zu coden - ist langfristig einfach eine gute Angewohnheit.

    ErfinderDesRades schrieb:

    V#2 wird dir auch am einfachsten verständlich sein

    Nein. Wie ich schon oben schrubte: Wozu ein DataRecord? Wo da der Vorteil liegt, habe ich noch nicht verstanden.

    ErfinderDesRades schrieb:

    Nur wäre glaub reader.GetString("BoxNrIntern") angemessener.

    Ja, allerdings. Da fällt dann die Umwandlung des Typs weg.

    ErfinderDesRades schrieb:

    Ach, und Using-Block wäre bisserl sauberer.

    Ok. Ich habe die Funktionsweise glaub falsch verstanden.
    Da ich ja alles schon in einen Using-Block packte, ging ich davon aus, das auch der das reader-Objekt nach schließen des conn-Blocks freigegeben wird.
    Dem ist wohl nicht so.

    Also wohl eher:

    VB.NET-Quellcode

    1. Public Function get_lieferboxen(ByVal tmptype As String, Optional ByVal schongelieferte As Boolean = False) As List(Of String)
    2. Dim iReturn As New List(Of String)
    3. Dim SQLString As String = "SELECT * FROM InterneBoxen WHERE `Typ` = @type and `schongeliefert`= 0 ORDER BY 'BoxNrIntern';"
    4. If schongelieferte Then
    5. SQLString = "SELECT * FROM InterneBoxen WHERE `Typ` = @type ORDER BY 'BoxNrIntern';"
    6. End If
    7. Using conn As New MySqlConnection(connstring)
    8. Using cmd As New MySqlCommand(SQLString, conn)
    9. cmd.Parameters.AddWithValue("@type", tmptype)
    10. conn.Open()
    11. Using reader As MySqlDataReader(cmd.ExecuteReader)
    12. Do While reader.Read : iReturn.Add(reader.GetString("BoxNrIntern")) : Loop
    13. End Using
    14. End Using
    15. End Using
    16. Return iReturn
    17. End Function