Im programming in vb.net
I want to read data from an mssql database. I want to make it flexible for several different queries, so I put the connecting part into a separate class. Whenever I want to make a query I can get with it a preconfigured DataAdapter.
But because of this separation I dont know how to correctly dispose my SqlConnection after collecting the data.
An examplatory use:
Public Class Form1
Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
Dim DBA As New DBAccess("dummycommand")
DBA.provideAdapter.Fill(dummytable)
...Dispose? 'This is the part where you usually dispose your datacollecting ressources
End Sub
End Class
Friend Class DBAccess
Private SqlString As String
Friend Sub New(ByVal sql As String)
SqlString = sql
End Sub
Friend Function provideAdapter() As SqlDataAdapter
Dim cn As New SqlConnection("dummyconstring")
Dim da As New SqlDataAdapter(SqlString, cn)
Return da
End Function
End Class
Can you tell me how I change this concept to fit a dispose?
You could make your data access class Disposable
Friend Class DBAccess
Implements IDisposable
Private ReadOnly sqlString As String
Private disposedValue As Boolean
Private cn As SqlConnection
Private da As SqlDataAdapter
Friend Sub New(sql As String)
sqlString = sql
End Sub
Friend Function provideAdapter() As SqlDataAdapter
cn = If(cn, New SqlConnection("dummyconstring"))
da = If(da, New SqlDataAdapter(SqlString, cn))
Return da
End Function
Protected Overridable Sub Dispose(disposing As Boolean)
If Not disposedValue Then
If disposing Then
da?.Dispose()
cn?.Dispose()
End If
disposedValue = True
End If
End Sub
Public Sub Dispose() Implements IDisposable.Dispose
Dispose(disposing:=True)
GC.SuppressFinalize(Me)
End Sub
End Class
and use it like this
Using DBA As New DBAccess("dummycommand")
DBA.provideAdapter.Fill(dummytable)
End Using ' Will automatically call Dispose here
but in the long run you could look at an ORM such as Entity Framework to make your life easier.
Related
I have a ComboBox that I use on multiple WinForms. Instead of dropping a ComboBox on each WinForm and then filling the ComboBox with data from a DataTable on each individual WinForm, couldn't I create a User Control (ComboBox) that has the data populated already and just use that UC on my Winforms?
Below is how I fill the data for each individual combobox now. (I have a public class for the sql stuff)
The Variable SQL comes from a Class called SQLControl. the Class has all the sql connection stuff.
Public Sub Fillcombobox()
sql.AddParam("#ExaminerType", 3)
sql.ExecQuery("MyStoredProcedure")
ComboBoxExaminer.ValueMember = "Examiner_ID"
ComboBoxExaminer.DisplayMember = "Last_Name"
ComboBoxExaminer.DataSource = sql.DBDT
End Sub
Private Sub MyWinform_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Call Fillcombobox()
End Sub
You can put a small Class Examiner
Public Class Examiner
Public Property Examiner_ID As Integer
Public Property Last_Name As String
Public Sub New(ID As Integer, lname As String)
Examiner_ID = ID
Last_Name = lname
End Sub
End Class
Then, when the first form loads, get the data in a list declared in a module so it can be accessed from any form in the application. Of course, you may have other things in the Module.
Module Module1
Public ExaminerData As New List(Of Examiner)
End Module
Private Sub MyWinform_Load(sender As Object, e As EventArgs) Handles MyBase.Load
FillExaminerList()
ComboBoxExaminer.ValueMember = "Examiner_ID"
ComboBoxExaminer.DisplayMember = "Last_Name"
ComboBoxExaminer.DataSource = ExaminerData
End Sub
Any other form that needs the data to fill a combo box can use ExaminerData. You only call FillExaminerList once at the beginning of the application. There is only a single hit on the database.
Private OPConStr As String = "Your connection string."
Private Sub FillExaminerList()
Dim dt As New DataTable
Using cn As New SqlConnection(OPConStr),
cmd As New SqlCommand("MyStoredProcedure", cn)
cmd.Parameters.Add("#ExaminerType", SqlDbType.Int).Value = 3
Using reader = cmd.ExecuteReader
dt.Load(reader)
End Using
End Using
For Each row As DataRow In dt.Rows
Dim ex As New Examiner(CInt(row("Examiner_ID")), row("Last_Name").ToString)
ExaminerData.Add(ex)
Next
End Sub
I have the follow classes for Sqlite and SqlServer:
Class for SQLite:
Imports System.Data.SQLite
Public Class clsOperDB_SQLite
Public Shared Function getValue(sql As String) As String
Try
Using conn As New SQLiteConnection(strConn_SQLITE)
Using cmd As New SQLiteCommand()
cmd.Connection = conn
conn.Open()
cmd.CommandText = sql
Return cmd.ExecuteScalar
End Using
End Using
Catch ex As Exception
MsgBox(ex.Message)
End Try
Return ""
End Function
End Class
Class for SQLSERVER:
Imports System.Data.SqlClient
Public Class clsOperDB_SQLSERVER
Public Shared Function getValue(sql As String) As String
Try
Using conn As New SqlConnection(strConn_SQLSERVER)
Using cmd As New SqlCommand()
cmd.Connection = conn
conn.Open()
cmd.CommandText = sql
Return cmd.ExecuteScalar
End Using
End Using
Catch ex As Exception
MsgBox(ex.Message)
End Try
Return ""
End Function
End Class
this is my test form:
Public Class Form1
'form level variable
Dim dbConnector
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim connectionType As String = "SQLITE"
' need something like this or any way to set form level variable
If connectionType = "SQLITE" Then
dbConnector = clsOperDB_SQLite
Else
dbConnector = clsOperDB_SQLSERVER
End If
End Sub
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
'get value from SQLITE
Dim ValueFromDatabase As String = dbConnector.getValue("select .....")
End Sub
End Class
I need help to define dbConnector variable and set its value, also intellisense should show me class methods, using a parameter I want to change database and avoid using a conditional for every time I want to use one or the other database :
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
Dim Query As String = "Select ..."
Dim ValueFromDatabase As String = ""
' dont want to use if for each sql query
If connectionType = "SQLITE" Then
ValueFromDatabase = clsOperDB_SQLite.getValue(Query)
Else
ValueFromDatabase = clsOperDB_SQLSERVER.getValue(Query)
End If
End Sub
The rest of methods and params for both classes are the same only change class data objects (SQLiteConnection, SqlConnection, and so)
Thanks
You should define an interface that species all the common members. You can then create a class for each data source that implements that interface. In your application code, you can then declare a variable of that interface type and assign an instance of any class that implements it to that variable. You can then just use that variable and invoke any member of the interface without caring what type the actual class instance is.
The interface and the implementing classes would look something like this:
Public Interface IDataAccessProvider
Function GetValue(sql As String) As String
End Interface
Public Class SqliteDataAccessProvider
Implements IDataAccessProvider
Public Function GetValue(sql As String) As String Implements IDataAccessProvider.GetValue
'Provide SQLite-specific implementation here.
End Function
End Class
Public Class SqlServerDataAccessProvider
Implements IDataAccessProvider
Public Function GetValue(sql As String) As String Implements IDataAccessProvider.GetValue
'Provide SQL Server-specific implementation here.
End Function
End Class
Your application code might then look like this:
Private dataAccessProvider As IDataAccessProvider
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
'Read a value that identifies the data source and store it here.
'The value might be stored in the config file or wherever else is appropriate.
Dim dataSourceIdentifier As String
Select Case dataSourceIdentifier
Case "SQLite"
dataAccessProvider = New SqliteDataAccessProvider()
Case "SQL Server"
dataAccessProvider = New SqlServerDataAccessProvider()
End Select
End Sub
You can then just call dataAccessProvider.GetValue in your code without any care for what the data source actually is, except to ensure that your SQL syntax is valid for that data source.
Please note that, while what you do is up to you, I have chosen to use a sane naming convention in this code. No one would last long working for me using class names like clsOperDB_SQLSERVER. There's a reason you don't see any types with names like that in the .NET Framework.
I am getting an error. I am trying to connect to SQL SERVER with this appSettings. But this fragment of code is giving an error.
Warning 6 'Public Shared ReadOnly Property AppSettings As System.Collections.Specialized.NameValueCollection' is obsolete: 'This method is obsolete, it has been replaced by System.Configuration!System.Configuration.ConfigurationManager.AppSettings'.
`
Public Class clsDL
'Fields - Data
Private adp As SqlDataAdapter
Private cmd As SqlCommand
Private Cn As SqlConnection
Private Shared db As clsDL
Private dr As SqlDataReader
Private ds As DataSet
Private dt As DataTable
Private gUserPwdSeed As Object = 10
Private intIndex As Integer
Private m_trans As SqlTransaction
Private objConSettings As ConfigurationSettings
Private strCnString As String
Private strDatabase As String
Private strPwd As String
Private strServer As String
Private strUID As String
Private Sub Connect()
Try
Me.strServer = ConfigurationSettings.AppSettings("Server")
Me.strDatabase = ConfigurationSettings.AppSettings("Database")
Me.strUID = ConfigurationSettings.AppSettings("UID")
End Sub`
Use the suggestion in the warning.
Private Sub Connect()
Try
Me.strServer = System.Configuration.ConfigurationManager.AppSettings("Server")
Me.strDatabase = System.Configuration.ConfigurationManager.AppSettings("Database")
Me.strUID = System.Configuration.ConfigurationManager.AppSettings("UID")
End Sub`
We took that simple approach from our old codebase whilst converting up rather than rewriting and it worked fine.
What I have is an object that contains a list of objects that each contain another list of objects that have properties and such.
Currently I use pass-through methods to be able to add to those nested objects, like in this extremely simplified example:
Public Class clsA
Private objB As List(Of clsB) = New List(Of clsB)
Public Sub New()
objB.Add(New clsB)
End Sub
Public Sub AddInt(ByVal BIndex As Int32, ByVal CIndex As Int32, ByVal Number As Int32)
objB(BIndex).AddInt(CIndex, Number)
End Sub
End Class
Public Class clsB
Private objC As List(Of clsC) = New List(Of clsC)
Public Sub New()
objC.Add(New clsC)
End Sub
Public Sub AddInt(ByVal CIndex As Int32, ByVal Number As Int32)
objC(CIndex).AddInt(Number)
End Sub
End Class
Public Class clsC
Private lstNum As List(Of Int32) = New List(Of Int32)
Public Sub AddInt(ByVal Number As Int32)
lstNum.Add(Number)
End Sub
End Class
It seems like proper coding standards would tell me this is correct compared to:
Public Class clsD
Public objE As List(Of clsE) = New List(Of clsE)
Public Sub New()
objE.Add(New clsE)
End Sub
End Class
Public Class clsE
Public objF As List(Of clsF) = New List(Of clsF)
Public Sub New()
objF.Add(New clsF)
End Sub
End Class
Public Class clsF
Public lstNum As List(Of Int32) = New List(Of Int32)
End Class
Are there some instances where either method would be acceptable? Or would the pass-through setup always be preferred?
Public Class Form1
Dim oA As clsA = New clsA
Dim oD As clsD = New clsD
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
oA.AddInt(0, 0, 3)
oD.objE(0).objF(0).lstNum.Add(3)
End Sub
End Class
Think about how it's done throughout the .NET Framework. The collection should be assigned to a private field and exposed via a public read-only property.
Public Class Thing
Private _stuff As New List(Of Thing)
Public ReadOnly Property Stuff() As List(Of Thing)
Get
Return _stuff
End Get
End Property
End Class
The caller can then access the collection directly to call its Add method, etc, but cannot assign a whole new collection. There are examples everywhere: Control.Controls, ListBox.Items, ComboBox.Items, ListView.Items, DataSet.Tables, DataSet.Relations, DataTable.Rows, Datatable.Columns, etc, etc, etc.
I have a class to open a sqlconnection and return back with reference to that connection via the main application, the problem is this connection is always nothing how to modify this behaviour:
Class code:
Public Class Class1
Public ClassConnection As SqlClient.SqlConnection
Public Function OpenConnection() As Object
Try
Dim sConnString As String = ""
sConnString = "Server=ServerName;Database=DBName;User ID=Username;Password=MyPassword"
Using ClassConnection As New SqlClient.SqlConnection
ClassConnection.ConnectionString = sConnString
ClassConnection.Open()
Return ClassConnection
End Using
Catch ex As Exception
Throw ex
Return Nothing
End Try
End Function
End Class
My form Code:
Public Class Form1
Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
Dim uu As New Class1
Dim iii As SqlClient.SqlConnection = uu.OpenConnection()
MsgBox(iii.State)
End Sub
End Class
You're problem is that by wrapping it in a Using block you are disposing the ClassConnection as soon as you return it.
Using ClassConnection As New SqlClient.SqlConnection
ClassConnection.ConnectionString = sConnString
ClassConnection.Open()
Return ClassConnection
End Using ' This is equivalent to calling ClassConnection.Dispose()
should just be:
Dim ClassConnection As New SqlClient.SqlConnection
ClassConnection.ConnectionString = sConnString
ClassConnection.Open()
Return ClassConnection
And it is then the client's responsibility to dispose of the connection.
Note: Leaving the client responsible for cleaning up is generally considered poor API design. I would suggest investigating the Unit-of-Work pattern.