Does this function release resources properly? - database

I'm trying to make sure that I don't leave any loose ends open in my application and am concerned about a few but might get my answer from this one. I've "overriden" some functions so that way I can try and keep all the resources as clean and free as possible. So in this instance, I have a function called ExecuteReader which returns a DbDataReader as normal, but all I had to pass to it was a SQL string rather than recreating a DbCommand every time. I want to make sure that even though I'm unable to call dbCommand.Dispose() that it is actually doing so. Any and all help is appreciated.
Public Function ExecuteReader(ByVal strSQL As String) As DbDataReader
Dim dbCommand = _dbConnection.CreateCommand()
dbCommand.CommandText = strSQL
dbCommand.Prepare()
Return dbCommand.ExecuteReader()
End Function
I was thinking of using a using statement, but I remember seeing a thread where someone said they think it was causing them problems having a return in the using statement. Also, I'm not sure if this should be community wiki or not. If it should be, let me know. Thanks.
Code Update:
Here's an example of how I'm using it.
Public Sub RevertDatabase()
'This function can be used whenever all changes need to be undone, but was created'
'for saving the data as a .out file. It sets all changes back to their original value.'
'Set the data reader to all parts and columns that were changed.'
_dbReader = ExecuteReader("SELECT PART_ID, PART_PREV_VALUE, REPORT_COLUMN_NAME FROM REPORTS WHERE PART_PREV_VALUE NOT NULL")
'Create an instance of the Command class.'
Dim cmd = New Command()
While _dbReader.Read()
'For each part and columns that has been changed, set the values in the'
'new cmd variable and then update the value using the same function'
'that is used whenever a value is changed in the data grid view.'
cmd.CommandString = _dbReader("REPORT_COLUMN_NAME").ToString().Replace("_", " ")
cmd.Value = _dbReader("PART_PREV_VALUE").ToString()
cmd.ID = _dbReader("PART_ID").ToString()
UpdateValue(cmd)
End While
'Close the reader.'
_dbReader.Close()
End Sub
In here, I set the _dbReader to what I'd get from the function and eventually I close the _dbReader. I do not close the connection as I don't open it each time I make a query. This is a SQLite database that only one user will be using at a time (small application with very very very little likeliness it will grow) so I didn't think it necessary to close and open the connection all the time. Maybe I'm wrong, not sure. Using it this way though, it is potentially ok for cleaning resources?

It's a bad idea passing back DbDataReader as this requires the stream to be kept open and relies on calling code to do the right thing by disposing the reader. It's also makes it difficult to close the underlying command and connection objects. One way to facilitate this if you really want to expose the reader, is to use CommandBehavour. This can be used to close the underlying connection when the reader itself is closed.
dbCommand.ExecuteReader(CommandBehavour.CloseConnection)
As DbConnection, DbCommand and DbDataReader are Disposable you need to refactor the code to allow for these to be cleaned up when the code has finished with them. One way is to achieve this is to implement IDisposable within your own class and bubble dispose though to any encapsulated objects. The calling code can then implement using to ensure resources are freed.
Using helper As New DatabaseHelper()
Using reader As IDataReader = helper.LoadSomeDataReader()
' do something with reader
End Using
End Using
UPDATE:
Your second block of code would become more like:
Public Sub RevertDatabase()
Using _dbReader As IDataReader = ExecuteReader(...)
While _dbReader.Read()
Using cmd As New Command()
cmd.CommandString = _dbReader("REPORT_COLUMN_NAME").ToString().Replace("_", " ")
cmd.Value = _dbReader("PART_PREV_VALUE").ToString()
cmd.ID = _dbReader("PART_ID").ToString()
UpdateValue(cmd)
End Using
End While
End Using
End Sub
You should always open and close the connection, rather then just leaving it open. It's could practice and although highly unlikely in a very small application, you could run into issues. You also shouldn't be keeping a reference to the _dbReader IMO.

Returning an IDisposable instance from a function is perfectly fine. The ownership of the instance is being transferred from the function to the caller and it is now the caller's responsibility to call Dispose. This is the exact same principal in play when calling IDbCommand.ExecuteReader.
You should definitely be using the Using statement. It makes the code more readable since it automatically inserts the correct try-finally block that calls the Dispose method. I do not know the details of the issue you were referring to, but you should not have any problems yourself since you are not doing anything out of the ordinary.
Public Sub RevertDatabase()
Using dbReader As DbDataReader = ExecuteReader(...)
// Use the data reader here.
End Using
End Sub

Related

VB.NET ADODB MS SQL Server Open Recordset

After I open a SqlConnection, I try to open a recordset like I did it thousands times before in VBA. Yes, I know that there are differences between VBA and VB.NET, but maybe it's to simple to see it.
Public Class Form1
Const m_cstrCnnString As String = "MyCorrectCnnString"
Dim cnn As SqlConnection
Dim rcs As New ADODB.Recordset
Private Sub MyConnection()
cnn = New SqlConnection(m_cstrCnnString)
cnn.Open()
'so far it works, my cnn.State is 1
rcs.Open("SELECT * FROM dbo.myTable", cnn) 'this line doesn´t work
End Sub
I tried the rcs.Open with and without CursorTypeEnum, LockTypeEnum, but I always get the same error:
System.Runtime.InteropServices.COMException: The arguments are of the wrong type, are out of scope, or are inconsistent with each other.
Background: Win10, Connection to a MS SQL Server Express by VB.NET.
First I import System.Data.SqlClient
Microsoft ActiveX Data Objects 6.1 Library is activated.
Also I tried it with Microsoft ActiveX Data Objects Recordset 6.0 Library.
And I'm new in VB.NET
There's some high-level stuff I need to cover before we can get into the details of what this code should look like.
First, if you're using VB.Net, you should not be opening a RecordSet object. Recordset is from ADO, which is not the same as ADO.Net. You definitely can't use an ADO.Net SqlConnection to open a classic ADO RecordSet. Again: these are two completely different libraries. The classic library exists in .Net only for backwards compatibility and to aid in porting forward old code. It should not be used for new development.
Additionally, when you move forward to ADO.Net you need to be aware of a feature called Connection Pooling. This feature takes care of caching connection objects for you, such that it's counter-productive (uses more RAM and makes things slower) to try to keep a single connection option ready for use throughout the application. Instead, it really is better to create a new connection object for most queries.
Finally, classic ADO had a problem with properly closing resources, such that some of those older applications could occasionally even lock out the database. .Net provides a way to handle this such that you are sure everything is closed and disposed properly and promptly: a Using block.
The weakness of this mechanism is DataReader objects (which replace RecordSet) need the connection to remain open for the life of the reader. This makes it hard to build a good data abstraction to hide all the boiler plate code that goes with data access.
The best pattern I've seen for offering the best of both worlds requires some moderate and advanced VB.Net language features (lambda methods, iterator blocks, and IEnumerable) that are unfamiliar to many traditional VB coders. It's going to require me to go a lot deeper than normal in this answer. That said, once you wrap your head around it, the result is really nice.
To show what this might look like, I need an example that's a little more concrete. We'll pretend you have an Employee table with columns for ID, FirstName, and LastName, and a class Employee with properties of the same name:
Public Class Employee
Public Property ID As Integer
Public Property FirstName As String
Public Property LastName As String
Public Shared Function FromDataRecord(record As IDataRecord) As Employee
Return New Employee() With {
ID = record("ID"),
FirstName = record("FirstName"),
LastName = record("LastName")
}
End Function
End Class
That's right: VB sometimes uses braces now. The Shared method at the end is optional, but it will help make some of what comes next easier to follow. Here is what modern ADO.Net code might look like. Pay special attention to the use of Private and Public
Public Module DB
Private Shared Property ConnectionString As String = "MyCorrectCnnString"
Private Iterator Function GetData(Of T)(SQL As String, transform As Func(Of IDataRecord, T), addParameters As Action(Of SqlParameterCollection)) As IEnumerable(Of T)
Using cn As New SqlConnection(ConnectionString)
Using cmd As New SqlCommand(SQL, cn)
If addParameters IsNot Nothing Then addParameters(cmd.Parameters)
cn.Open()
Using rdr As SqlDataReader = cmd.ExecuteReader()
While rdr.Read()
Yield transform(rdr)
End While
End Using
End Using
End Using
End Function
Public Function GetEmployees() As IEnumerable(Of Employee)
Dim SQL As String = "SELECT * FROM dbo.Employee"
Return GetData(SQL, Employee.FromDataRecord, Nothing)
End Function
Public Function GetEmployeeById(ID As Integer) As Employee
Dim SQL As String = "SELECT * FROM dbo.Employee WHERE ID= #ID"
Return GetData(SQL, Employee.FromDataRecord,
Sub(pc) pc.Add("#ID", SqlDbType.Integer).Value = ID
).FirstOrDefault()
End Function
Public Function GetEmployeesByLastName(LastName As String) As IEnumerable(Of Employee)
Dim SQL As String = "SELECT * FROM dbo.Employee WHERE LastName= #LastName"
Return GetData(SQL, Employee.FromDataRecord,
Sub(pc) pc.Add("#LastName", SqlDbType.NVarChar, 25).Value = LastName
)
End Function
End Module
Now the main part of the application NEVER deals in SQL directly, or even has to know there's a relational database involved. It's just calling methods on a regular module, such that the code in your form for reading data looks more like this:
Dim employees = DB.GetEmployeesByLastName(TextBox1.Text)
DataGridView1.DataSource = employees
You make similar methods in the new Module for writing INSERT/UPDATE/DELETE queries to save changes. These methods will end up calling the cmd.ExecuteNonQuery() method, but they should NEVER accept an SQL statement string as input.
As an application grows you might have a number of classes with FromDataRecord() methods, such that you instead refactor these methods to their own Module. You might also end up with enough Public methods in the DB Module to divide them up into a number of related Modules. At this point, these Modules would all go into a separate class library project (using Friend in some places instead of Private).

Why is my vb code not updating the ms access database (only cached temporarly)

I am doing a small system using Ms. Access (the database has more than 10 tables ) connecting to visual studio. I made a public class for opening the connection to the database so I can use it in every form. Everything is working and I can get the data from the database But any inserting or deleting data in forms, the database in ms access not getting the update. I can see the new records in forms but nothing in the database.
Imports System.Data.OleDb
Public Class dbconnectClass1
'create db connection
Private DBcon As New OleDbConnection("Provider=Microsoft.ACE.OLEDB.12.0; Data Source=dental_clinic.accdb;")
'prepare db command
Private dbcmd As OleDbCommand
' db data
Public DBDA As OleDbDataAdapter
Public DBDT As DataTable
'query parameters
Public params As New List(Of OleDbParameter)
'query statics
Public recordcount As Integer
Public Exception As String
Public Sub ExecQuery(query As String)
'reset query status
recordcount = 0
Exception = ""
Try
'open connection
DBcon.Open()
'create db command
dbcmd = New OleDbCommand(query, DBcon)
'load params into dbcommand
params.ForEach(Sub(p) dbcmd.Parameters.Add(p))
'clear params list
params.Clear()
'excute command and fill dataset
DBDT = New DataTable
DBDA = New OleDbDataAdapter(dbcmd)
recordcount = DBDA.Fill(DBDT)
Catch ex As Exception
Exception = ex.Message
End Try
'close the database connection
If DBcon.State = ConnectionState.Open Then DBcon.Close()
End Sub
'include query and command parameters
Public Sub addparam(name As String, value As Object)
Dim newparam As New OleDbParameter(name, value)
params.Add(newparam)
End Sub
End Class
This my code inside the forms:
Public Class NewExpense
Private access As New dbconnectClass1
' a varuble having the appointment Id to connect between 2 forms
Private appointmentNo As Integer
Private Function NoError(Optional report As Boolean = False) As Boolean
If Not String.IsNullOrEmpty(access.Exception) Then
If report = True Then MsgBox(access.Exception)
Return False
Else
Return True
End If
End Function
Private Sub Savebuttum_Click(sender As Object, e As EventArgs) Handles
Savebuttum.Click
Dim oDate As DateTime = Convert.ToDateTime(DateTimePicker1.Value)
access.addparam("#expensenme", expensenmtXT.Text)
access.addparam("#expensedetail", ExpenseDetailTXT.Text)
access.addparam("#expenseamount", ExpenseAmountTXT.Text)
access.addparam("#expensedate", oDate)
access.addparam("#expensepaidTo", paidtoTXT.Text)
access.ExecQuery("INSERT INTO Expense (Expenses_name, expense_details,
expenses_amount, ExpenseDate_Paid, ExpensePaid_To) Values (#expensenme,
#expensedetail, #expenseamount, #expensedate, #expensepaidTo);")
'report on errors
If Not String.IsNullOrEmpty(access.Exception) Then
MsgBox(access.Exception) : Exit Sub
'success
access.DBDA.Update(access.DBDT)
MsgBox("Expense Has been Added Successfully")
End Sub
End Class
Hum, you have this:
params.ForEach(Sub(p) dbcmd.Parameters.Add(p))
Great, we add the parmaters - looks good to go!!!
then, next line CLEARS all the work above!!! (the parameters are removed!!!!)
'clear params list
params.Clear()
Next up? Many will build a connection object, then a reader, and then a adaptor. But you ONLY need a data adaptor if you going to update a data table. if you just going to execute a command, then you don't need the data table, and you don't need a adaptor FOR that table. Adaptor = ability to modify a existing datatable (or dataset).
You are MUCH better to use the command object.
Why?
Because the command object has a connection object (don't need a separate one)
Because the command object has a data reader for you (no need for a whole data adaptor to JUST fill a table. And remember, you don't need a whole data adaptor UNLESS you are going to send/update a data table back to the database.
And because the command object has the command text, then you don't even need a variable for that!!!
And because all objects are in "one object", then really all you need is something to handy get you the connection.
So, for your insert example, we really don't gain by having that object, do we?
Ok, so here is your insert code without using those extra objects:
so in the following, I declare ONE variable, - the sql command object.
And do the insert
And as FYI? Your save button is not a save - but a insert button - every time you hit it, you will insert a new row. Lets deal with that issue in a bit.
So, here is our code:
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Using cmdSQL As New OleDbCommand("INSERT INTO Expense
(Expenses_name, expense_details,expenses_amount, ExpenseDate_Paid, ExpensePaid_To)
Values (#expensenme,#expensedetail, #expenseamount, #expensedate, #expensepaidTo)",
New OleDbConnection(My.Settings.TESTOLEDB))
With cmdSQL.Parameters
.Add("#expensenme", OleDbType.WChar).Value = expensenmtXT.Text
.Add("#expensedetail", OleDbType.WChar).Value = ExpenseDetailTXT.Text
.Add("#expenseamount", OleDbType.Currency).Value = ExpenseAmountTXT.Text
.Add("#expensedate", OleDbType.DBDate).Value = DateTimePicker1.Value
.Add("#expensepaidTo", OleDbType.WChar).Value = paidtoTXT.Text
End With
cmdSQL.Connection.Open()
cmdSQL.ExecuteNonQuery()
End Using
So things in above:
We have strong data typing and converstion.
Because of this, note how I did NOT have to create a separate date/time variable here.
Note the SAME for "money" or so called currency conversion - again strong data type by using parameters this way.
And is this a date only, or a date+ time value? If it is date, then
.Add("#expensedate", OleDbType.DBDate).Value = DateTimePicker1.Value
If it was/is a date + time, then this:
.Add("#expensedate", OleDbType.DBTimeStamp).Value = DateTimePicker1.Value
So, notice how all your object stuff REALLY did not help one bit, and in fact you did not really save code, and above actually had LESS variables defined to do the whole job.
Now, back to the insert issue/problem. (you save is doing a insert). But what about editing existing records?
So, I would suggest you work out the problem this way:
You create/get/have/assume a data row for the form.
The form takes the data row, fills the controls. You edit, and when you hit save, the data row is sent back to the datbase. So, once this works, then to add? Well, the add code will CREATE a new data row, save to database and THEN you send that new data row to the above eixsting form that can edit a data row, and can save a data row. So the form now is able to deal with both issues (adding vs editing existing). If the user dont' want the row, then you offer a delete button.
And REALLY nice is a data row means you don't deal with SQL, and don't deal with parmaters!!
So the code (desing pattern) I use is thus this:
dim da as oledbDataAdaptor
dim myTable as DataTable = MyRstEdit("SELECT * from tblHotels WHERE ID = " & lngID,da)
dim MyDataRow as DataRow = myTable.Rows(0)
' code to fill controls
txtHotelName.Text = MyDataRow("HotelName")
txtCity.Text = MyDataRow("City")
' etc. etc. etc.
Now to save? Well I put the values back into that DataRow like this:
MyDataRow("HotelName") = txtHotelName.Text
MyDataRow("City") = txtCity.Text
MyDateRow("BookingDate") = txtTimePick1.Value
da.Update(MyTable)
Notice how I don't have parameters, and even strong data type checking occurs for say the above Date/Time booking date column.
And the above is nice, since I don't have to deal with ANY parmaters to udpate a row of data.
The MyRstEdit routine looks like this and returns byREf a "da" (data adaptor).
Public Function MyrstEdit(strSQL As String, Optional strCon As String = "", Optional ByRef oReader As SqlDataAdapter = Nothing) As DataTable
' Myrstc.Rows(0)
' this also allows one to pass custom connection string - if not passed, then default
' same as MyRst, but allows one to "edit" the reocrdset, and add to reocrdset and then commit the update.
If strCon = "" Then
strCon = GetConstr()
End If
Dim mycon As New SqlConnection(strCon)
oReader = New SqlDataAdapter(strSQL, mycon)
Dim rstData As New DataTable
Dim cmdBuilder = New SqlCommandBuilder(oReader)
Try
oReader.Fill(rstData)
oReader.AcceptChangesDuringUpdate = True
Catch
End Try
Return rstData
End Function
So, now in vb.net, I actually find it is LESS code then even writing + using recordsets in MS-Access VBA code.
However, BEFORE you go down ANY of the above road?
Have you considered using the vb.net data binding features. Data-binding in vb.net means that you do NOT write ANY of the above code. it means that vb.net will do all of the dirty work, and write and setup ALL OF the code for you to edit data on a form. The end result is you don't write any code to update a table.
You do have to use + create a "data set". Once done, then you just drag controls onto the form, and you even get this. So you just drop in a dataset, table adaptor, Binding navagator, and you get this:
Note now the tool bar at the top (and you can place it on teh bottom if you wish). So you get this:
So that WHOLE form was created without having to write ONE line of code. And you can see we have navigation, edits and saves and even the ability to add. So, you can build up a editing form - and it thus becomes similar to say working in MS-Access and ZERO lines of code is required to build the above form.
However, if you ARE going to roll your own code? Then use a data row. That way you can shuffle data to/from the table, and NOT have to use parmaters and SQL update and insert statements - but ONLY have nice clean code in which you shove, or get values from that data row. .net will "write" all the update stuff for you.

How to Unit Test private methods that update a database, without actually updating the database - in a WinForms VB.Net legacy code app?

I've started working on a legacy code (i.e. no unit tests) Windows Forms app written in VB.Net and I've been asked to start introducing unit tests as I make changes. I have not done much unit testing before and I am unsure how to test a recurring pattern I see in the methods. Here is my attempt to outline a simplified typical example:
Private Sub cmdButton_Click(ByVal eventSender As Object, ByVal eventArgs As EventArgs) Handles cmdAprv.Click
'The event is Private and it then calls multiple other private methods
'but I will simplify drastically for this example:
If <conditions> Then
InsertEmployee()
End If
End Sub
Private Function InsertEmployee() As Integer
'Typically, the functions are quite long with lot of code initializing
'variables, but I will simplify drastically for this example:
Dim _name As String = "[]"
Dim _departmenmt As String = "Engineering"
Dim _salary As Double = 2000
'Then at the end there may be multiple database updates like this one:
Dim connectionString As String = "<Call a Function to get ConnectionString details>"
Using conn As New SqlConnection(connectionString)
Using cmd As New SqlCommand("INSERT INTO Employees (Name, Department, Salary) VALUES (#Name, #Department, #Salary)", conn)
cmd.CommandType = CommandType.Text
cmd.Parameters.AddWithValue("#Name", _name)
cmd.Parameters.AddWithValue("#City", _departmenmt)
cmd.Parameters.AddWithValue("#City", _salary)
conn.Open()
Dim i As Integer = cmd.ExecuteNonQuery()
conn.Close()
Return i
End Using
End Using
End Function
There is a lot of code in Private methods like this. To make these methods accessible to the testing project, I reckon I can downgrade the Function's accessibility protection from Private to Friend (VB.Net “Friend” = C# “internal” access modifier) and then use InternalsVisibleTo Attribute to mark the unit testing assembly as a friend assembly.
But I’m really not sure what to do with the Database INSERT or UPDATE code to make the function testable? One requirement I have is that the Unit Tests must not actually modify the database.
Can anyone show me a good example of refactoring a method like this for unit testing?
This is of course opinion-based. But here is what I do with legacy un-testable methods.
In your tested project, as you said, introduce InternalsVisibleTo
In your tested project introduce unit test - specific methods that will be only available for DEBUG configuration
#If DEBUG Then
Friend Function InsertEmployee_UT() As Integer
' call your method here
End Function
#End If
This is the principle. In fact, wrap your InternalVisibleTo with #If DEBUG Then
Build and perform your unit tests only in DEBUG configuration, and call the UT-functions that will not exist in RELEASE/Production code
Now Lets talk about breaking from the database. While calling DB can be a different sort of test, like Functional Test, you're right. Preferably, you can run your unit test without DB connection. What to do
Separate db calls into a single provider and pass Interface into the method. Yes, you need slight method change
Friend Function InsertEmployee(provider As IEmployeeData) As Integer
With this, in your unit test you can mock your provider and then examine. You can actually set this provider during construction or a property, then you don't even need to pass it to each method.
You can pass a callback As Function(Of . . ) or callback As Action(Of . . )
This way you also can separate functionality of retrieving data and calling for it.
These changes are not very intensive and can be done relatively quickly. And you will be able to do your testing. Changing private methods is safe, you can do it in any form or shape.

Find out which control caused ValidateChildren() to fail

When I call myControl.ValidateChildren() it returns false. How can I find out, which of the children failed to validate?
(I'm working with WinForms for the first time and am confronted with a huge pile of messy legacy code.)
The only way to do that seems to be to debug through the Validating event handlers. (See Hans' comment on the question.)
With some Reflection it is possible to call the same internal functions that .net calls inside ValidateChildren.
This allows to retrieve the validation result of each control and find the one that causes the problem.
I wrote this small function for simple use:
Public Function GetFirstInvalidControl(container As ContainerControl) As Control
Dim getStyle = GetType(Control).GetMethod("GetStyle", Reflection.BindingFlags.NonPublic Or Reflection.BindingFlags.Instance)
Dim isInvalid = GetType(Control).GetMethod("PerformControlValidation", Reflection.BindingFlags.NonPublic Or Reflection.BindingFlags.Instance)
Dim parents = New Queue(Of Control)
parents.Enqueue(container)
While parents.Any
For Each control As Control In parents.Dequeue.Controls
If CBool(getStyle.Invoke(control, {ControlStyles.Selectable})) AndAlso CBool(isInvalid.Invoke(control, {True})) Then
Return control
End If
parents.Enqueue(control)
Next
End While
Return Nothing
End Function

How to determine if a database has been altered for Access, SQL, Oracle or File Systems

At the company I work for all applications pull information from that database, I have decided to write a detailed answer to answer how different databases can let the user know they have been altered. I will answer for the following types:
Access
SQL
Oracle
File systems (Files and folders)
Why I have done this?...... The company I work for have many different databases and applications that use these databases. However the applications spend a lot of time within the database checking to see if the data has been changed. I have complied this list to show how certain databases/files can use different tools to let an application know it has been changed. So an event can be fired off. This will hopefully reduce computing power and speed up the applications.
Please edit as you seem fit. If you need any other information a comment would be great. I am still in the process of adding the Oracle database solution and editing the Access and SQL.
Access and FileSystems/Files
For the access point I have used a SystemFileWatcher. This keeps an eye on the database and if it has been modified it will run the code to get the new data from the database. This means that the application is not constantly going in to the database and grabbing new data when it is not needed.
The FileSystemWatcher can run different code from the events such as name changed, moved or modified. I only need to use the modified. I have got the database path from an XML File I am using which means it is not hard coded and can be changed from the xml file and the watcher will watch else where.
Protected Overrides Sub OnStart(ByVal args() As String)
Dim g1 As New FileSystemWatcher()
g1.Path = GetSingleNode(XmlFileName, "data/G1Path")
g1.NotifyFilter = (NotifyFilters.LastAccess Or NotifyFilters.LastWrite Or NotifyFilters.FileName Or NotifyFilters.DirectoryName)
g1.Filter = GetSingleNode(XmlFileName, "data/G1Filter")
AddHandler g1.Changed, AddressOf OnChanged
g1.EnableRaisingEvents = True
Dim g2 As New FileSystemWatcher()
g2.Path = GetSingleNode(XmlFileName, "data/G2Path")
g2.NotifyFilter = (NotifyFilters.LastAccess Or NotifyFilters.LastWrite Or NotifyFilters.FileName Or NotifyFilters.DirectoryName)
g2.Filter = GetSingleNode(XmlFileName, "data/G2Filter")
AddHandler g2.Changed, AddressOf OnChanged
g2.EnableRaisingEvents = True
End Sub
Protected Overrides Sub OnStop()
End Sub
Public Shared Function GetSingleNode(ByVal xmlPath As String, ByVal nodeLocation As String) As String
Try
Dim xpathDoc As New XPathDocument(xmlPath) 'gets the nodes from the XML file ready to be put in to the network path/variables
Dim xmlNav As XPathNavigator = xpathDoc.CreateNavigator()
Return xmlNav.SelectSingleNode(nodeLocation).Value
Catch ex As Exception
Throw
End Try
End Function
After this I simply have an on changed function. Hope this helps anyone that needs it.
FileSystems/Files
For the file path and system paths the code above is very similar just using different paths and filters to get the certain types or names of files. This will then run the code if these have been changed/modified. If anybody would like code for this please write a comment and I can supply some.
SQL Databases
In the SQL databases there are multiple ways of checking to see if the data has been changed. I will reference a few MSDN Pages along with another question to provide information to these. However the way I used was slightly different as I didn't have a service broker running and no queues were enabled on my SQL databases.
Is there something like the FileSystemWatcher for Sql Server Tables?
http://msdn.microsoft.com/en-us/library/62xk7953.aspx#Y342
However the way I used was by using a checksum and a timer and checking the checksum on a loop to see if the database was changed. As the 'hash' always changes if the data is changed:
http://sqlserverplanet.com/design/how-to-detect-table-changes
http://www.mssqltips.com/sqlservertip/1023/checksum-functions-in-sql-server-2005/
My Code:
'Within main to get the first checksum value ready to be comapred with at a later date. These are global variables
Dim newdata As DataTable = SQLMethods.ExecuteReader(ConnectionString1, "SELECT CHECKSUM_AGG(BINARY_CHECKSUM(*)) FROM Alarms")
checksum = newdata.Rows(0).Item(0)
Timer1.Start()
Private Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer1.Tick
Timer1.Stop()
Dim newdata As DataTable = SQLMethods.ExecuteReader(ConnectionString1, "SELECT CHECKSUM_AGG(BINARY_CHECKSUM(*)) FROM Alarms")
checksumNew = newdata.Rows(0).Item(0)
If checksum <> checksumNew Then
MsgBox("Hello")
checksum = checksumNew
End If
Timer1.Start()
End Sub
As you can see if they do match the checksum is changed to match so the next time it happens they will be the same unless the database is indeed changed. I have stopped and then restarted the time to avoid confusion of the message box, however the message box is used for debugging purposes as an event could be fired here or what ever code was wanting to happen if the database was changed.
Oracle
After doing research I have not been able to implement this solution in my own application but hopefully it will provide info to other users. In Oracle there is something called OracleDependencyClass which provides an application a notification if the chosen data has been modified. I will supply some hyper-links that have some examples and the basics of how to use these in the hope someone doesn't need to mirror my own research.
Developing Applications with Database Change Notification
OracleDependency Class
Oracle® Data Provider for .NET Developer's Guide - OracleDependency Class(2)
Example of using the class in C# and VB.NET
If these pages don't help there are plenty of other webpages that you can access if you either search for "oracle dependency", "OracleDependency Class" and "Database Change Notification".

Resources