Does the database connection have to be set inside a TransactionScope?
Or can I set it in the ctor and then have instance methods create up a TransactionScope?
EDIT: e.g.
Public Sub New()
Dim conn = new SqlConnection(...connection string)
Public Sub SomeClassMethod()
using ts as new TransactionScope
//conn has already been initialized
//so, here you can set commands, ExecuteDataSet, etc.
vs
Public Sub New()
//nothing here
Public Sub SomeClassMethod()
using ts as new TransactionScope
conn = new SqlConnection(...connection string)
set commands, ExecuteDataSet, etc.
the question is do you need to create the connection to the database after you've created a TransactionScope or can it be done before?
If you want you SqlConnection to be under transaction, than you need to create it under TransactionScope.
using(TransactionScope scope = new TransactionScope())
{
SqlConnection x = new SqlConnestion("....");
x.Open();
....your code... SQlCommands etc....
x.Close();
scope.Complete();
}
Related
I have a problem in a vb.net Windows Form project. My company is having network issues and is losing connection between the Windows Form application and the SQL server. When this happens, the application locks up and closes. Before any SQL commands are executed, is there a way to check if the SQL connection is even available?
I've tried using this:
If (cmd.Connection.State <> ConnectionState.Closed)
after the cmd is setup like this:
Dim cmd As SqlCommand
cmd = MSSQL_CONN.CreateCommand
but the state is often Open because the network connection fails after the SQL command was initialized.
I've tried to catch the error like this:
Private m_conn As SqlConnection
Try
m_conn = New SqlConnection(cn_str)
Call m_conn.Open()
Catch e As Exception
MessageBox.Show("Error")
If MSSQL_CONN.TransactionStarted Then
MSSQL_CONN.RollbackTransaction()
End If
End Try
but this has problems because it's trying to do a rollback for any other errors and the rollback throws an error because of the loss of connection state. This also starts a cascade into other timers and background processes that continue to run and try to connect to the SQL server.
I'm hoping there something like
If *SQL connection is even still available* Then
Call m_conn.Open()
Else
*Don't execute any other SQL on this form or any other background forms*
End If
Normally it's best to use a using statement for a connection where the connection is scoped to the method you are working with the database. Otherwise you could create the connection as needed e.g. check the connection state as on error.
Public Class DataOperations
Public Shared Async Function Connection() As Task(Of SqlConnection)
Dim cn As New SqlConnection With {.ConnectionString = "TODO"}
Try
Await cn.OpenAsync()
Return cn
Catch ex As Exception
Return New SqlConnection
End Try
End Function
End Class
Then there is locally scoped where the choice is OpenAsync or Open.
Public Shared Function GetCatagories() As DataTable
Dim dt As New DataTable
Using cn As New SqlConnection With {.ConnectionString = "TODO"}
Using cmd As New SqlCommand With {.Connection = cn}
cmd.CommandText = "TODO"
Try
cn.Open()
dt.Load(cmd.ExecuteReader())
Catch ex As Exception
' decide on how to handle
End Try
End Using
End Using
Return dt
End Function
EDIT:
Public Shared Async Function IsServerConnected() As Task(Of Boolean)
Using cn As New SqlConnection With {.ConnectionString = ConnectionString}
Try
Await cn.OpenAsync()
Return True
Catch ex As Exception
Return False
End Try
End Using
End Function
I have the following connection string class
Imports System.Data.SqlClient
Public Class DBConnection
Sub GetConnection()
Dim CrmsConn As New SqlConnection(System.Configuration.ConfigurationManager.ConnectionStrings("crmsConnectionString").ConnectionString)
End Sub
End Class
And I have tried to call the connection string from the above class as follows:
Dim dbcon As DBConnection
Dim con As New SqlConnection(dbcon.GetConnection())
But it has an error near dbcon.GetConnection().
What is the solution?
GetConnection is a sub. This means that it doesn't return anything and you cannot try to use an inexistant return value. (This is a compile time error, you can't produce any executable code until you fix it)
Make it a function and public
Public Function GetConnection() as SqlConnection
return New SqlConnection(System.Configuration.ConfigurationManager.ConnectionStrings("crmsConnectionString").ConnectionString)
End Function
and then use it as (but create the class instance first otherwise a null reference exception occurs at runtime)
' Create the instance of the DBConnection class and ...
Dim dbcon As DBConnection = new DBConnection()
' ... get the connection
Dim con as SqlConnection = dbcon.GetConnection()
Finally remember to use the Using statement around disposable objects like this
Using con = dbCon.GetConnection()
....
End Using
I hate to admit this but I am new to object oriented programming in VB.NET. I have a class object called Subscriber.vb which works OK but I'd like to create a "set" or list of these objects. Could someone please help me leverage the following code to create a list of the subscribers so a "consumer" could loop through this list of subscribers? Here is what I have so far:
Public Class Subscriber
Public Sub New(ByVal theSubscriberID As Int32)
Dim sConnDatabase As String = ConfigurationManager.ConnectionStrings("DatabaseConnString").ConnectionString
Dim connection As New SqlConnection(sConnDatabase)
Dim cmd As SqlCommand
Try
cmd = New SqlCommand("GetSubscriberInfo_v", connection)
cmd.CommandType = CommandType.StoredProcedure
cmd.Parameters.AddWithValue("#SubscriberID", theSubscriberID)
connection.Open()
Dim objReader As SqlDataReader = cmd.ExecuteReader()
Do While objReader.Read()
SetObjectData(objReader)
Loop
objReader.Close()
connection.Close()
Catch ex As Exception
Throw
End Try
End Sub
Private Sub SetObjectData(ByVal theObjReader As SqlDataReader)
Try
Me._ID = Convert.ToInt32(theObjReader("SubscriberID"))
Me._NameForLogon = theObjReader("SubscriberName").ToString()
Me._NameInFull = theObjReader("SubscriberNameFull").ToString()
Me._DaysUntilExpired = Convert.ToInt32(theObjReader("DaysUntilExpired"))
Me._SignupDate = theObjReader("SignupDate")
Me._ExpirationDate = theObjReader("ExpirationDate")
Me._SubscriberPhone = theObjReader("SubscriberPhone").ToString()
Me._MostRecentRenewal = theObjReader("MostRecentRenewal")
Me._CumulativeRevenue = Convert.ToDecimal(theObjReader("CumulativeRevenue"))
Me._NumberOfRenewals = theObjReader("NumberOfRenewals")
Me._SubscriptionStatusCode = theObjReader("SubscriptionStatusCode")
Me._SubscriptionStatus = theObjReader("SubscriptionStatus").ToString()
Me._NotificationStatusCode = theObjReader("NotificationStatusCode")
Me._NotificationStatus = theObjReader("NotificationStatus")
Catch ex As Exception
Throw
End Try
End Sub
End Class
I did not show the getters and setters. This has to be restricted to Visual Studio 2008 unfortunately. For a few reasons, we cannot upgrade this environment.
What would be the best practice here? Add a Public Class SubscriberList to the Subscriber.vb file or should it be a separate file? More importantly, I am stuck on how to take what I have an create a proper list. Then the caller would create an instance of the SubscriberList object. Please help me get started. Thanks.
EDIT: Here is what I came up with thanks to your idea (I'm thinking of adding some overloaded constructors which might filter the data some various ways...would that be a good practice?):
Public Class SubscriberList
Public Sub New()
Dim sConnDatabase As String = ConfigurationManager.ConnectionStrings("DatabaseConnString").ConnectionString
Dim connection As New SqlConnection(sConnDatabase)
Dim cmd As SqlCommand
Dim oSubscriberList As New List(Of Subscriber)
cmd = New SqlCommand("GetSubscriberInfo_v", connection)
cmd.CommandType = CommandType.StoredProcedure
connection.Open()
Dim objReader As SqlDataReader = cmd.ExecuteReader()
Do While objReader.Read()
Dim id As Integer = objReader("SubscriberID")
Dim s As Subscriber = New Subscriber(id)
oSubscriberList.Add(s)
Loop
objReader.Close()
connection.Close()
End Sub
End Class
New error trying to use:
Dim allSubscribers As New SubscriberList
For Each Subscriber In allSubscribers
' allSubscribers is not declared
Next
Why not declared ? Confused rookie mistake I am sure...
EDIT (Number 2):
Changed name from SubscriberList to Subscribers plural & got this working (see below) - but I am very puzzled by the advice to remove the database connection and query from the constructor(s) and place in separate class(es). I was picturing adding overloaded constructors to Subscriber (and Subscribers). I cannot imagine how the constructors of each would get their respective data.
Public Class Subscribers
Implements IEnumerable(Of Subscriber)
#Region "properties"
Public List As New List(Of Subscriber)
#End Region
Public Function GetEnumerator() As IEnumerator(Of Subscriber) _
Implements IEnumerable(Of Subscriber).GetEnumerator
Return List.GetEnumerator()
End Function
Private Function GetEnumerator1() As IEnumerator _
Implements IEnumerable.GetEnumerator
Return List.GetEnumerator()
End Function
Public Sub New()
Dim sConnDatabase As String = ConfigurationManager.ConnectionStrings("DatabaseConnString").ConnectionString
Dim connection As New SqlConnection(sConnDatabase)
Dim cmd As SqlCommand
cmd = New SqlCommand("SELECT * FROM dbo.Subscriber_v", connection)
cmd.CommandType = CommandType.Text
connection.Open()
Dim objReader As SqlDataReader = cmd.ExecuteReader()
Do While objReader.Read()
Dim id As Integer = objReader("SubscriberID")
Dim s As Subscriber = New Subscriber(id)
List.Add(s)
Loop
objReader.Close()
connection.Close()
End Sub
End Class
In VB you can make a list of a custom object.
dim oSubscriberList as new List(of Subscriber)
Then you can instantiate new subscribers and add them to the list
oSubscriberList.add('add object here')
This is probably the most simple, quick and dirty way to handle it. You can also create a separate class to create a collection of your object. "Best" practices, if you want to follow SOLID programming principles and use test driven development, would point you towards making a separate collection class to deal with it, but it isnt necessary.
EDIT: as per comment below
You dont need to create a subscriberlist class. Just create a regular list of Subscribers and add them to the list as so. Do this where you are wanting to create this list (form load, some event, etc.)
Dim oSubscriberList as NEW List(of Subscriber)
Dim sConnDatabase As String = ConfigurationManager.ConnectionStrings("DatabaseConnString").ConnectionString
Dim connection As New SqlConnection(sConnDatabase)
Dim cmd As SqlCommand
cmd = New SqlCommand("SELECT * FROM dbo.Subscriber_v", connection)
cmd.CommandType = CommandType.Text
connection.Open()
Dim objReader As SqlDataReader = cmd.ExecuteReader()
while objReader.Read()
oSubscriberList.Add(New Subscriber(objReader("SubscriberID"))
end while
'additional cleanup steps here
Then you can just iterate over you list as so:
For each sub as Subscriber in oSubscriberList
'do something
Next
Will the following statement cause a memory leak:
Imports System.Data.SQLClient
Public Function getConnection () As SQLConnection
return New SQLConnection()
End Function
Public Sub TestConnection()
Dim con As SQLConnection
con = getConnection
con.close
con = Nothing
End Sub
How does .close or .dispose get called on the SQLConnection in getConnection?
You're returning a reference type, hence you operate on the same instance in TestConnection so no memory leak here.
At the end you have 2 instances with null (gc will collect them), but the connection is closed.
There will be no memory leak because it will be garbage collected after you've called the method.
But this method does nothing but causing confusion. You should always dispose connections(which closes it implicitely) as soon as you're finished with it (even in case of an exception).
You can do that in a finally of a Try/Finally or (easier) with the Using statement. But since both approaches need to wrap the connection, your method enables the calling method to forget it. Therefore it is bad practise.
So simply do this:
Public Sub TestConnection()
Using con = New SqlConnection("connection string here")
Using cmd = new SqlCommand("sql query here", con)
' do something, f.e. cmd.ExecuteNonQuery() '
End Using
End Using
End Sub
I'm starting to put in a database into my application, however I'm drawing a blank on how to share a database connection among the dozen or so different forms in my MDI application. I'm assuming this has to do with interfaces or something but I can't find any relevant examples anywhere. Can someone help me out? Ideally what I'd like is when the app is loaded up there is a call to a function in the forms loading area which establishes a single connection to the mdb, that I can then call via any form so I don't always have to open/close connections everytime I need to update the db (assuming what I'm suggesting is better for overhead), unless that is a better option?
Here's a basic example of the mdb database access code I've got working:
Dim dt As DataTable = New DataTable()
Dim OleDbTran As OleDbTransaction = Nothing
Using connJET As OleDbConnection = New OleDbConnection("connection string here...")
Try
connJET.Open()
Dim sqlCount As OleDbCommand = New OleDbCommand("select * from mytable", connJET)
Using aReader As OleDbDataReader = sqlCount.ExecuteReader()
dt.Load(aReader)
End Using
If (dt.Rows.Count > 0) Then
MsgBox(dt.Rows.Count)
End If
OleDbTran = connJET.BeginTransaction()
Dim aCommand As OleDbCommand = connJET.CreateCommand()
aCommand.CommandText = "INSERT INTO Programs (title) VALUES (#title)"
aCommand.Transaction = OleDbTran
aCommand.Parameters.Add("#title", OleDbType.VarChar)
aCommand.Parameters("#title").Value = "Test"
aCommand.ExecuteNonQuery()
OleDbTran.Commit()
Catch ex As Exception
MessageBox.Show(ex.Message)
End Try
End Using
Assuming that you create the connection in your startup form, then you could just add constructors to the other forms that accept a SqlConnection and send that in whenever you create an instance of that form.
Or if you prefer, you create something like this:
Public Class Connection
Private Shared connection As OleDb.OleDbConnection
Public Shared ReadOnly Property Instance As OleDb.OleDbConnection
Get
If connection Is Nothing Then
connection = New OleDb.OleDbConnection("connstring")
End If
Return connection
End Get
End Property
End Class
And then you could access it by just calling Connection.Instance whenever you need it.