I have a class, which has many "New()" functions to initiate it with various parameters. I would like to create a New() function that will init the class with no arguments. The problem is there is a "fillData" function which fills the data for the class, and takes in the stored procedure to do the work, but has a stored procedure parameter. I don't want to create a new fillData function, but would like to use the fillData function with no additional need for a stored procedure. My way of going about this was to pass an empty SqlParameter var to the fillData function, but no mater what I do, when I check the params parameter for being empty, it always seems to think there's stuff in there.
I would like to check if this "params" parameter is empty:
Public Sub New()
'just created this so i could pass something
'and continue using fillData as is.
Dim p(1) As SqlParameter
Dim db As sqldb.command
db = New sqldb.command(ConfigurationManager.ConnectionStrings("sqlConnectionString").ConnectionString)
fillData("r2m_getAllListingsSorted", p)
End Sub
...
Private Sub fillData(ByVal sp As String, ByVal params As SqlParameter())
Dim db As sqldb.command
Dim r As SqlDataReader
db = New sqldb.command(ConfigurationManager.ConnectionStrings("sqlConnectionString").ConnectionString)
'right here, im having the trouble, seems no matter how I try to "params",
'the code always defaults to db.executeReader(sp, p)
'I've tried params.Length = 0 and params.ToString = "" also.
If params.Value Then
r = db.executeReader(sp)
Else
r = db.executeReader(sp, params)
End If
How would I modify this so i could continue to use the fillData function, without passing a SqlParameter parameter to it?
Thank you!
You can make your SqlParameter Optional and give it a default value of Nothing:
Private Sub fillData(ByVal sp As String,
ByVal Optional params As SqlParameter() = Nothing)
Then call the method without the params argument:
fillData("StoredProcName")
You can then check the parameter inside the fillData method to see if it's Nothing before using it.
If params is Nothing Then
// Do something if there's no params
Else
// Do something if there is params
End If
Related
I'm developing an ASP.NET MVC web app in VB and I am required to output a set of data to a table format, and to allow the user to configure the order and presence of columns from an available set. The data set is stored as a list of the object type representing the row model.
Currently, I implement this using CallByName. Iterating over an ordered list of property names and outputting the value from the instance of the row model. However, based on testing this seems to be a major bottleneck in the process.
I've seen a recommendation to store delegates to get the property, against the string representation of the property's name. So, I can presumably do something like this:
Public Delegate Function GetColumn(ByRef RowObj As RowModel) As String
Dim GetPropOne As GetColumn = Function(ByRef RowObj As RowModel) RowObj.Prop1.ToString()
Dim accessors As New Hashtable()
accessors.Add("Prop1", GetPropOne)
Then, loop through and do something like this:
Dim acc As GetColumn = accessors(ColumnName)
Dim val As String = acc.Invoke(currentRow)
It looks faster, but it also looks like more maintenance. If this is indeed faster, is there a way I can dynamically build something like this? I'm thinking:
Public Delegate Function GetObjectProperty(Instance As Object) As Object
For Each prop In GetType(RowModel).GetProperties()
Dim acc As GetObjectProperty = AddressOf prop.GetValue
columns.Add(prop.Name, acc)
Next
Dim getColVal As GetObjectProperty = columns(ColumnName)
Dim val As String = getColVal.Invoke(currentRow).ToString()
Open to suggestions for different approaches.
I do a similar thing to turn a SOAP response into a Data Table
Public Function ObjectToDataSource(objName) As DataSet
Dim CollName = ""
Dim ds As New DataSet()
For Each m As System.Reflection.PropertyInfo In objName.GetType().GetProperties()
If m.CanRead Then
If InStr(m.PropertyType.ToString, "[]") <> 0 Then
CollName = m.Name
Exit For
End If
End If
Next
Dim CollObj
CollObj = CallByName(objName, CollName, CallType.Get)
If CollObj.length = 0 Then
Call EndTask("No Supply Chains to display", "Royal Mail failed to return Supply Chain information for these credentials", 3)
Else
Dim dt_NewTable As New DataTable(CollName)
ds.Tables.Add(dt_NewTable)
Dim ColumnCount = 0
For Each p As System.Reflection.PropertyInfo In CollObj(0).GetType().GetProperties()
If p.CanRead Then
If p.Name <> "ExtensionData" Then
dt_NewTable.Columns.Add(p.Name, p.PropertyType)
ColumnCount = ColumnCount + 1
End If
End If
Next
Dim rowcount = CollObj.Length - 1
For r = 0 To rowcount
Dim rowdata(ColumnCount - 1) As Object
For c = 0 To ColumnCount - 1
rowdata(c) = CallByName(CollObj(r), dt_NewTable.Columns.Item(c).ToString, CallType.Get)
Next
dt_NewTable.Rows.Add(rowdata)
rowdata = Nothing
Next
End If
Return ds
End Function
This is specific to my needs in terms of getting CollName and not requiring ExtensionData
If ColumnName is the same name as one of the RowModel's properties I don't see why you need the long workaround with delegates...
An extension method which gets only the property you want right now is both faster and consumes less memory.
Imports System.Runtime.CompilerServices
Public Module Extensions
<Extension()> _
Public Function GetProperty(ByVal Instance As Object, ByVal PropertyName As String, Optional ByVal Arguments As Object() = Nothing) As Object
Return Instance.GetType().GetProperty(PropertyName).GetValue(Instance, Arguments)
End Function
End Module
Example usage:
currentRow.GetProperty("Prop1")
'or:
currentRow.GetProperty(ColumnName)
Why is there no Shared function for ExecuteDataTable in SQLHelper.vb. There is an: ExecuteReader, ExecuteDataset and ExecuteScaler.
This is not a problem as I will write my own. I am just wandering why this is the case. I would normally use a DataReader but I am writing a data logic layer and the DataTable needs to outlive the connection (DataReaders cannot outlive a connection).
ExecuteDataset() will already do what you need. A dataset is, in one sense, just a collection of DataTables.
I would normally use a DataReader but I am writing a data logic layer and the DataTable needs to outlive the connection (DataReaders cannot outlive a connection).
In this case, may I suggest that instead of building an ExecuteDatatable() method, you build an ExecuteEnumerable() method that uses a DataReader in an Iterator block. The code would look something like this:
Public Shared Iterator Function ExecuteEnumerable(Of T)( ... ) As IEnumerable(Of T)
Using cn As New SqlConnection( ... ), _
cmd As New SqlCommand( ... )
'As needed
'cmd.Parameters.Add( ... ).Value = ...
Using rdr As SqlDataReader = cmd.ExecuteReader()
While rdr.Read()
Yield transform(rdr)
End While
End Using
End Using
End Function
You'll notice that I skipped over a few things. I'm not familiar with the existing SqlHelper.vb file, and as you would want to match existing style I left room in the code for you to adapt for that. However, there are two important pieces I want to call out:
Note the cmd.Parameters.Add() call. One common failing of utility sql help classes is that they fail to adequately provide for query parameters. All to often the result is horribly insecure code. If you don't have a way right now to pass parameter data for your existing methods, you need to make one. That is priority 1.
The transform(rdr) call there will use a Func(IDataRecord, T) delegate that must be supplied as an argument to the function. For the ExecuteEnumerable() iterator concept to work, you must take a copy of the current values in the SqlDataReader object on each iteration. You could set up some kind of generic data transfer object here, as is done with the DataRow type using in a DataTable. However, rather than spending cpu and memory time creating a copy into a generic data transport object of some type, I prefer to use a delegate to have the code copy it directly into a strongly-typed business object. The downside is needing to send instructions on how to do that for your specific object with every call to method. Most often, though, this is easy enough to do with a shared factory method on your business object.
We can create same as DataSet like
' Execute a SqlCommand (that returns a resultset) against the specified SqlConnection
' using the provided parameters.
' e.g.:
' Dim dt As DataTable = ExecuteDataTable(conn, CommandType.StoredProcedure, "GetOrders", new SqlParameter("#prodid", 24))
' Parameters:
' -connection - a valid SqlConnection
' -commandType - the CommandType (stored procedure, text, etc.)
' -commandText - the stored procedure name or T-SQL command
' -commandParameters - an array of SqlParamters used to execute the command
' Returns: A dataset containing the resultset generated by the command
Public Overloads Shared Function ExecuteDataTable(ByVal connection As SqlConnection, _
ByVal commandType As CommandType, _
ByVal commandText As String, _
ByVal ParamArray commandParameters() As SqlParameter) As DataTable
If (connection Is Nothing) Then Throw New ArgumentNullException("connection")
' Create a command and prepare it for execution
Dim cmd As New SqlCommand
Dim dt As New DataTable
Dim dataAdatpter As SqlDataAdapter
Dim mustCloseConnection As Boolean = False
PrepareCommand(cmd, connection, CType(Nothing, SqlTransaction), commandType, commandText, commandParameters, mustCloseConnection)
Try
' Create the DataAdapter & DataSet
dataAdatpter = New SqlDataAdapter(cmd)
' Fill the DataSet using default values for DataTable names, etc
dataAdatpter.Fill(dt)
' Detach the SqlParameters from the command object, so they can be used again
cmd.Parameters.Clear()
Finally
If (Not dataAdatpter Is Nothing) Then dataAdatpter.Dispose()
End Try
If (mustCloseConnection) Then connection.Close()
' Return the dataset
Return dt
End Function ' ExecuteDataTable
I know I'm being an idiot here and I just can't work it out. But i'm trying to take some data back from a vb.net database. It's falling over with a Object reference not set to an instance of an object error. And before the code runs it's saying the variable is being used before it's set, but I can't see how. Code:
Private taNotifications As dsDataTableAdapters.NotificationsTableAdapter = New dsDataTableAdapters.NotificationsTableAdapter
Dim notification As dsData.NotificationsDataTable = taNotifications.GetDataByClientID(userrow.UserID)
If notification.Rows.Count > 0 Then
Dim notificationrow As dsData.NotificationsRow
Dim forwardURL As String = notificationrow.ForwardLocation
End If
It falls over on the Dim forwardURL As String = notificationrow.ForwardLocation
The problem is that you have never instantiated the notificationRow inside the if statement. You've declared it, but it doesn't belong to anything. You need to make an assignment or loop through your rows before doing anything with this object:
Dim notificationrow As dsData.NotificationsRow ' this is not instantiated
Dim forwardURL As String = notificationrow.ForwardLocation
What you really want in this case is:
For Each notificationRow As dsData.NotificationRow In notification
Dim forwardURL As String = notificationRow.ForwardLocation
' Do Something
Next
If you only HAVE one row and you know you only have 1 or 0 rows then you could use your if statement by doing:
If notification.Rows.Count > 0 Then
Dim notificationrow As dsData.NotificationsRow = _
CType(notification.Rows(0), dsData.NotificationsRow)
Dim forwardURL As String = notificationrow.ForwardLocation
End If
Edit: In the code above, I originally just had notification.Rows(0). This will produce a DataRow object, but it will not be strongly typed. You need to perform the CType that I added in order to use the custom property ForwardLocation.
You never set notificationrow to anything. Did you mean to set it like this?
Dim notificationrow As dsData.NotificationsRow = CType(notification.Rows(0), dsData.NotificationsRow)
I have a dictionary of form data that I want to modify using a function.
function queryCleanForm(myDictForm)
dim arrayKeys
arrayKeys = myDictForm.keys
for i=0 to myDictForm.count-1
myDictForm(arrayKeys(i)) = replace(myDictForm(arrayKeys(i)), "'", "''")
response.write myDictForm(arrayKeys(i))
next
queryCleanForm = myDictForm
end function
The problem is the line queryCleanForm = myDictForm errors as
Wrong number of arguments or invalid property assignment
Is there a way to do this in VBScript?
Try this:
SET queryCleanForm = myDictForm
With objects you need to use SET to tell VBScript that it is an object reference you are assigning not a value type.
Yes, you need to use the SET command:
Set queryCleanForm = myDictForm
You can also use the ByRef or ByVal values in the function. ByVal, the object you sent to a function or sub is copied into private memmory to be used inside the function and discarded after the function is completed. ByRef, the object you sent to a function is referenced to and all manipulations you make, removing keys, setting the object etc, is directly done to the object you sent.
E.g.
Sub test
DIM testDict as variant
call setdict(testDict)
testDict.Add "test", "value"
call addValue(testDict, "test2","another value")
msgbox testDict.Count
Set testDict = Nothing
End Sub
Sub setdict(ByRef in_Dict as Variant)
If Typename(in_Dict) <> "Dictionary" Then
SET in_Dict = CreateObject("Scripting.Dictionary")
end if
end sub
sub addValue(ByRef in_Obj as Variant, ByVal in_Key as String, ByVal in_Value as String)
if not in_Obj.Exists(in_Key) then
in_Obj.Add in_Key, in_Value
end if
end sub
The test sub calls with a variable of type variant to the sub setdict. In the function I validate the type of the object sent to the sub. If the object type is not a dictionary object (which it is not) then the object in_Dict which is actually the testDict object declared in the sub test, will be set to a dictionary object.
To demonstrate the reference better I also included the second sub called addvalue. I pass the object to the function again as reference and add another key to the dictionary object. In the main test sub ill then post the count. In this case there are 2 keys present.
I have a business object that I've been using, which has a bunch of properties and a Save method, which inserts/updates to the database. The save method is NOT status, so the object needs to be instantiated, and the properties for the DB update/insert get pulled from the object.
Now I'm trying to bind the object to a FormView with the ObjectDataSource. I have it working so it instantiates based on the QueryString parameter, no problem, and populates the textboxes just fine. The UpdateMethod I have set to the Save function. Now it gets stuck.
It seems the ObjectDataSource needs a method with all the fields/properties/textboxes as parameters. I would have thought it would update the object's properties and then call the parameterless Save function. Is this wishful thinking?
Do I now need to change my Save function to include parameters, and change all the instances where it's getting used to this new method, just for this reason?
Thanks
Sean
Unfortunatly it does require params.
I overloaded my insert/update methods to include a few params. Attach the ObjectDataSource to the method with params.
The overloaded Update method calls the original Update method saving all the data. Seems kind of hackish to me, but it works.
Public Sub Update()
Dim isUpdated As Boolean = False
sql = "UPDATE AudioFiles SET Title = #Title, [desc] = #desc, Active = #Active WHERE fileID = #fileID"
conn = New SqlConnection(connString)
conn.Open()
...
End Sub
Public Sub Update(ByVal upFileID As Integer, ByVal upTitle As String, ByVal upDesc As String, ByVal upActive As Boolean)
Dim isUpdated As Boolean = False
Dim audioFile As New AudioFiles(fileID)
If Len(upTitle) > 0 Then
_title = title
End If
...
audioFile.Update()
End Sub