i need to call a function that do some query while another connection is opened and its doing a transaction.
Ok i get this is weird, here some code:
Main part:
Using connection As New SqlConnection(connectionString)
connection.Open()
Dim command As SqlCommand = connection.CreateCommand()
Dim transaction As SqlTransaction
transaction = connection.BeginTransaction("myTransaction")
command.Connection = connection
command.Transaction = transaction
command.CommandText = sSQL
Try
command.ExecuteNonQuery()
Dim functionResult As String = myFunction(param1, param2)
If functionResult <> "" Then
'error! i need to rollback the first query done here!
transaction.Rollback()
else
transaction.Commit()
End If
Catch ex As Exception
transaction.Rollback()
End Try
End If
End Using
myFunction do lot of stuff, and a lot of querys. Every query needs to reopen connection (without transaction this time) but everytime i try to execute the first query inside my function i got timeout error from database (after 30 seconds).
I know i can do this work "copy-pasting" all the myFunction code inside that already opened connection and using the already opened connection, but i use that function more than once and i don't want to mess up my code.
How can i solve this?
edit for more information:
that was an already reduced version of the code i'm using, but here a reduced version on what "myFunction" do:
Dim connectionString As String = "my connection string"
Dim queryString As String = "SELECT id FROM foo WHERE param1 = #myValue"
Dim ds As DataSet = New DataSet()
Try
Using connection As New SqlConnection(connectionString)
Dim command As New SqlCommand(queryString, connection)
connection.Open()
command.CommandText = queryString
command.Parameters.Add("#myValue", SqlDbType.Int).Value = 10
Dim adapter As New SqlDataAdapter()
adapter.SelectCommand = command
adapter.Fill(ds, "randomName")
If ds.Tables("randomName").Rows.Count < 0 Then
'error!
connection.Close()
Return "error"
End If
End Using
Catch ex As Exception
Return "Database error - " & ex.Message
End Try
The code execution (even in debug) freeze on the adapter.Fill(ds, "randomName") command for 30 seconds, after that i get a timout error
You can use as many connections as you want, just make sure they don't interfere with each other. SQL server is very diligent about preserving data integrity, so if one uncommitted transaction conflicts with another uncommitted transaction, you get a deadlock.
You may want to play with transaction isolation level, default is READ COMMITTED for SQL server, try to set it to READ UNCOMMITTED. Please read the docs to be aware of the consequences.
From the above link:
In SQL Server, you can also minimize locking contention while protecting transactions from dirty reads of uncommitted data modifications using either:
The READ COMMITTED isolation level with the READ_COMMITTED_SNAPSHOT database option set to ON.
The SNAPSHOT isolation level.
Related
I am using sql server with my VB.NET application where in multiple instance of the application is run from different server (CITRIX). I am sorting and picking up one individual Row for processing and immediately marking that row as picked in a column so that other instance doesn't pick up the same row and waste time. The issue is, in between picking up the row and updating as picked, another instance of the application is picking up the row. I have been suggested for using with DB Lock but the concept is not that much clear to me like whether it will solve my problem, whether I need admin right to use it (I do not have admin right in client DB) etc. Below is the code snippet I have used.
Dim MyConnection As SqlConnection
Try
MyConnection = New SqlConnection(connString)
MyConnection.Open()
Dim tableName As String = myTableName
Dim sqlQuery As String = "Select Top 1 * from " + tableName + " where "<some condition>
Dim MyCommand As SqlDataAdapter = New SqlDataAdapter(sqlQuery, MyConnection)
Dim DS as DataSet = New DataSet
MyCommand.Fill(DS, tableName)
If DS.Tables(0).Rows.Count >= 1 Then
sqlQuery = "UPDATE " + tableName + " SET Fld = #fld where Cond1= '" + DS.Tables(0).Rows(0).Item("Cond1").ToString + "'"
Dim cmd As New Data.SqlClient.SqlCommand(sqlQuery)
cmd.CommandType = CommandType.Text
cmd.Parameters.Add("#fld", Data.SqlDbType.VarChar).Value = "Picked"
Try
cmd.Connection = MyConnection
cmd.ExecuteNonQuery()
Catch ex As Exception
MsgBox(ex.ToString)
End Try
End If
Catch ex As Exception
MsgBox(ex.ToString)
Finally
MyConnection.Close()
End Try
I want it to make in such way that if an instance picks up a row, until it finishes updating the row, the row will not be visible to other instance with same query on the table, but other instance will continue to work with the other rows at the same time.
Two options I see:
Change your SELECT and UPDATE queries to a single UPDATE query. I didn't see where your SELECT was buying you anything.
If the SELECT is truly needed, then use a stored procedure on the database to handle the SELECT and the UPDATE on the database server side. You can lock the row during the transaction. See: Transaction Locking and Row Versioning Guide
Note that in general you should try to move your database queries to stored procedures. Not only does this reduce the amount of network traffic moving datasets back and forth, it increases the reliability, separates your database code from the UI, allows updates to the procedures without having to push new versions of the client application out and also avoids SQL injection.
Preparing an application which will be used by around 40 users in office with local SQL Server at local network. Application developed in VB.NET. I already read some documentation but would like to get some knowledge directly from your side about access to data.
This is a Winforms app and I wonder whether transactions I am using will be just enough to protect data e.g when one user uses some data and other one will change it in same time, does transaction would protect it? Can someone explain me briefly how it is?
Example of SQL transaction I use in my application
Dim result As Boolean = True
Dim strcon = New AppSettingsReader().GetValue("ConnectionString", GetType(String)).ToString()
Using connection As New SqlConnection(strcon)
'-- Open generall connection for all the queries
connection.Open()
'-- Make the transaction.
Dim transaction As SqlTransaction
transaction = connection.BeginTransaction(IsolationLevel.ReadCommitted)
Try
For Each sentId In pSentsId
'-- Insert aricle to Article's table (T_Artikel) and get inserted row id to use it in other queries
Using cmd As New SqlCommand("INSERT INTO T_Sentence_SubSec_SecKatSubKat_SubSubKat (FK_Sentence_ID, FK_SubSec_SecKatSubKat_SubSubKat) VALUES (#FK_Sentence_ID, #FK_SubSec_SecKatSubKat_SubSubKat)", connection)
cmd.CommandType = CommandType.Text
cmd.Connection = connection
cmd.Transaction = transaction
cmd.Parameters.AddWithValue("#FK_Sentence_ID", sentId)
cmd.Parameters.AddWithValue("#FK_SubSec_SecKatSubKat_SubSubKat", SubSec_SecKatSubKat_SubSubKat)
cmd.ExecuteScalar()
End Using
Next
transaction.Commit()
Catch ex As Exception
result = False
'-- Roll the transaction back.
Try
transaction.Rollback()
Catch ex2 As Exception
' This catch block will handle any errors that may have occurred
' on the server that would cause the rollback to fail, such as
' a closed connection.
'Console.WriteLine("Rollback Exception Type: {0}", ex2.GetType())
'Console.WriteLine(" Message: {0}", ex2.Message)
End Try
End Try
End Using
Return result
There are two versions according to your business rules.
A) All inserts succeed or all fail.
Dim result As Boolean = True
Dim strcon = New AppSettingsReader().GetValue("ConnectionString", GetType(String))'.ToString()'no need. It is already **String**
Using connection As New SqlConnection(strcon)
''//--Following 2 lines are OK
''//--Using cmd As New SqlCommand("INSERT INTO T_Sentence_SubSec_SecKatSubKat_SubSubKat (FK_Sentence_ID, FK_SubSec_SecKatSubKat_SubSubKat) VALUES (#FK_Sentence_ID, #FK_SubSec_SecKatSubKat_SubSubKat)", connection)
''//--cmd.CommandType = CommandType.Text
''//--but these two are better
Using cmd As New SqlCommand("dbo.T_Sentence_SubSec_SecKatSubKat_SubSubKat_ins ", connection)
''//-- Insert aricle to Articles table (T_Artikel) and get inserted
''//--row id to use it in other queries
cmd.CommandType = CommandType.StoredProcedure
''//-- Open generall connection for all the queries
''//--open connection in try block
''//--connection.Open()
''//-- Make the transaction.
''//--Dim transaction As SqlTransaction
''//--transaction = connection.BeginTransaction(IsolationLevel.ReadCommitted)
Try
connection.Open()
cmd.Transaction = connection.BeginTransaction()
cmd.Parameters.Add("#FK_Sentence_ID", SqlDbType.Int) ''//--or whatever
cmd.Parameters.Add("#FK_SubSec_SecKatSubKat_SubSubKat", SqlDbType.Int)
For Each sentId In pSentsId
cmd.Parameters("#FK_Sentence_ID").Value = sentId
cmd.Parameters("#FK_SubSec_SecKatSubKat_SubSubKat").Value = SubSec_SecKatSubKat_SubSubKat
cmd.ExecuteNonQuery() ''//--returns rows affected. We do not use result
Next
''//--everything is OK
cmd.Transaction.Commit()
Catch ex as SqlException
result = False
''//--SqlException is more informative for this case
If cmd.Transaction IsNot Nothing
cmd.Transaction.Rollback
''//--extra try...catch if you wish
End If
Catch ex As Exception
result = False
''//-- Roll the transaction back.
Try
cmd.Transaction.Rollback()
Catch ex2 As Exception
''// This catch block will handle any errors that may have occurred
''// on the server that would cause the rollback to fail, such as
''// a closed connection.
''//Console.WriteLine("Rollback Exception Type: {0}", ex2.GetType())
''//Console.WriteLine(" Message: {0}", ex2.Message)
End Try
Finally
If connection.State <> Closed
connection.Close()
End If
End Try
End Using''//cmd
End Using''//connection
Return result
B) Each insert is independent. Don't use overall transaction. Add try...catch inside for loop.
We have a problem with some database code that apparently executes with the wrong isolation level. In this particular part of the code, it is supposed to execute with "READ UNCOMMITTED" to minimize locks. Inconsistent data is OK at this point.
However, the code actually reads with READ COMMITTED, and we can't figure out why.
Here's what we did:
Open the connection
Execute on this connection "SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED"
Hit a breakpoint
Execute the SQL
On the breakpoint, we issue this command to the database:
select s.session_id, s.transaction_isolation_level, st.text from sys.dm_exec_sessions s
inner join sys.sysprocesses sp on (sp.spid = s.session_id)
CROSS APPLY sys.dm_exec_sql_text(sp.sql_handle) st
This SQL reports 4 pooled connections right now, one of which is our connection that we can step beyond the breakpoint to execute our SQL with, that has this state:
53 2 SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
ie. session 53 has isolation level 2 (READ COMMITTED), and the last SQL that was executed on this session was that "SET TRANSACTION ..." command.
How can this be?
We verified with SQL Profiler that this connection did not live before our .NET code opened it, so it was not reused from the connection pool.
Yet, with a fresh connection, and the only and first SQL executed on it explicitly told it to use READ UNCOMMITTED, how can the connection still be READ COMMITTED?
What should we look at here?
The connection string (with bits redacted) is like this:
SERVER=hostname;DATABASE=dbname;Integrated Security=false;USER ID=sa;PASSWORD=****;Application Name=appname;Type System Version=SQL Server 2000;Workstation ID=hostname;
The connections are normal SqlConnection connections, opened in the normal way.
Unfortunately we're unable to reproduce the problem if we write normal code opening a SqlConnection, so there has to be something with the application state, but since SqlProfiler and Sql Server both tells us that yes, the SQL was executed, but no, I don't care.
What can impact this?
The exact same code also opens other connections, that is, the code is executed many times and opens many connections, so more than one connection ends up in the pool, yet only the very first connection ends up having this problem.
This is SQL Server 2008 R2 and we have also reproduced this problem on 2012.
Edit
OK, some more information.
First, we are enabling pooling, or rather, we're not explicitly disabling it, nor are we twiddling the connection string to make "N" pools.
However, this connection is the first being opened with this particular connection string, thus it is not retrieved from the pool. Also see my note below about it being permanently "sick".
This connection is being set up like this:
var conn = new SqlConnection(...);
conn.StateChance += connection_StateChange;
private void connection_StateChange(Object sender, StateChangeEventArgs e)
{
if (e.CurrentState == ConnectionState.Open)
{
using (IDbCommand cmd = ((SqlConnection)sender).CreateCommand())
{
cmd.CommandText = "SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED";
cmd.ExecuteNonQuery();
}
We're not executing any other SQL before this.
Note that this code is used many times during the lifetime of the application, it is only the very first connection it opens that ends up being wrong.
This connection also becomes permanently sick. Since every time we open the connection (even though we might get it out of the connection pool), the above state change event executes, attempting to set the isolation level again. This also fails, but just for this single connection.
Additionally we've found one thing that impacts this since I posted this question.
By changing the connection string, that I posted above:
...;Type System Version=SQL Server 2000;...
to this:
...;Type System Version=SQL Server 2008;MultipleActiveResultSets=true;...
then this problem goes away, at the breakpoint listed earlier, the connection now has "READ UNCOMMITTED" state.
This was a red herring, the connection was no longer being reported in our overview until we had actually executed code there.
We're continuing our debugging.
The problem here is that the SqlConnection.BeginTransaction that does not take parameters defaults to read committed. I guess we didn't understand what the "default isolation level" text is on that page.
That page has this text:
If you do not specify an isolation level, the default isolation level is used. To specify an isolation level with the BeginTransaction method, use the overload that takes the iso parameter (BeginTransaction). The isolation level set for a transaction persists after the transaction is completed and until the connection is closed or disposed. Setting the isolation level to Snapshot in a database where the snapshot isolation level is not enabled does not throw an exception. The transaction will complete using the default isolation level.
(my highlight)
Here's a LINQPad script that demonstrates:
void Main()
{
using (var conn = new SqlConnection("Data Source=.;Initial Catalog=master;Integrated security=true"))
{
conn.Open();
Dump(conn, "after open");
using (var cmd = new SqlCommand())
{
cmd.Connection = conn;
cmd.CommandText = "SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED";
cmd.ExecuteNonQuery();
}
Dump(conn, "after set iso");
using (var cmd = new SqlCommand())
{
cmd.Connection = conn;
cmd.CommandText = "BEGIN TRANSACTION";
cmd.ExecuteNonQuery();
}
Dump(conn, "after sql-based begin transaction");
using (var cmd = new SqlCommand())
{
cmd.Connection = conn;
cmd.CommandText = "COMMIT";
cmd.ExecuteNonQuery();
}
Dump(conn, "after sql-based commit");
var trans = conn.BeginTransaction();
Dump(conn, "after .net begin transaction", trans);
trans.Commit();
Dump(conn, "after .net commit");
}
}
public static void Dump(SqlConnection connection, string title, SqlTransaction transaction = null)
{
using (var cmd = new SqlCommand())
{
cmd.Connection = connection;
if (transaction != null)
cmd.Transaction = transaction;
cmd.CommandText = "SELECT transaction_isolation_level FROM sys.dm_exec_sessions WHERE session_id = ##SPID";
Debug.WriteLine(title + "=" + Convert.ToInt32(cmd.ExecuteScalar()));
}
}
It will output:
after open=2
after set iso=1
after sql-based begin transaction=1
after sql-based commit=1
after .net begin transaction=2
after .net commit=2
Here you can see that manually beginning and committing a transaction through SQL would not change the isolation level, but beginning a transaction in .NET without explicitly stating the isolation level still changes it to read committed.
Since everywhere we read, starting a transaction without explicitly stating the isolation level said that it inherited the isolation level of the session, I guess we didn't understand that .NET would not do the same.
I have an old visual basic 6 application when some users reported me errors when the computer going back from sleep. This problem did not occurs on every client computer, (I would say some Windows 7). If the vb6 application was still open then if they try to use this application it crashes with the following error message.
I debugged and I found the problem: I have a global variable that keep the connection to the database. This variable is initialized only once in the beginning of the application. When the computer going to sleep and go back some times later, the status of this variable is still "OPEN" but in fact the connection is lost! If I "CLOSE" and then "OPEN" this variable connection I am able to query the database.
I wonder if this is normal that I lost my database connection?!
Here is some code:
' This is my global variable
Global cn As New ADODB.Connection
' Set connection properties for sql server.
cn.ConnectionTimeout = 25
cn.Provider = "sqloledb"
cn.Properties("Data Source").Value = ".\SQL2008"
cn.Properties("Initial Catalog").Value = DB_INITIAL_CATALOG
cn.Properties("User ID").Value = DB_USERNAME
cn.Properties("Password").Value = DB_PASSWORD
cn.Open
' This is a typical query on my database
Set rs = New ADODB.Recordset
strSql = "SELECT * FROM tblUsers"
rs.Open strSql, cn, adOpenKeyset
Any idea?
Thanks.
Yes, I've seen this kind of thing before. There may be settings on the connect string that can help reduce it, but time outs and/or network drops can break the underlying (often TCP) connection to the DB server. You then see the error manifest itself in the next I/O to the database.
I recommend wrapping access to the shared connection so you can transparently catch that specific error and retry. Keep the connection private in a class or module and have methods such as:
'Open is called to set the args to connect, these should be saved for reconnect
Public Sub Open(connect params here)
'save arsg to prive members to reconnect
'connect to db
End Sub
Public Function OpenKeyset(sql) As RecordSet
Set rs = New ADODB.Recordset
On Error Resume Next
rs.Open strSql, privateConn, adOpenKeyset
'if the error is the disconnect
If Error.Number = xxx Then 'or inspect the error message or error collection
'turn of error trap
Err.Clear
On Error Goto 0
'reopen db conn
'then retry
rs.Open strSql, privateConn, adOpenKeyset
End If
OpenKeyset = rs
End Function
You could even periodically perform a no-op db operation like quering the catalog or whatever to keep the connection live and proactivle reconnect. You couild also watch for large jumps in time that happen if a computer hibernates.
You should trap the sleep and wake events in your vb6 code, any long running queries should be closed at sleep, so that the dB connection can be closed. At wake, you do the opposite. You need to listen to
WM_POWERBROADCAST message.https://msdn.microsoft.com/en-us/library/windows/desktop/aa373247(v=vs.85).aspx
Good luck
Jeppe
Currently I'm trying to create some coding short cuts for our website. Right now to query a database and return a record set 2 functions have to be called:
GetDBConn returns an open ado connection object to the connection string passed in.
GetRS returns a record set object based on the ado connection and sql passed in.
set objConn = GetDBConn(connString, commandTimeout, connTimeout, cursorType)
set objRs = GetRS(objConn, sql)
I want to essentially write those two as a single function but my question really becomes this... I am pretty sure that you're supposed to close and destroy your ado connections when done with them. If I dim the connection inside the new function, query the database and return the record set, I can't close and destroy the connection inside the function or else the record set returned by the function becomes useless (i.e. that connection object is never explicitly closed/destroyed). Is that ok? Will it have any negative impact on the SQL/Web Servers?
Thanks
Not closing connections will cause problems with your SQL server's resources. Depending on your hardware and how many connections are getting established, you may not notice a problem. Or it may make the server inaccessible...
Using it for record sets is probably not a good idea (too easy to forget to close them), however opening / closing a connection shouldn't be a problem. I use the following two functions on all my legacy Classic ASP sites:
conn.asp:
<%
Dim oConn
Sub openConn()
Set oConn = Server.CreateObject("ADODB.Connection")
oConn.Open sConn 'connection string here'
End Sub
Sub closeConn()
If IsObject(oConn) Then
On Error Resume Next
If oConn.State = 1 Then
oConn.Close
End If
Set oConn = Nothing
Err.Clear
On Error Goto 0
End If
End Sub
%>
Then I include conn.asp and use openConn() once at the top of the page and closeConn() at the bottom.
The On Error Resume Next is usually regarded as a "bad coding" practice, however in this case I think it's appropriate otherwise you'll be getting errors on all the pages you use closeConn() on for those edge cases where oConn.State fails for any reason.
I've always used the dbhelper.asp provided by the MS reference app FmStocks
The functions always return a disconnected recordset so you never get in trouble
The functions are like this one:
Function RunSQLReturnRS(sqlstmt, params())
On Error Resume next
' Create the ADO objects'
Dim rs , cmd
Set rs = server.createobject("ADODB.Recordset")
Set cmd = server.createobject("ADODB.Command")
' Init the ADO objects & the stored proc parameters'
cmd.ActiveConnection = GetConnectionString()
cmd.CommandText = sqlstmt
cmd.CommandType = adCmdText
cmd.CommandTimeout = 900 ' 15 minutes
collectParams cmd, params
' Execute the query for readonly'
rs.CursorLocation = adUseClient
rs.Open cmd, , adOpenForwardOnly, adLockReadOnly
If err.number > 0 then
BuildErrorMessage()
exit function
end if
' Disconnect the recordset'
Set cmd.ActiveConnection = Nothing
Set cmd = Nothing
Set rs.ActiveConnection = Nothing
' Return the resultant recordset'
Set RunSQLReturnRS = rs
End Function