Multiple SQL Server databases access - sql-server

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.

Related

How to fix ADODB connection simple login code WPF

I have been dying in solving the issue in my code my goal is to simply look for the account relative to the user's input. I have been encountering closed states of my record set and or having no response at all from my program i need some clarifications on my code and I also would like to know the best practices in implementing a ADODB connection as SQL query
Dim WithEvents ErrorMessageTimer As New DispatcherTimer
Private Sub BTNLogin_Click(sender As Object, e As RoutedEventArgs) Handles BTNLogin.Click
If (Trim(FLDUsername.Text) = "") Then
LBLErrorMessage.Text = "Username is Empty"
LBLErrorMessage.Visibility = Visibility.Visible
ErrorMessageTimer.Interval = TimeSpan.FromSeconds(2.7)
ErrorMessageTimer.Start()
Else
Dim dbCon As New ADODB.Connection
Dim dbRecSet As New ADODB.Recordset
dbCon.Open("PROVIDER=Microsoft.jet.oledb.4.0;Data Source=inventory.mdb")
dbRecSet.Open("SELECT * FROM [User] WHERE Username='" & FLDUsername.Text & Chr(39), dbCon, ADODB.CursorTypeEnum.adOpenKeyset, ADODB.LockTypeEnum.adLockOptimistic)
Try
If (dbRecSet.Fields("Username").Value = FLDUsername.Text) And (dbRecSet.Fields("Password").Value = FLDPassword.Password) Then
Dim mainMenu As New MainMenuWindow
Me.Hide()
mainMenu.Show()
Else
ErrorMessageTimer.Start()
LBLErrorMessage.Text = "! Invalid Credentials"
End If
Catch ex As Exception
ErrorMessageTimer.Start()
LBLErrorMessage.Text = "! Account not Found"
End Try
dbCon.Close()
dbRecSet.Close()
End If
End Sub
I will repeat my comments here so you don't miss them.
Dump the ancient ADODB and switch to ADO.net. No Com interop.
Do not concatenate strings with user input to build Sql commands. You are risking Sql injection.
NEVER store passwords as plain text.
Using Parameters avoids the risk of Sql injections because the values will not be considered to be executable code. For OleDb (Access) the names of the parameters do not matter. It is the order that they are added to the ParametersCollection must match the order that they appear in the Sql command. Not only will parameters protect your database, they make it easier to write the Sql. No single quotes, double quotes, ampersands.
The Using...End Using blocks will ensure that your database objects are closed and disposed even if there is an error.
I don't think your Data Source = inventory.mdb will be sufficient for the file to be found. A complete path would be better.
I will leave it to you to research how to salt and hash passwords for storage and then retrieve and compare to user input.
Private Sub BTNLogin_Click(sender As Object, e As RoutedEventArgs) Handles BTNLogin.Click
If FLDUsername.Text = "" OrElse FLDPassword.Text = "" Then
'Make this visible at Design time
LBLErrorMessage.Text = "Please fill in both fields."
'I have no idea what you are trying to do with your timer.
Return
End If
Dim RetVal As Integer
Try
Using dbCon As New OleDbConnection("PROVIDER=Microsoft.jet.oledb.4.0;Data Source=inventory.mdb")
Using cmd As New OleDbCommand("SELECT Count(*) FROM [User] WHERE [Username] = #UserName And [Password] = #Password;", dbCon)
cmd.Parameters.Add("#UserName", OleDbType.VarChar, 50).Value = FLDUsername.Text
cmd.Parameters.Add("#Password", OleDbType.VarChar, 50).Value = FLDPassword.Text
dbCon.Open()
RetVal = CInt(cmd.ExecuteScalar())
End Using
End Using
Catch ex As Exception
'An error here is probably due to invalid connection of network error
'Show the error message not "! Account not Found"
MessageBox.Show(ex.Message)
Return
End Try
'Now your connection is closed and your objects disposed.
'Only after the last End Using do we evaluate the results of our query
'and take action.
If RetVal = 1 Then
Dim mainMenu As New MainMenuWindow
Me.Hide()
mainMenu.Show()
Else
LBLErrorMessage.Text = "! Invalid Credentials"
End If
End Sub

is there any way to lock a row temporarily

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.

StoredProcedure executes in SSMS, not in VB.NET application (no SqlException thrown)

I have a SQL StoredProcedure that performs two INSERT INTO operations in SSMS as expected.
When executing this SP in my VB.NET application, it is executing (no SqlException thrown in Try block) but not executing the INSERT INTO commands.
This application uses numerous SP's that all work without problems.
Code is as follows:
Using (ParentMDI.dbCon)
Dim sqlcmd As New SqlCommand("hyd_top_level_isr")
With sqlcmd
.CommandType = CommandType.StoredProcedure
.Parameters.AddWithValue("#part_num", part_num)
.Parameters.AddWithValue("#issue", issue)
End With
Try
sql.ExecuteNonQuery()
Catch ex As SqlException
If DialogResult.Yes = MessageBox.Show("Error inserting top-level ISR." & vbCrLf & vbCrLf & "Send Error Report?", "Workflow Error", MessageBoxButtons.YesNo, MessageBoxIcon.Exclamation) Then
ParentMDI.emailerrorstring = "Stored Procedure: hyd_top_level_isr"
ParentMDI.emailerrormessage = ex.Message
ParentMDI.ErrorEmail()
End If
End Try
End Using
For clarification;
I have inserted breakpoints before ExecuteNonQuery(). The sub does execute the ExecuteNonQuery() and the parameters being passed are populated with the correct values.
I have also inserted a RETURN in the SP to return the SCOPE_IDENTITY(). This returns an empty string (not NULL, as I was expecting).
If any of you need more information, please let me know.
I will be massively appreciative of anyone who can educate me on where I'm going wrong!
(This is my first time ever asking for help, please be kind!) :)
EDIT:
Sorry guys. I seem to have lead you on incorrectly. The code pasted above is me trying all sorts of different attempts at trying to solve this. What I'll post now is what I should have posted in the first place. With the same outcome. Sorry for the confusion, and thanks for your attempts so far...
Dim sqlcmd As New SqlCommand("hyd_top_level_isr", ParentMDI.dbCon)
With sqlcmd
.CommandType = CommandType.StoredProcedure
.Parameters.AddWithValue("#part_num", part_num)
.Parameters.AddWithValue("#issue", issue)
End With
Try
sqlcmd.ExecuteNonQuery()
Catch ex As SqlException
If DialogResult.Yes = MessageBox.Show("Error inserting top-level ISR." & vbCrLf & vbCrLf & "Send Error Report?", "Workflow Error", MessageBoxButtons.YesNo, MessageBoxIcon.Exclamation) Then
ParentMDI.emailerrorstring = "Stored Procedure: hyd_top_level_isr"
ParentMDI.emailerrormessage = ex.Message
ParentMDI.ErrorEmail()
End If
End Try
In addition to #Zohar Peled's comment I think you should set sqlCmd's connection and use sqlCmd.ExecuteNonQuery. Below you should also use local sqlConnection.
Using sqlcmd As New SqlCommand("hyd_top_level_isr", ParentMDI.dbCon)
With sqlcmd
.CommandType = CommandType.StoredProcedure
.Parameters.AddWithValue("#part_num", part_num)
.Parameters.AddWithValue("#issue", issue)
End With
Try
sqlCmd.ExecuteNonQuery()
Catch ex As SqlException
'' Handle exception here
End Try
End Using
Firstly I think that in SQLCommand you should pass the SQL connection like:
Dim sqlcmd As New SqlCommand("hyd_top_level_isr", ParentMDI.dbCon)
Secondly, you are not run sqlcmd but sql.executenonquery.
Third, executenonquery is an integer. If you dim a variable:
Dim result = sqlcmd.ExecuteNonQuery()
What do you get?

Concurrent reads using SQLFileStream?

Using .Net 4.52 and SQL Server 2014 with FILESTREAM
We have a webservice that is failing on concurrent reads with "System.InvalidOperationException: The process cannot access the file specified because it has been opened in another transaction."
I have isolated the code to reproduce the failure in a test program that spawns 10 concurrent tasks that read the same data with IsolationLevel.ReadCommitted and IO.FileAccess.Read. My understanding is thatthere will be a shared lock both in the database and the file system and there should be no "blocking".
With a single task, the code works consistently. Sometimes it works with 2-3 tasks. With 10 tasks the code fails nearly consistently - every once in a while it works. I have considered that other programmers might be accessing the data since the database is on one of our development servers, but that wouldn't explain the nearly consistent failure with 10 tasks.
Any suggestions as to what could be causing the failure would be greatly appreciated!
The code driving the test:
Dim profileKey As Guid = New Guid("DC3F1949-37DB-4D47-B204-0170FA4A40CD")
Dim taskList As List(Of Task) = New List(Of Task)
For x = 1 To 10
Dim tsa As New TestSqlFileStream
taskList.Add(Task.Run(Function() tsa.GetProfiles(profileKey, True)))
Next
Task.WaitAll(taskList.ToArray)
The class under test:
Public Class TestSqlFileStream
Public Function GetProfiles(profileKey As Guid, getSmallVersionOfImage As Boolean) As List(Of Profile)
Dim retProfiles = New List(Of Profile)
Using conn As New SqlConnection("server=blah,1435; initial catalog=blah;Trusted_Connection=Yes;")
conn.Open()
Dim cmd = conn.CreateCommand()
Dim iso As IsolationLevel = IsolationLevel.ReadCommitted
cmd.Transaction = conn.BeginTransaction(iso)
Try
cmd.CommandText = "GetProfiles"
cmd.CommandType = CommandType.StoredProcedure
cmd.Parameters.Add(New SqlParameter("#profileKey", SqlDbType.UniqueIdentifier)).Value = profileKey
Using reader As SqlDataReader = cmd.ExecuteReader
retProfiles = MapGetProfiles(reader, getSmallVersionOfImage)
End Using
cmd.Transaction.Commit()
Catch ex As Exception
cmd.Transaction.Rollback()
Throw
Finally
conn.Close()
End Try
End Using
Return retProfiles
End Function
Public Function MapGetProfiles(reader As SqlDataReader, getSmallVersionOfImage As Boolean) _
As List(Of Profile)
Dim profiles As New List(Of Profile)
Dim transactionToken As Byte()
Try
While reader.Read()
Dim profile As New ServiceTypes.SubTypes.Profile
profile.ParentKey = reader("ParentKey")
profile.ProfileKey = reader("ProfileKey")
profile.ProfileType = ConvertToProfileType(reader("ProfileType"))
If reader("Active") Is Nothing Then profile.Active = False Else profile.Active = reader("Active")
If IsDBNull(reader("Data")) Then profile.Data = Nothing Else profile.Data = reader("Data")
Dim imagePath
If getSmallVersionOfImage Then imagePath = reader("ImageThumbnailPath") Else imagePath = reader("ImagePath")
transactionToken = DirectCast(reader("transactionContext"), Byte())
If Not IsDBNull(imagePath) Then
If Not transactionToken.Equals(DBNull.Value) Then
LoadImage(profile, imagePath, transactionToken)
End If
End If
profiles.Add(profile)
End While
Catch ex As Exception
Throw
Finally
reader.Close()
End Try
Return profiles
End Function
Public Sub LoadImage(ByRef profile As Profile, image As String, transactionContext As Byte())
Using sqlFileStream = New SqlFileStream(image, transactionContext, IO.FileAccess.Read, FileOptions.SequentialScan, 0)
Dim retrievedImage = New Byte(sqlFileStream.Length - 1) {}
sqlFileStream.Read(retrievedImage, 0, sqlFileStream.Length)
profile.Image = retrievedImage
sqlFileStream.Close()
End Using
End Sub
Private Function ConvertToProfileType(profileType As String) As ProfileType
Dim type = ServiceTypes.SubTypes.ProfileType.None
Select Case profileType
Case Nothing
type = ServiceTypes.SubTypes.ProfileType.None
End Select
Return type
End Function
End Class
Update: I have looked at this question but the issue is different because they were splitting to parallel within a transaction: Threading and SqlFileStream. The process cannot access the file specified because it has been opened in another transaction
In my example each task starts its own transaction.
Update2 When I stop at a breakpoint within the transaction and run DBCC OPENTRAN in a query window the result is "No active open transactions" It seems like SqlConnection.BeginTransaction is not actually opening a transaction in the database.
Update3 Also read from transaction log (after naming the transaction):
Use myDB
GO
select top 1000 [Current LSN],
[Operation],
[Transaction Name],
[Transaction ID],
[Transaction SID],
[SPID],
[Begin Time]
FROM fn_dblog(null,null)
order by [Begin Time] desc
No transaction of the name I provided is showing in the log.
Note: This is only ok for solutions where the atomicity of the retrieved set is not critical. I suggest a TransactionScope for better atomicity.
It seems like the transaction queue/locking mechanism gets confused when many files are retrieved under the transaction (the While Reader.Read loop). I broke out the file retrieval to use a new transaction or each file retrieval and can run 100 parallel tasks against the same hierarchical set of profiles for a single parent.

Open connection while another connection is open

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.

Resources