vb.net Mock an "iterative database" connection inside the managed code - vb.net

Problem: I am refactoring vb.net code to make it more unit test friendly. I have a vb.net function which has 2 data components. In the original code, both data models were implemented in the code which made it almost impossible to pass test data. I implemented an interface for the first data object which is now injected into the call to the function. This solves the issue of passing mock data to the function when testing. The second data component is executed iteratively inside the function.
Question - Is there a way to "mock" the iterative data retrieval while still maintaining the code functionality?
Basic Function - The data object injected at call time is used to pass parameters to the second database call within the code. This is repeated for each row in the injected DataTable object.
Function Signature: date and dailyScores are injected for Unit Tests or Run Time Execution
Public Shared Function CalcDailyScoresByDate(ByVal dayUtc As Date,
ByVal ReID As Integer,
ByVal dailyScores As DataTable
) As DailyDataCollection
Function Code:
Public Shared Function CalcDailyScoresByDate(ByVal dayUtc As Date,
ByVal ReID As Integer,
ByVal dailyScores As DataTable
) As DailyDataCollection
Dim timeUtc As Date = dayUtc
Dim srvSet As ServiceSettings = New ServiceSettings(dayUtc)
Dim ept As Date = TimeZoneInfo.ConvertTimeFromUtc(timeUtc, TimeZoneInfo.Local)
ept = ept.AddDays(1)
Dim effDayUtc As Date = TimeZoneInfo.ConvertTimeToUtc(ept)
Dim daily As New DailyDataCollection
Try
#Region "Region DataAccess"
Dim regDs = New DataAccess
Try
regDs.InitializeFromMDB(srvSet.MyConnectionString, "MyDb")
Catch ex As Exception
MyUtil.ErrorLog("RegDS.InitializeFromMDB " & ex.Message)
End Try
For Each rw In dailyScores.Rows
Dim drw As New DailyData
drw.ReID = rw(0)
drw.SignalID = rw(1)
drw.MyDay = effDayUtc
drw.dailyScore = rw(2)
' Calls the actual implementation to retrieve the supporting data for each record in the outer for loop
' Need to find a way to allow this to be Unit Tested with Mock data
Dim rsc = regDs.GetRegResource(drw.ReID, drw.SignalID, effDayUtc)
If rsc.Type = DataAccess.ReTypes.DG Then
For Each child In rsc.Children
Dim childDaily As New DailyData
childDaily.ReID = child.ID
childDaily.SignalID = child.SignalID
childDaily.myDay = effDayUtc
childDaily.dailyScore = drw.dailyScore
daily.Add(childDaily)
Next
End If
Next
dailyScores.Dispose()
dailyScores = Nothing
srvSet = Nothing
#End Region
Catch ex As Exception
MyUtil.ErrorLog("CalcDailyScoresByDate: " & ex.Message)
End Try
Return daily
End Function

Related

calling a VB.net Function for SQL recordset

Beginner here
I have the following code which I would like to call using a button called findCustomerBTN
Public Function Execute(ByVal sqlQuery As String) As ADODB.Recordset
If SecuritySSPIchkbx.Checked Then
chk = "TRUE"
Else chk = "FALSE"
End If
builder.DataSource = ServerBox.Text
builder.InitialCatalog = DatabaseBox.Text
builder.UserID = Username.Text
builder.Password = Password.Text
builder.IntegratedSecurity = chk
MessageBox.Show(builder.ConnectionString)
Using sqlConnection1 As New SqlConnection(builder.ConnectionString)
sqlConnection1.Open()
Try
command = New SqlCommand(sqlQuery, sqlConnection1)
adapter.SelectCommand = command
adapter.Fill(ds, "Create DataView")
adapter.Dispose()
command.Dispose()
sqlConnection1.Close()
dv = ds.Tables(0).DefaultView
DataGridView1.DataSource = dv
Catch ex As Exception
MsgBox(ex.ToString)
End Try
End Using
End Function
How do I call this function:
Private Sub findCustomerBTN_Click(sender As Object, e As EventArgs) Handles
sqlquery = "Select * from customers where name = 'Smith'"
call function?
End Sub
I have googled but I can't wrap my head around how a function works any pointers to help me understand would be great thanks
In VB, a function is a block of code that returns a value. Your code does not return a value, and the type of query execution you're carrying out will never return an ADODB.RecordSet - that's an ancient technology remeniscent of the VB6 era, and you're using a much more modern data access strategy (ADO.NET, DataTables and DataAdapter) though it's not the latest and greatest.
To offer a run through of your code, and the other issues it has:
Execute is a pretty bland name - go for something more specific, just incase you run the wrong execute and some poor prisoner ends up in front of the firing squad
Your function takes an sql string as a parameter to run, but then overwrites it with a fixed string, so there isn't much point offering it as a parameter in the first place. I could call Execute("SELECT * FROM kittens") expecting to get some cute data back, and all I get is the same old customer
Avoid calling MessageBox.Show in any code that shoudl reasonably be expected to run quietly and repeatedly, otherwise the user is going to get hella annoyed. If youre putting this here for debugging purposes, learn how the visual studio debugger works instead
Your code runs an sql query and assigns the resulting data table data to the datasource of a grid, so that the grid will show the data. There's absolutely no need for this code to be a function (and in c# it wouldnt even compile because it doesn't return a value
What are functions? What do they do? They take some inuts and return some outputs:
Public Function AddTheseTwo(a as Integer, b as Integer) As Integer
Return a + b
End Function
They are called like this:
Dim sum = AddTheseTwo(2, 3)
I.e. you give the name of the function and the input values, which can be variables, and store the result (usually, because you want to use it). Here's a code of yours that is a sub - a block of code that doesn't return a value
Private Sub findCustomerBTN_Click(sender As Object, e As EventArgs) Handles
Execute("Select * from customers where name = 'Smith'")
End Sub
It's not linked to your button click, because there's nothing afte rthe Handles keyword. It should be something like Handles findCustomerBTN.Click. You can mash that button all day and nothing will happen
It called Execute but didn't store the return value (because it didn't need to, because Execute doesn't return anything, so Execute should have been declared as a sub, not a function)
Edit:
You mentioned you want the function to return a datatable:
Public Function GetDataTable(ByVal sqlQuery As String) As DataTable 'need to Imports System.Data if you haven't already
If SecuritySSPIchkbx.Checked Then
chk = "TRUE"
Else chk = "FALSE"
End If
'better to declare builder in this function, not elsewhere
builder.DataSource = ServerBox.Text
builder.InitialCatalog = DatabaseBox.Text
builder.UserID = Username.Text
builder.Password = Password.Text
builder.IntegratedSecurity = chk
MessageBox.Show(builder.ConnectionString)
Using sqlConnection1 As New SqlConnection(builder.ConnectionString)
sqlConnection1.Open()
Try
'note: better to declare adapter and command in this function too
command = New SqlCommand(sqlQuery, sqlConnection1)
adapter.SelectCommand = command
Dim dt as New DataTable
adapter.Fill(dt)
adapter.Dispose()
command.Dispose()
sqlConnection1.Close()
Return dt
Catch ex As Exception
MsgBox(ex.ToString)
End Try
End Using
Return Nothing 'a function has to return something from all possible code paths, even if it's Nothing :)
End Function
And then you call it like this, perhaps:
Private Sub findCustomerBTN_Click(sender As Object, e As EventArgs) Handles whateverbutton.Click
'you can set a datatable as a datasource, doesn't have to be the datatable.defaultview
myDataGRidView.DataSource = GetDataTable("Select * from customers where name = 'Smith'")
End Sub
I recommend you turn on the options for Strict/Explicit etc, to encourage better coding practices. By default VB is quite loose, letting you use variables that havent been declared (autodeclaring variable names that are a typo of another variable name etc), automatically returning Nothing for you from functions etc - it's these little auto's that will later lead to bugs and frsutrations. Computer programming si a precise art; turn on all options to force yourself to be as precise as possible
You can call Execute function using this code:
Private Sub findCustomerBTN_Click(sender As Object, e As EventArgs) Handles
Execute("Select * from customers where name = 'Smith'")
End Sub
You also have to remove this line from Execute function:
sqlQuery = ("select * from ac_billbook where ref = '900123'")
Please follow Steve suggestion to read a good book about programming in VB.NET.

vb.net 3 tier architecture error message open connection

i work in vb.net project using 3 tier architecture
i have class BL include function name : show all customer and this is the code :`
Public Function ShowCustomer() As Customer()
Dim sql As String
Dim emps(-1) As Customer
Dim e1 As Customer
sql = "select Customer_name from Customer"
Dim dr As SqlDataReader = OBJECT_M.Exe_SQL(sql)
While dr.Read
e1 = New Customer
e1.Customer_Name = dr(0)
ReDim Preserve emps(UBound(emps) + 1)
emps(UBound(emps)) = e1
End While
Return emps
End Function
now in interface i need to handle all Customer name and write the name in the listview
my code:
Dim obj As New BL
Dim obj2 As New Customer
Dim x As Integer = 1
While x <> obj.ShowCustomer.Length
lb.Items.Add(obj.ShowCustomer(x).Customer_Name.ToString())
End While
but i have error message talk about this :
Additional information: The connection was not closed. The connection's current state is open.
please can Anybody save my day ?
For SqlConnection, SqlCommand, SqlDataReader, DataTable, DataSet (and any other disposable object) make sure to Dispose() of the object when you are done.
If you don't call Dispose() (or Close() in this case) connections will remain open and you will get memory and handle leaks.
The easist way is to use the Using keyword (which will also dispose in the case of an exception or other short circuiting event:
Using dr As SqlDataReader = OBJECT_M.Exe_SQL(sql)
While dr.Read
e1 = New Customer
e1.Customer_Name = dr(0)
ReDim Preserve emps(UBound(emps) + 1)
emps(UBound(emps)) = e1
End While
End Using
Make sure to do the same in the object I have detailed above in OBJECT_M.Exe_SQL, once you have called Dispose()/Close() on an oject you will not be able to use the instance of the object again and will have to create a new instance.
Refer to the documentation for more examples: https://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqldatareader(v=vs.110).aspx
Also while there is nothing wrong with using arrays, you may find it easire to use a List(of T) which you can later convert to an using ToArray().

Reflection Optimization

I've recently implemented reflection to replace the more tedious aspects of our data retrieval from a SQL database. The old code would look something like this:
_dr = _cmd.ExecuteReader (_dr is the SQLDataReader)
While _dr.Read (_row is a class object with public properties)
_row.Property1 = Convert.ToInt16(_dr("Prop1"))
_row.Property2 = Convert.ToInt16(_dr("Prop2"))
_row.Property3 = Convert.ToInt16(_dr("Prop3"))
If IsDBNull(_dr("Prop4")) = False Then _row.Prop4 = _dr("Prop4")
...
Since my code base has a lot of functionality like this, reflection seemed like a good bet to simplify it and make future coding easier. How to assign datareader data into generic List ( of T ) has a great answer that was practically like magic for my needs and easily translated into VB. For easy reference:
Public Shared Function GenericGet(Of T As {Class, New})(ByVal reader As SqlDataReader, ByVal typeString As String)
'Dim results As New List(Of T)()
Dim results As Object
If typeString = "List" Then
results = New List(Of T)()
End If
Dim type As Type = GetType(T)
Try
If reader.Read() Then
' at least one row: resolve the properties
Dim props As PropertyInfo() = New PropertyInfo(reader.FieldCount - 1) {}
For i As Integer = 0 To props.Length - 1
Dim prop = type.GetProperty(reader.GetName(i), BindingFlags.Instance Or BindingFlags.[Public])
If prop IsNot Nothing AndAlso prop.CanWrite Then
props(i) = prop
End If
Next
Do
Dim obj = New T()
For i As Integer = 0 To props.Length - 1
Dim prop = props(i)
If prop Is Nothing Then
Continue For
End If
' not mapped
Dim val As Object = If(reader.IsDBNull(i), Nothing, reader(i))
If val IsNot Nothing Then SetValue(obj, prop, val)
Next
If typeString = "List" Then
results.Add(obj)
Else
results = obj
End If
Loop While reader.Read()
End If
Catch ex As Exception
Helpers.LogMessage("Error: " + ex.Message + ". Stacktrace: " + ex.StackTrace)
End Try
Return results
End Function
The only caveat with this is that it is somewhat slower.
My question is how to optimize. Sample code I find online is all in C# and does not convert neatly into VB. Scenario 4 here seems like exactly what I want, but converting it to VB gives all kinds of errors (Using CodeFusion or converter.Telerik.com).
Has anyone done this in VB before? Or can anyone translate what's in that last link?
Any help's appreciated.
Couple ideas for you.
Don't use the DataReader when reading ALL records at once, it is slower than using a DataAdapter.
When you use the DataAdapter to fill a DataSet, you can iterate through the rows and columns which does NOT use reflection and will be much faster.
I have a program I created (and many other programmers do this too) that generate the code from a database for me. Each table and row is a class that is specifically named an generated in such a way that I can use intellisense and prevents many run-time errors by making them compile-time errors when data changes. This is very much like the EntityFramework but lighter because it fits MY specific needs.

Why is this object function exiting?

A very simple issue im having trying to return a dataset from a VB.NET object function.
The following shows my function that is currently exiting from the function as soon as the SQL query is run and just before the new object connection is created.
The edit form is called here:
edit.Show()
Within the edit form, the following is run to to retrieve the details of the selected data in the database fro a retrieved datatset of the 'editEntry' method based on the ID set at the constructor.
Private Sub edit_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
Dim editDetails As New DBHandler(ID)
Dim returnedDetails As New DataSet
returnedDetails = editDetails.editEntry()
Dim nameReturned As Object = returnedDetails.Tables("editedTable").Rows(0)(1)
Dim firstNameEdit As String = nameReturned.ToString()
TextBox1.Text = firstNameEdit
This is the function where the problem is occuring. Nothing is being returned from the query
Constructor where the ID is set:
Public Sub New(ByVal ID As Integer)
IDofFault = ID
End Sub
The function of the class:
Public Function editEntry() As DataSet
Dim editDataSet As New DataSet
Dim editSql As String = "SELECT * FROM duraGadget WHERE _id = " + IDofFault + ""
'Exiting from the function here
Dim connectionEdit As New OleDbConnection(conString)
Dim editAdapter As New OleDbDataAdapter(editSql, connectionEdit)
connectionEdit.Open()
editAdapter.Fill(editDataSet, "editedTable")
connectionEdit.Close()
Return editDataSet
End Function
There is no error it simply exits from the function and im not sure why.
You could be receiving an exception and your visual studio debug settings are not configured to stop you on those types of exceptions.
Wrap the contents of the EditEntry function in a Try / Catch block, and put a break point inside the catch. See if that triggers and look at the exception details for more info on what occurred.
Very silly error this was guys. I simply stored the ID value as a sting...then tried to pass it as an Integer to the constructor...the result?.....Conversion exception.

Strange error reported in this code, please explain?

I put together this bit of code from a few other samples, and I am getting an error I cant understand. On this line in the code below, on the word Observer,
Dim Results As ManagementObjectCollection = Worker.Get(Observer)
I get the error
"Value of type 'System.Management.ManagementOperationObserver' cannot be converted to 'Integer'"
Can somebody explain what this means?
There are two signatures for ManagementObjectSearcher.Get(), one has no parameters and the other has one parameter, a ManagementOperationObserver for async operation. That is what I am providing, yet the error indicates conversion involving an integer?
Public Shared Sub WMIDriveDetectionASYNC(ByVal args As String())
Dim Observer As New ManagementOperationObserver()
Dim completionHandler As New MyHandler()
AddHandler Observer.Completed, AddressOf completionHandler.Done
Dim Machine = "192.168.0.15"
Dim Scope = New ManagementScope("\\" & Machine & "\root\cimv2")
Dim QueryString = "select Name, Size, FreeSpace from Win32_LogicalDisk where DriveType=3"
Dim Query = New ObjectQuery(QueryString)
Dim Worker = New ManagementObjectSearcher(Scope, Query)
Dim Results As ManagementObjectCollection = Worker.Get(Observer) 'use parameter to make async
For Each item As ManagementObject In Results
Console.WriteLine("{0} {2} {1}", item("Name"), item("FreeSpace"), item("Size"))
Dim FullSpace As Long = (CLng(item("Size")) - CLng(item("FreeSpace"))) \ 1000000
Console.WriteLine(FullSpace)
Next
End Sub
Public Class MyHandler
Private _isComplete As Boolean = False
Public Sub Done(sender As Object, e As CompletedEventArgs)
_isComplete = True
End Sub 'Done
Public ReadOnly Property IsComplete() As Boolean
Get
Return _isComplete
End Get
End Property
End Class
Thanks for any advice!
I think that uses a reference type to get the result and put it in the object you sent as a parameter. So I think it just needs to look like:
Worker.Get(Observer)
instead of trying to set something = to that since it isn't a function that returns a value.
Then use the events you hook up to the object to handle whatever you need to do with the items you find.