I have the following code which simply executes a stored procedure which accepts 1 parameter.
Public Function GetData(ByVal Faccode As String, Optional ByRef s As String = "") As DataSet
Dim params As SqlParameter() = {New SqlParameter("#aFacilityCode", SqlDbType.VarChar, ParameterDirection.Input)}
' Set the value
params(0).Value = "SW29" 'Faccode
Try
Dim DSet As DataSet = RunProcedure("usp_FL_GetAllData", params, "ContactData")
Return DSet
Catch ex As Exception
Return Nothing
End Try
End Function
Protected Overloads Function RunProcedure( _
ByVal storedProcName As String, _
ByVal parameters As IDataParameter(), _
ByVal tableName As String) _
As DataSet
Dim dataSet As New dataSet
Try
myConnection.Open()
Dim sqlDA As New SqlDataAdapter
sqlDA.SelectCommand = BuildQueryCommand(storedProcName, parameters)
sqlDA.Fill(dataSet, tableName)
Return dataSet
Catch ex As Exception
Return Nothing
Finally
If myConnection.State = ConnectionState.Open Then
myConnection.Close()
End If
End Try
End Function
Private Function BuildQueryCommand( _
ByVal storedProcName As String, _
ByVal parameters As IDataParameter()) _
As SqlCommand
Dim command As New SqlCommand(storedProcName, myConnection)
command.CommandType = CommandType.StoredProcedure
Dim parameter As SqlParameter
For Each parameter In parameters
command.Parameters.Add(parameter)
Next
Return command
End Function
The SQL procedure is defined like so:
CREATE PROCEDURE [dbo].[usp_FL_GetAllData]
(
#aFacilityCode VARCHAR(10)
)
When I run the software, SQL Profiler shows this call is being made:
exec usp_FL_GetAllData #aFacilityCode='S'
Initially, I was assigning the value Faccode for my parameter in the GetData function but noticed this weird truncation, which is why I'm now hardcoding the value.
The only thing I could think of is that the SQL procedure defined the parameter as a varchar(1) but it's defined as 10 so I don't know why this is happening. RunProcedure is used in many places which do not exhibit this behavior.
What else could be causing this?
To see why removing the parameter direction from your constructor call solves the problem, take a look at the list of constructors defined by the SqlParameter class. Note that there is no constructor that takes a parameter name, SqlDbType, and ParameterDirection; the constructor you're actually invoking is this one, whose third parameter is the parameter size. Because the backing value of ParameterDirection.Input is 1, you are explicitly setting the size of the parameter to one character.
When you instead invoke a constructor that doesn't explicitly give a size, the object infers the size of the parameter from the value that you assign, as described in the documentation for that property.
This makes a number of important changes to your code. It does address the parameter length issue, but you'll need to check if it actually helps.
's was not used, and ByRef is a code smell in .Net
Public Function GetData(ByVal Faccode As String) As DataSet
Dim params As New SqlParameter("#aFacilityCode", SqlDbType.VarChar, 10)
If String.IsNullOrEmpty(Faccode) Then Faccode = "SW29"
params.Value = Faccode
'Removed Try/Catch handler. It's NEVER a good idea to just swallow exceptions like that. Let the exception bubble up to higher level code that knows how to handle it.
Return RunProcedure("usp_FL_GetAllData", "ContactData", params)
End Function
'Note the change to SqlParameter. IDataParameter does not have a Length or Size property. That MIGHT be your problem.
'Also note the use of ParamArray... required changing the order of the arguments, but helped simplify code in the first function
Protected Overloads Function RunProcedure( _
ByVal storedProcName As String, ByVal tableName As String, _
ByVal ParamArray parameters() As SqlParameter) _
As DataSet
Dim dataSet As New dataSet
Using myConnection As New SqlConnection("string here"), _
command As SqlCommand = BuildQueryCommand(storedProcName, parameters), _
sqlDA As New SqlDataAdapter(command)
command.Connection = myConnection
sqlDA.Fill(dataSet, tableName) '.Fill() will open the connection for you if needed
End Using
Return dataSet
End Function
Private Function BuildQueryCommand( _
ByVal storedProcName As String, _
ByVal ParamArray parameters() As SqlParameter) _
As SqlCommand
Dim command As New SqlCommand(storedProcName)
command.CommandType = CommandType.StoredProcedure
If parameters IsNot Nothing Then command.Parameters.AddRange(parameters)
Return command
End Function
Note these changes WILL likely impact other code in your application, but they are important.
Changing the first line of GetData from this:
Dim params As SqlParameter() = {New SqlParameter("#aFacilityCode", SqlDbType.VarChar, ParameterDirection.Input)}
To this:
Dim params As SqlParameter() = {New SqlParameter("#aFacilityCode", SqlDbType.VarChar)}
Fixed my issue. I'm not sure why, and if anyone knows I'd love to know why.
Related
I am trying to get multiple data sets from SQL Server using a VB.NET application. The problem that every time I try to execute the query,
I get this message:
Cannot change property 'ConnectionString'. The current state of the connection is open
Then I tried to fix it by enabling MARS
<connectionStrings>
<add name="ConString"
providerName="System.Data.SqlClient"
connectionString="Data Source=my-PC;Initial Catalog=Project;Persist Security Info=True; MultipleActiveResultSets=true;User ID=user;Password=*****" />
</connectionStrings>
This is my code
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim obj, body
obj = TextBox1.Text
body = TextBox2.Text
For Each mail In getemail()
Send_mail(mail, obj, body, getattachment(mail))
Next
MsgBox("Traitement effectué")
End Sub
Function getemail() As List(Of String)
Dim strMailTo As New List(Of String)
Dim SQL As String = "Select EMail FROM [USER] WHERE EMail Is Not NULL And MatriculeSalarie Is Not NULL And [EMail] <> '' and EtatPaie = 3 and BulletinDematerialise = 1 "
Dim cmd As New SqlCommand
Dim sqLdr As SqlDataReader
Dim dr As DataRow
Try
ConnServer()
cmd.Connection = con
cmd.CommandText = SQL
Using sda As New SqlDataAdapter(cmd)
Using ds As New DataTable()
sda.Fill(ds)
sqLdr = cmd.ExecuteReader()
For i = 0 To ds.Rows.Count - 1
dr = ds.Rows(i)
strMailTo.Add(dr("EMail"))
Next
End Using
End Using
Return strMailTo
sqLdr.Close()
Catch ex As Exception
MsgBox(ex.Message.ToString)
End Try
closeCon()
Return strMailTo
End Function
Function getattachment(email) As String()
Dim SQL As String = "Select MatriculeSalarie FROM [USER] WHERE [EMail]='" & email & "'"
Dim cmd As New SqlCommand
Dim sqLdr As SqlDataReader
ConnServer()
cmd.Connection = con
cmd.CommandText = SQL
Dim mat As String
mat = ""
Dim Dir As String = ConfigurationManager.AppSettings("path1").ToString
Dim file()
sqLdr = cmd.ExecuteReader()
While sqLdr.Read
mat = sqLdr.GetValue(sqLdr.GetOrdinal("MatriculeSalarie"))
End While
file = IO.Directory.GetFiles(Dir, mat.Substring(1) & "*.pdf")
sqLdr.Close()
Return file
End Function
If all you are going to do is show a message box in a Catch, don't do it in the database code. Let the error bubble up to the user interface code and put the Try around where the method is called.
Do not declare variables without a DataType. The button code with Option Infer on sets the type of obj and body.
Private ConStr As String = "Your connection string"
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim obj = TextBox1.Text
Dim body = TextBox2.Text
Dim emails As New List(Of String)
Try
emails = getemail()
Catch ex As Exception
MessageBox.Show(ex.Message.ToString, "Error retrieving email list")
Exit Sub
End Try
For Each email In emails
Try
Send_mail(email, obj, body, getattachment(email))
Catch ex As Exception
MessageBox.Show(ex.Message, "Error getting attachments")
End Try
Next
MessageBox.Show("Traitement effectué")
End Sub
Parameters used by Sub and Function must have a DataType.
I don't know what you are doing here.
While sqLdr.Read
mat = sqLdr.GetValue(sqLdr.GetOrdinal("MatriculeSalarie"))
End While
Each iteration will overwrite the previous value of mat. I can only assume that you expect only a single value, in which case you can use ExecuteScalar to get the first column of the first row of the result set. Don't do anything with the data until after the connection is closed. Just get the raw data and close (End Using) the connection. Manipulate the data later.
Always use Parameters. Parameters are not treated as executable code by the database server. They are simply values. An example of executable code that could be inserted is "Drop table [USER];" where the value of a parameter belongs. Oops!
Function getemail() As List(Of String)
Dim SQL As String = "Select EMail FROM [USER]
WHERE EMail Is Not NULL
And MatriculeSalarie Is Not NULL
And [EMail] <> ''
And EtatPaie = 3
And BulletinDematerialise = 1;"
Dim dt As New DataTable
Using con As New SqlConnection("Your connection string"),
cmd As New SqlCommand(SQL, con)
con.Open()
Using reader = cmd.ExecuteReader
dt.Load(reader)
End Using
End Using
Dim strMailTo As New List(Of String)
strMailTo = (From row As DataRow In dt.AsEnumerable
Select row.Field(Of String)(0)).ToList
Return strMailTo
End Function
Function getattachment(email As String) As String()
Dim SQL As String = "Select MatriculeSalarie FROM [USER] WHERE [EMail]='" & email & "'"
Dim mat As String
Using con As New SqlConnection(ConStr),
cmd As New SqlCommand(SQL, con)
cmd.Parameters.Add("#email", SqlDbType.VarChar).Value = email
con.Open()
mat = cmd.ExecuteScalar().ToString()
End Using
Dim Dir As String = ConfigurationManager.AppSettings("path1").ToString
'Your original code was fine, no need for searchPattern.
'I added this so you could see if your search pattern was what you expected.
Dim searchPattern = mat.Substring(1) & "*.pdf"
Debug.Print(searchPattern) 'Appears in the Immediate window
Dim file = IO.Directory.GetFiles(Dir, searchPattern)
Return file
End Function
I have this code into a function:
Adp.SelectCommand = New SqlCommand()
Adp.SelectCommand.Connection = oConn
Adp.SelectCommand.CommandText = sp
Adp.SelectCommand.CommandType = CommandType.StoredProcedure
SqlCommandBuilder.DeriveParameters(Adp.SelectCommand)
table = New DataTable
Dim resultado = 0
Dim inputParamList As New List(Of SqlParameter)
For Each param As SqlParameter In Adp.SelectCommand.Parameters
If param.Direction = Data.ParameterDirection.Input OrElse _
param.Direction = Data.ParameterDirection.InputOutput Then
inputParamList.Add(param)
End If
Next
For Each parame As SqlParameter In inputParamList
Dim metodo() As String
Dim paramName As String = parame.ParameterName.ToString()
Dim paramValue As Object = DBNull.Value
metodo = parame.ParameterName.ToString().Split("#")
paramValue = Parametros(metodo(1))
Adp.SelectCommand.Parameters.AddWithValue(paramName, paramValue)
Next
resultado = Adp.SelectCommand.ExecuteNonQuery()
Adp.Fill(table)
Catch ex As Exception
MessageBox.Show("Ocurrió una excepción en: " + ex.Message + "", "SystemA", _
MessageBoxButtons.OK)
Adp.Dispose()
table.Dispose()
End Try
Return table
Basically, what I am trying to do is read directly to the database what parameters the stored procedure has and depending on the amount I have (input) I am creating them in this for cycle. So far so good, when I have to fill the DataTable with the result of the rows from the database I get the message: "The procedure has too many arguments specified". But if I debug the code, it assigns me the sql values and parameters correctly. If they are 3, 3 is created, if it is 1, 1 is created and so on.
I'm fix it:
Dim inputParamList As New List(Of SqlParameter)
For Each param As SqlParameter In Adp.SelectCommand.Parameters
If param.Direction = Data.ParameterDirection.Input OrElse param.Direction = Data.ParameterDirection.InputOutput Then
inputParamList.Add(param)
End If
Next
For Each parame As SqlParameter In inputParamList
Dim metodo() As String
Dim paramName As String
Dim paramValue As Object
metodo = parame.ParameterName.ToString().Split("#")
paramName = parame.ParameterName
paramValue = Parametros(metodo(1))
Adp.SelectCommand.Parameters(parame.ParameterName).Value = paramValue
==> 'Adp.SelectCommand.Parameters.Add(parame.ParameterName, parame.SqlDbType)
Next
Adp.Fill(table)
Catch ex As Exception
MessageBox.Show("Ocurrió una excepción en: " + ex.Message + "", "Sistema de Agencias", MessageBoxButtons.OK)
Adp.Dispose()
table.Dispose()
End Try
Return table
Just comment on the "Add" line and it works. This is because I was trying to add parameters and just enough to pass the value of the encapsulated methods to the parameter of type SQLParameter. Thanks for the answers and the help provided.
You seem to get the SqlCommandBuilder to download all the parameter information from the DB and add it to an SqlCommand witha populated parameter collection, but then you skip through it looking for input parameters, load them into a list and enumerate it adding even more parameters to the same command:
For Each parame As SqlParameter In inputParamList
Dim metodo() As String
Dim paramName As String = parame.ParameterName.ToString()
Dim paramValue As Object = DBNull.Value
metodo = parame.ParameterName.ToString().Split("#")
paramValue = Parametros(metodo(1))
'you're adding more parameters, with a slightly different but essentially same name!!?
Adp.SelectCommand.Parameters.AddWithValue(paramName, paramValue)
Next
Surely you should just enumerate the existing parameters and supply a value for them based on their name (the type will/ should already be correct? Parametros is a Dictionary(Of string, object) that has parameter names without # and values ?)
For Each param As SqlParameter In Adp.SelectCommand.Parameters
If param.Direction = Data.ParameterDirection.Input OrElse _
param.Direction = Data.ParameterDirection.InputOutput Then
param.Value = Parametros(param.ParameterName.TrimStart("#"c))
End If
Next
Throw all that List inputParam/second ForEach away
I am writing a web service function in VB.net in a stored procedure in SQL server. I think the DataReader does not return any value.
Here is my stored procedure in Sql Server:
Create proc getVillageName
#village varchar(50)
as
Begin
SELECT
lookup_table.value FROM dbo.lookup_table INNER JOIN dbo.lookup_description ON
lookup_table.group_id = lookup_description.desc_id WHERE
lookup_description.description = 'Village' AND
lookup_table.value LIKE #village + '%'
End
Here is my code:
Public Function GetVillage(ByVal villageName As String) As List(Of String)
Dim strsql As String
Dim Villagevalue As List(Of String)
Dim param As SqlParameter
Dim conn As New SqlConnection With {.ConnectionString = "Server=MINTOY_DEV\MIGSSERVER;Database=SouthLinkDBO;User=sa;Pwd=123;"}
Dim cmd As New SqlCommand
Dim reader As SqlDataReader
Using conn
strsql = "getVillageName"
cmd = New SqlCommand(strsql, conn)
cmd.CommandType = CommandType.StoredProcedure
param = New SqlParameter("#village", villageName)
cmd.Parameters.Add(param)
conn.Open()
reader = cmd.ExecuteReader
While reader.Read
Villagevalue.Add(reader.Item("value").ToString)
End While
End Using
End Function
You're missing a line:
While reader.Read
Villagevalue.Add(reader.Item("value").ToString)
End While
End Using
Return Villagevalue '<-----------------
End Function
Are you looking at compiler warnings? VB will tell you if you forget a Return statement.
Better yet, open the Project Properties, click on the Compiler tab, and set "Function returning a reference type without a return value" to "Error".
Also, how is this working at all?
Dim Villagevalue As List(Of String)
That should be
Dim Villagevalue As New List(Of String)
Lots of things we can improve here:
Public Iterator Function GetVillage(ByVal villageName As String) As IEnumerable(Of String)
Using conn As New SqlConnection("Server=MINTOY_DEV\MIGSSERVER;Database=SouthLinkDBO;User=sa;Pwd=123;"), _
cmd As New SqlCommand("getVillageName", conn)
cmd.CommandType = CommandType.StoredProcedure
cmd.Parameters.Add("#village", SqlDbType.VarChar, 50).Value = villageName
conn.Open()
Using rdr As SqlDataReader = cmd.ExecuteReader()
While reader.Read()
Yield DirectCast(reader("value"), String)
End While
End Using
End Using
End Function
The big thing is we actually return a value (Yield handles this for Iterator functions).
If you really need an actual List(Of String) (Hint: you usually don't; Your code will almost always perform a lot better if you arrange things to work with IEnumerable(Of T) for as long as possible) you can append a .ToList() when calling this method.
I've got a method that uses ADODB to execute a sproc and read the results as a recordset. Everything was working fine until i changed one of the output fields from varchar(2000) to varchar(max) (SQL2008). Now I can't read the data from that field. The strange thing is that the data is visible in the debugger immediately after running the Execute, but stepping in the debugger makes the data vanish.
Here is the code:
Public Function sproc_RptEAComment(ByVal accountName As String, ByVal contractName As String,
ByVal acctType As String, ByVal asOfDate As DateTime,
ByVal sessionID As String, ByVal clin As String,
ByVal dollarDisplay As String) As List(Of sproc_RptEAComment_Row) Implements ISprocRepository.sproc_RptEAComment
Try
Dim cmd = New Command()
cmd.ActiveConnection = Connection
cmd.CommandType = CommandTypeEnum.adCmdStoredProc
cmd.CommandText = "sproc_RptEAComment"
ADOUtilities.AddParamToSproc(cmd, "#ChargeNum", accountName)
ADOUtilities.AddParamToSproc(cmd, "#Contract", contractName)
ADOUtilities.AddParamToSproc(cmd, "#EmployeeName", "")
ADOUtilities.AddParamToSproc(cmd, "#Org", acctType)
ADOUtilities.AddParamToSproc(cmd, "#HoursVal", "TRUE")
ADOUtilities.AddParamToSproc(cmd, "#Sort", "1")
ADOUtilities.AddParamToSproc(cmd, "#Employer", "")
ADOUtilities.AddParamToSproc(cmd, "#Type", "1")
ADOUtilities.AddParamToSproc(cmd, "#FromD", asOfDate.ToShortDateString())
ADOUtilities.AddParamToSproc(cmd, "#ToD", asOfDate.AddMonths(-5).ToShortDateString())
ADOUtilities.AddParamToSproc(cmd, "#SessionID", sessionID)
ADOUtilities.AddParamToSproc(cmd, "#Clin", clin)
ADOUtilities.AddParamToSproc(cmd, "#RptDisplay", "")
ADOUtilities.AddParamToSproc(cmd, "#Parm_DT", dollarDisplay)
Dim lst = New List(Of sproc_RptEAComment_Row)
Dim rs = cmd.Execute()
While Not (rs.EOF)
Dim newEntity = New sproc_RptEAComment_Row(rs)
lst.Add(newEntity)
rs.MoveNext()
End While
Return lst
Catch ex As Exception
MsgLogger.Err(ex)
Return Nothing
End Try
End Function
If I look in the debugger immediately after the Execute I see this. Note the field EacJustCom has the proper string value:
I take one step in the debugger and see this. THe value is gone. Note the field "_Account" is still intact (it's defined as varchar(100)):
The problem you are having is that a varchar(max) can hold up to 2 GB of data.
In the old days with Visual Basic and DAO, there was a method call GetChunk() to read bits of data from a binary field and AppendChunk() to write bits. Use a binary or text file stream to put the data back together on the client side.
"Use the GetChunk method on a Field object to retrieve part or all of its long binary or character data."
See the MSDN reference.
These feilds have to be handled differently than a regular record set. Exclude them from your record set.
Check out this article Who is afraid of the big bad blob?
Since there are so many development environments, vs xxx or ADO or ADO.NET, it is difficult to point out your particular issues w/o more information.
Write back to me if this helps ...
I never found the answer to the problem, but did find a workaround. If I create the recordset first and fill it from the command, it works.
Dim rs As New ADODB.Recordset()
rs.CursorLocation = ADODB.CursorLocationEnum.adUseClient
rs.CursorType = ADODB.CursorTypeEnum.adOpenForwardOnly
rs.LockType = ADODB.LockTypeEnum.adLockBatchOptimistic
rs.StayInSync = False
rs.Open(cmd)
rs.ActiveConnection = Nothing
Dim lst = New List(Of sproc_RptEAComment_Row)
While Not (rs.EOF)
Dim newEntity = New sproc_RptEAComment_Row(rs)
lst.Add(newEntity)
rs.MoveNext()
End While
Return lst
I've written a console app in VB.NET to do some database work and a strange runtime error has arisen...
Here's the main code:
Sub Main(ByVal args() As String)
Try
user = args(0)
batchID = args(1)
GetBatchRevision()
'batchRev = 1
Dim getTestScripts As SqlCommand = New SqlCommand("GetTestScriptsInTestBatch", cs)
getTestScripts.CommandType = CommandType.StoredProcedure
Dim batchIDParam As SqlParameter = getTestScripts.Parameters.Add("#batchID", SqlDbType.Int, 4)
Dim batchRevParam As SqlParameter = getTestScripts.Parameters.Add("#batchRev", SqlDbType.Int, 4)
'batchIDParam.Value = 1
'batchRevParam.Value = 1
batchIDParam.Value = batchID
batchRevParam.Value = batchRev
Console.WriteLine(batchID & " " & batchRev)
Console.WriteLine(cs.State)
Console.ReadLine()
Using cs
cs.Open()
Dim reader As SqlDataReader = getTestScripts.ExecuteReader(CommandBehavior.CloseConnection)
While reader.Read()
Console.WriteLine("Executing Test Script " & reader("ScriptID").ToString() & " Revision " & reader("ScriptRev").ToString)
End While
Console.ReadLine()
End Using
Catch ex As Exception
End Try
End Sub
GetBatchRevision:
Private Sub GetBatchRevision()
Using cs
Dim GetNewestRev As New SqlCommand("SELECT Max(BatchRev) FROM TestBatch WHERE BatchID=" & batchID, cs)
cs.Open()
Dim reader As SqlDataReader = GetNewestRev.ExecuteReader(CommandBehavior.CloseConnection)
reader.Read()
If Not IsDBNull(reader(0)) Then
batchRev = reader(0).ToString()
End If
End Using
End Sub
batchRev and batchID are both global variables within the module.
Behaviorally:
The app prints out "1" (user input), "1" (database result), "0" (enum of Closed connection)
When I press Enter to get past the first Console.ReadLine(), the app simply closes out.
If I comment out GetBatchRevision and directly set batchRev = 1, I get the above result as well as "Executing Test Script 1 Revision 52", "Executing Test Script 2 Revision 66" which are the expected results from the stored procedure GetTestScriptsInTestBatch.
The global variable declarations are as follows:
Private batchID As String
Private batchRev As String
Any ideas why GetBatchRevision() causes the app to crash? By itself (removing the stored proc part of the code), it executes just fine. My initial guess was that there was a hanging connection, but ending a "using" block is supposed to close a SQL connection as well as any open readers associated with said connection (as mentioned before, cs.State returns 0).
Your problem is on these lines:
reader.Read()
If Not IsDBNull(reader(0)) Then
reader.Read() is probably returning false; yet you try to access reader(0). Boom!
You should change it to:
IF reader.Read() AndAlso Not IsDBNull(reader(0)) Then
'' etc
End If
It looks like cs is also a global variable. This is a bad idea. .Net data access works a lot better when you're using a new connection each time. You're probably fine in this app, but you're setting up some bad habits. Instead, load your connection string as a global variable and use that when creating your connections.
Next up, there's no reason for GetBatchRevision() to talk to global variables. Have it accept an argument and return it's result instead. And of course I can't overlook the sql injection issue because you concatentate the batchid to the end of your string. Here's the new version of the function after fixing those errors:
Private Function GetBatchRevision(ByVal BatchID As String) As String
Using cn As New SqlConnection(cs), _
GetNewestRev As New SqlCommand("SELECT Max(BatchRev) FROM TestBatch WHERE BatchID= #BatchID", cn)
GetNewestRev.Parameters.Add("#Batch", SqlDbType.Int).Value = Convert.ToInt32(BatchId)
cn.Open()
Return GetNewestRev.ExecuteScalar().ToString()
End Using
End Function
This can get even better if you keep BatchRev and BatchID as int's rather than strings internally.