Using Parameterized Queries and Reports in VB.NET Database Applications

Introduction

Hello again! Today I will talk about parameterized queries and the reporting options you have available in your VB.NET applications.

Queries

In case you don’t know what a query is, let me explain : A query is used to retrieve data from a database table, or to do a certain action, such as inserting and deleting of records. More information on Queries can be found in an earlier article of mine entitled Doing Data Extraction with VB.NET.

Parameterized Queries

Well, parameterized Queries are basically the same as an ordinary query, but, they allow you to make use of parameters inside your queries. OK, so what is a parameter? A parameter is additional information you can supply to an SQL Query. This information  usually works in conjunction with a condition inside the SQL query. We all know by now (or should) that with a Condition, you can narrow your search down better.

A simple example of an SQL Query containing a parameter looks like this:

"Select * From StudentInfo where StudentName = @Name"

In this case, Name is a parameter. This comes in handy, because now we do not need to hardcode the values we wanted inside our query’s condition, as well as we solve the risk of SQL Injection. To supply a value to the parameter, you can use the following VB.NET code:

	'Create Parameter Instead Of Hardcoding Values
	'Name = Whatever txtSearch Contains
	oleAccCommand.Parameters.AddWithValue("Name", txtSearch.Text)

Here, I add a parameter to a Command object (which I will explain later in more detail) called Name and supply the value of whatever text has been entered into the txtSearch TextBox. The user is now able to type any value inside the txtSearch textbox, and it will be added to the final query that will run on the database. Another way to add a parameter looks like this:

        command.Parameters.Add("@StudentNumber", SqlDbType.Int)
        command.Parameters("@StudentNumber").Value = txtStudentNumber.Text

Here, you create the parameter and set its data type. On the next line, you give it a value. As always, there are numerous ways to skin a cat, whichever one you choose depends on the project at hand, performance, as well as, I suppose, your personal preference.

Our Project

To get a better understanding of how parameters work, I decided to let you create a little app utilizing parameters in all your queries. For our project, we will make use of Visual Basic 2012, so open it, and start a new Windows Forms Project. With this article, I will demonstrate how to use Parameters within your queries to a Microsoft Access database, as well as an SQL Server database. It is your choice if you want to do both, as I don’t know your needs, hence the reason why I will demonstrate both.

Design your Forms to resemble Figure 1 and Figure 2 respectively.

Form 1 - Access Database
Figure 1 – Form 1 – Access Database

Form 2 - SQL Server Database
Figure 2 – Form 2 – SQL Server Database

You can name the objects anything you like, but keep in mind that I may have named my objects differently.

You do not need to concern yourself with setting up database connections now, as you will do it all in code. For interest sake, you may want to have a look at the article entitled Exploring theDatae Controls in Visual Basic.NET I wrote earlier. There I explained how to set up design time database connections. Just a further note: The sample databases I am making use of today has also been created with the said article. Just in case you don’t want to read the Data Controls article, you should create a Table named StudentInfo inside a database named Students, and then create these three fields:

  1. StudentName, which has a Text data type.
  2. StudentSurname, which has a Text data type.
  3. StudentNumber, which has numeric data type.

Navigate to your desired Form’s code window and add the following Imports statement to import the Namespace equipped to handle Access or SQL Server databases:

Access

Imports System.Data.OleDb 'Import Access Db Handling Capabilities

SQL Server

Imports System.Data.SqlClient 'Import SQL Server Db Handling Capabilities

Add the following modular variables, which we will use in our entire form(s):

Access

	'Access Database Connection String
	Dim strAccConn As String = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=C:\Users\HannesTheGreat\Documents\Students.accdb;Persist Security Info=False;"
	'Object To Use As SQL Query
	Dim strAccQuery As String

	'Access Database Connection
	Dim oleAccCon As OleDbConnection

SQL Server

	'SQL Server Database Connection String
	Dim strSQLConn As String = "Data Source=HANNES;Initial Catalog=Students;Integrated Security=True"
	'Object To Use As SQL Query
	Dim strQuery As String
	'SQL Server Database Connection
	Dim sqlSQLCon As SqlConnection

We create a string object with Access’ and SQL Server’s connection string information. Here, I needed to supply the exact location of the Access database (so yours might be different), as well as security information. For the SQL Server database, I needed to supply the server name, the Database name as well as its security information. A good place to find any and all connection strings is connectionstrings.com. I then created an object to host our query as well as created a Connection object, which allows you to connect to the database through code.

Add the Form_Load event:

Access

	Private Sub Form1_Load( sender As Object,  e As EventArgs) Handles MyBase.Load

		'Search SQL Query
		'This Query Simply Retrieves All Information
		strAccQuery = "Select * From StudentInfo"

		'Instantiate Connection Object
		oleAccCon = New OleDbConnection(strAccConn)

		'Using Structure Simplifies Garbage Collection And Ensures That The Object Will Be Disposed Of Correctly Afterwards
		Using oleAccCon

			'Create Command Object, To Make Use Of SQL Query
			Dim oleAccCommand As New OleDbCommand(strAccQuery, oleAccCon)

			'Open Connection To The Database
			oleAccCon.Open()

			'Reader Object To Traverse Through Found Records
			Dim oleAccReader As OleDbDataReader = oleAccCommand.ExecuteReader()

			'If The Reader Finds Rows
			If oleAccReader.HasRows Then

				'Retrieve The Content, For Each Match
				While oleAccReader.Read()

					'GetString(1) Represents Column 2 Of StudentsInfo table
					txtName.Text = oleAccReader.GetString(1)

					'GetString(2) Gets Information Stored In Third Column
					txtSurname.Text = oleAccReader.GetString(2)

					'Use GetValue or GetInt32 Here, Because StudentNumber Is A Number Field
					txtStudentNumber.Text = oleAccReader.GetValue(0)

				End While

			Else

				'No Match Was Found
				MessageBox.Show("No rows found.")

			End If

			'Close Reader Object
			oleAccReader.Close()

		End Using


	End Sub

SQL Server

	Private Sub Form2_Load( sender As Object,  e As EventArgs) Handles MyBase.Load

		'Search SQL Query
		'This Query Simply Retrieves All Information
		strQuery = "Select * From StudentInfo"

		'Instantiate Connection Object
		sqlSQLCon = New SqlConnection(strSQLConn)

		'Using Structure Simplifies Garbage Collection And Ensures That The Object Will Be Disposed Of Correctly Afterwards
		Using sqlSQLCon

			'Create Command Object, To Make Use Of SQL Query
			Dim sqlSQLCommand As New SqlCommand(strQuery, sqlSQLCon)

			'Open Connection To The Database
			sqlSQLCon.Open()

			'Reader Object To Traverse Through Found Records
			Dim sqlSQLReader As SqlDataReader = sqlSQLCommand.ExecuteReader()

			'If The Reader Finds Rows
			If sqlSQLReader.HasRows Then

				'Retrieve The Content, For Each Match
				While sqlSQLReader.Read()

					'GetString(0) Represents Column 1 Of StudentsInfo table
					txtname.Text = sqlSQLReader.GetString(0)

					'GetString(1) Gets Information Stored In Second Column
					txtSurname.Text = sqlSQLReader.GetString(1)

					'Use GetValue or GetInt32 Here, Because StudentNumber Is A Number Field
					txtStudentNumber.Text = sqlSQLReader.GetValue(2)

				End While

			Else

				'No Match Was Found
				MessageBox.Show("No Rows Found.")

			End If

			'Close Reader Object
			sqlSQLReader.Close()

		End Using

	End Sub

It looks more intimidating than it is really. Because this is the Form_Load event, which fires as soon as the form is loaded, I thought it best to show all the records when the form is shown. The first line of code just populates the string variable, which will ultimately be used as the query. The query itself is quite simple and it just retrieves all the records from the StudentInfo table.

I then created a connection object, which will physically connect to the database we specified earlier. I chose to make use of the Using statement here. The Using statement is very useful because it allows for proper Garbage Collection of the specified object, in this case oleAccCon and sqlSQLCon.

Inside the Using block I then created a Command with which I will fire the query to the open connection in order to retrieve the desired information. I open the Connection and create a DataReader object. The DataReader checks to see if there are any matches for the specified query. If there are matches, it will return them one by one, column by column until all the retrieved data has been supplied. Once this is done, it is just a matter of closing the connection and informing the user about the retrieved data, if there was any.

This sequence will actually remain the same throughout the project you will build today. The only thing that will really change is the query itself, as well as the number of parameters.

Now, add the following code for the Search button:

Access

	'Search Button Code
	Private Sub btnSearch_Click( sender As Object,  e As EventArgs) Handles btnSearch.Click

		'Search SQL Query, Including a Parameter Called Name
		'This Query Simply Retrieves All Information That Matches Our Criteria
		strAccQuery = "Select * From StudentInfo where StudentName = @Name"

		'Instantiate Connection Object
		oleAccCon = New OleDbConnection(strAccConn)

		'Using Structure Simplifies Garbage Collection And Ensures That The Object Will Be Disposed Of Correctly Afterwards
		Using oleAccCon

			'Create Command Object, To Make Use Of SQL Query
			Dim oleAccCommand As New OleDbCommand(strAccQuery, oleAccCon)

			'Create Parameter Instead Of Hardcoding Values
			'Name = Whatever txtSearch Contains
			oleAccCommand.Parameters.AddWithValue("Name", txtSearch.Text)

			'Open Connection To The Database
			oleAccCon.Open()

			'Reader Object To Traverse Through Found Records
			Dim oleAccReader As OleDbDataReader = oleAccCommand.ExecuteReader()

			'If The Reader Finds Rows
			If oleAccReader.HasRows Then

				'Retrieve The Content, For Each Match
				While oleAccReader.Read()

					'GetString(1) Represents Column 2 Of StudentsInfo table
					txtName.Text = oleAccReader.GetString(1)

					'GetString(2) Gets Information Stored In Third Column
					txtSurname.Text = oleAccReader.GetString(2)

					'Use GetValue or GetInt32 Here, Because StudentNumber Is A Number Field
					txtStudentNumber.Text = oleAccReader.GetValue(0)

				End While

			Else

				'No Match Was Found
				MessageBox.Show("No Rows Found.")

			End If

			'Close Reader Object
			oleAccReader.Close()

		End Using

	End Sub

SQL Server

	Private Sub btnSearch_Click( sender As Object,  e As EventArgs) Handles btnSearch.Click

		'Search SQL Query, Including a Parameter Called Name
		'This Query Simply Retrieves All Information That Matches Our Criteria
		strQuery = "Select * From StudentInfo where StudentName = @Name"

		'Instantiate Connection Object
		sqlSQLCon = New SqlConnection(strSQLConn)

		'Using Structure Simplifies Garbage Collection And Ensures That The Object Will Be Disposed Of Correctly Afterwards
		Using sqlSQLCon

			'Create Command Object, To Make Use Of SQL Query
			Dim sqlSQLCommand As New SqlCommand(strQuery, sqlSQLCon)

			'Create Parameter Instead Of Hardcoding Values
			'Name = Whatever txtSearch Contains
			sqlSQLCommand.Parameters.AddWithValue("Name", txtSearch.Text)

			'Open Connection To The Database
			sqlSQLCon.Open()

			'Reader Object To Traverse Through Found Records
			Dim sqlSQLReader As SqlDataReader = sqlSQLCommand.ExecuteReader()

			'If The Reader Finds Rows
			If sqlSQLReader.HasRows Then

				'Retrieve The Content, For Each Match
				While sqlSQLReader.Read()

					'GetString(0) Represents Column 1 Of StudentsInfo table
					txtname.Text = sqlSQLReader.GetString(0)

					'GetString(1) Gets Information Stored In Second Column
					txtSurname.Text = sqlSQLReader.GetString(1)

					'Use GetValue or GetInt32 Here, Because StudentNumber Is A Number Field
					txtStudentNumber.Text = sqlSQLReader.GetValue(2)

				End While

			Else

				'No Match Was Found
				MessageBox.Show("No Rows Found.")

			End If

			'Close Reader Object
			sqlSQLReader.Close()

		End Using

	End Sub

This code is almost verbatim as the Form_Load’s procedure. Now what is different? Look closely at the first line of code. There I created the query like this:

strQuery = "Select * From StudentInfo where StudentName = @Name"

I added the WHERE SQL clause as well as a parameter called Name. Inside the Using block, apart from the previous code, I also added a Parameter to the Command object with this line:

Command.Parameters.AddWithValue("Name", txtSearch.Text)

Here, as mentioned earlier (but repetition is always good!) I added Name as a parameter and gave it a value of the text that is inside the txtSearch TextBox.

If you were to run this project now, you would be able to enter a name into the Search field, and search for its existence inside the database table. As you might notice, I did no validation on any of the controls here, as that is not part of this topic, and I have covered it in an earlier article which you can find here.

Add the next code to Insert a record into the database table:

Access

	Private Sub btnAdd_Click( sender As Object,  e As EventArgs) Handles btnAdd.Click

		'INSERT SQL Query
		'This Query Inserts Our Input Data Into The Table
		strAccQuery = "Insert Into StudentInfo (StudentName, StudentSurname, StudentNumber) Values (@Name, @Surname, @StudentNo)"

		'Instantiate Connection Object
		oleAccCon = New OleDbConnection(strAccConn)

		'Using Structure Simplifies Garbage Collection And Ensures That The Object Will Be Disposed Of Correctly Afterwards
		Using oleAccCon

			'Create Command Object, To Make Use Of SQL Query
			Dim oleAccCommand As New OleDbCommand(strAccQuery, oleAccCon)

			'Create Parameters Instead Of Hardcoding Values
			'Name = Whatever txtName Contains
			'Surname = Whatever txtSurname Contains
			'StudentNo = txtStudentNumber Text
			oleAccCommand.Parameters.AddWithValue("Name", txtName.Text)
			oleAccCommand.Parameters.AddWithValue("Surname", txtSurname.Text)
			oleAccCommand.Parameters.AddWithValue("StudentNo", txtStudentNumber.Text)

			'Open Connection To The Database
			oleAccCon.Open()

			'Execute Command As NonQuery As It Doesn't Return Info
			oleAccCommand.ExecuteNonQuery()

			'Inform User That Row Has Been Added
			MessageBox.Show("Added")

		End Using

	End Sub

SQL Server

	Private Sub btnAdd_Click( sender As Object,  e As EventArgs) Handles btnAdd.Click

		'INSERT SQL Query
		'This Query Inserts Our Input Data Into The Table
		strQuery = "Insert Into StudentInfo (StudentName, StudentSurname, StudentNumber) Values (@Name, @Surname, @StudentNo)"

		'Instantiate Connection Object
		sqlSQLCon = New SqlConnection(strSQLConn)

		'Using Structure Simplifies Garbage Collection And Ensures That The Object Will Be Disposed Of Correctly Afterwards
		Using sqlSQLCon

			'Create Command Object, To Make Use Of SQL Query
			Dim sqlSQLCommand As New SqlCommand(strQuery, sqlSQLCon)

			'Create Parameters Instead Of Hardcoding Values
			'Name = Whatever txtName Contains
			'Surname = Whatever txtSurname Contains
			'StudentNo = txtStudentNumber Text
			sqlSQLCommand.Parameters.AddWithValue("Name", txtName.Text)
			sqlSQLCommand.Parameters.AddWithValue("Surname", txtSurname.Text)
			sqlSQLCommand.Parameters.AddWithValue("StudentNo", txtStudentNumber.Text)

			'Open Connection To The Database
			sqlSQLCon.Open()

			'Execute Command As NonQuery As It Doesn't Return Info
			sqlSQLCommand.ExecuteNonQuery()

			'Inform User That Row Has Been Added
			MessageBox.Show("Added")

		End Using

	End Sub

This is new! Well, obviously…

The query you created looks like this:

"Insert Into StudentInfo (StudentName, StudentSurname, StudentNumber) Values (@Name, @Surname, @StudentNo)"

This SQL query inserts records into the specified table. In this case we are dealing with the StudentInfo table. You have to supply the field list inside brackets. This field list is the list of fields in which you want to insert data. You may find that with Identity columns, you do not have to supply that column here as it gets populated automatically. For more information on Identity columns, have a look here.

After you specify the field list, you need to supply values to those fields. Makes sense, doesn’t it? So, after the Values keyword, you need to supply the appropriate values in the same order you specified in the field list, otherwise your fields will contain the wrong data, or won’t even accept the supplied data due to the wrong data type. You will notice that here I created three Parameters: Name, Surname, StudentNo, so the obvious next step is to add these parameters to the command object:

			Command.Parameters.AddWithValue("Name", txtName.Text)
			Command.Parameters.AddWithValue("Surname", txtSurname.Text)
			Command.Parameters.AddWithValue("StudentNo", txtStudentNumber.Text)

You may have also noticed that I didn’t make use of the DataReader object here. It is because it is not needed as this type of query doesn’t return anything back; it simply does what you asked. This is why you need to Execute it as NonQuery.

Add the following event for the Update button:

Access

	Private Sub btnUpdate_Click( sender As Object,  e As EventArgs) Handles btnUpdate.Click

		'UPDATE SQL Query, Including Parameters
		'This Query Updates Our Existing Data To Our Entered Values
		strAccQuery = "Update StudentInfo Set StudentName = @Name, StudentSurname = @Surname, StudentNumber = @StudentNo Where StudentName = @Search"

		'Instantiate Connection Object
		oleAccCon = New OleDbConnection(strAccConn)

		'Using Structure Simplifies Garbage Collection And Ensures That The Object Will Be Disposed Of Correctly Afterwards
		Using oleAccCon

			'Create Command Object, To Make Use Of SQL Query
			Dim oleAccCommand As New OleDbCommand(strAccQuery, oleAccCon)

			'Create Parameters Instead Of Hardcoding Values
			'Name = Whatever txtName Contains
			'Surname = Whatever txtSurname Contains
			'StudentNo = txtStudentNumber Text
			oleAccCommand.Parameters.AddWithValue("Name", txtName.Text)
			oleAccCommand.Parameters.AddWithValue("Surname", txtSurname.Text)
			oleAccCommand.Parameters.AddWithValue("StudentNo", txtStudentNumber.Text)
			oleAccCommand.Parameters.AddWithValue("Search", txtSearch.Text)

			'Open Connection To The Database
			oleAccCon.Open()

			'Execute Command As NonQuery As It Doesn't Return Info
			oleAccCommand.ExecuteNonQuery()

			'Inform User That The Record Has Been Edited
			MessageBox.Show("Updated")

		End Using

	End Sub

SQL Server

	Private Sub btnUpdate_Click( sender As Object,  e As EventArgs) Handles btnUpdate.Click

		'UPDATE SQL Query, Including Parameters
		'This Query Updates Our Existing Data To Our Entered Values
		strQuery = "Update StudentInfo Set StudentName = @Name, StudentSurname = @Surname, StudentNumber = @StudentNo Where StudentName = @Search"

		'Instantiate Connection Object
		sqlSQLCon = New SqlConnection(strSQLConn)

		'Using Structure Simplifies Garbage Collection And Ensures That The Object Will Be Disposed Of Correctly Afterwards
		Using sqlSQLCon

			'Create Command Object, To Make Use Of SQL Query
			Dim sqlSQLCommand As New SqlCommand(strQuery, sqlSQLCon)

			'Create Parameters Instead Of Hardcoding Values
			'Name = Whatever txtName Contains
			'Surname = Whatever txtSurname Contains
			'StudentNo = txtStudentNumber Text
			sqlSQLCommand.Parameters.AddWithValue("Name", txtName.Text)
			sqlSQLCommand.Parameters.AddWithValue("Surname", txtSurname.Text)
			sqlSQLCommand.Parameters.AddWithValue("StudentNo", txtStudentNumber.Text)
			sqlSQLCommand.Parameters.AddWithValue("Search", txtSearch.Text)

			'Open Connection To The Database
			sqlSQLCon.Open()

			'Execute Command As NonQuery As It Doesn't Return Info
			sqlSQLCommand.ExecuteNonQuery()

			'Inform User That The Record Has Been Edited
			MessageBox.Show("Updated")

		End Using

	End Sub

Now you have a query that looks like:

Update StudentInfo Set StudentName = @Name, StudentSurname = @Surname, StudentNumber = @StudentNo Where StudentName = @Search

This is an Update query. You enter the keyword Update, then supply the Table we need to edit and then the Fields along with their respective new values. This query allows you to change existing data in the database table. Let’s say someone has changed his or her surname; in that case you will have to use an update query such as above, to change the person’s surname.

You may also have noticed the added WHERE condition. This is necessary otherwise the query that fires will not know which record you want to change. This query also does not return any data, so we have to use ExecuteNonQuery to execute it.

Add the code to delete a certain record:

Access

	Private Sub btnDelete_Click( sender As Object,  e As EventArgs) Handles btnDelete.Click

		'DELETE SQL Query
		'This Query Deletes Desired Records
		strAccQuery = "Delete From StudentInfo where StudentName = @Name"

		'Instantiate Connection Object
		oleAccCon = New OleDbConnection(strAccConn)

		'Using Structure Simplifies Garbage Collection And Ensures That The Object Will Be Disposed Of Correctly Afterwards
		Using oleAccCon

			'Create Command Object, To Make Use Of SQL Query
			Dim oleAccCommand As New OleDbCommand(strAccQuery, oleAccCon)

			'Create Parameter Instead Of Hardcoding Values
			'Name = Whatever txtSearch Contains
			oleAccCommand.Parameters.AddWithValue("Name", txtSearch.Text)

			'Open Connection To The Database
			oleAccCon.Open()

			'Execute Command As NonQuery As It Doesn't Return Info
			oleAccCommand.ExecuteNonQuery()

			'Inform User Of Record(s) Deletion
			MessageBox.Show("Deleted")

		End Using

	End Sub

SQL Server

	Private Sub btnDelete_Click( sender As Object,  e As EventArgs) Handles btnDelete.Click

		'DELETE SQL Query
		'This Query Deletes Desired Records
		strQuery = "Delete From StudentInfo where StudentName = @Name"

		'Instantiate Connection Object
		sqlSQLCon = New SqlConnection(strSQLConn)

		'Using Structure Simplifies Garbage Collection And Ensures That The Object Will Be Disposed Of Correctly Afterwards
		Using sqlSQLCon

			'Create Command Object, To Make Use Of SQL Query
			Dim sqlSQLCommand As New SqlCommand(strQuery, sqlSQLCon)

			'Create Parameter Instead Of Hardcoding Values
			'Name = Whatever txtSearch Contains
			sqlSQLCommand.Parameters.AddWithValue("Name", txtSearch.Text)

			'Open Connection To The Database
			sqlSQLCon.Open()

			'Execute Command As NonQuery As It Doesn't Return Info
			sqlSQLCommand.ExecuteNonQuery()

			'Inform User Of Record(s) Deletion
			MessageBox.Show("Deleted")

		End Using

	End Sub

Our query now looks like :

Delete From StudentInfo where StudentName = @Name

This simply deletes all associated records with the same name value that the user supplies to the Name parameter.  This query also does not return any data, so we have to use ExecuteNonQuery to execute it.

Test your application now and you will see that you will be able to add records, update records as well as delete records from a database table now. This concludes the Parameterized Queries section of this article.

Reports and Data

Although reporting is eligible for its own article, I still thought to introduce you to the various options available. You have the following options when you need to print a report.

  1. SSRS – SQL Server Reporting Services
  2. Crystal reports
  3. Microsoft reports
  4. Access reports

To name just a few.

SSRS

For more information on SQL Server Reporting Services, have a look at these:

  1. http://technet.microsoft.com/en-us/library/aa964126%28v=sql.90%29.aspx
  2. http://social.technet.microsoft.com/Forums/sqlserver/en-US/d7c11008-7654-4991-8560-2950a0f8c3a1/best-tutorial-for-ssrs-with-sql-server-2008-r2-express-with-advanced-services-using-adventureworks?forum=sqlreportingservices
  3. To show that I am not biased, also have a look here : http://www.codeproject.com/Articles/13725/Getting-started-with-SQL-Server-Reporting-Services

Crystal Reports

For more information on Crystal Reports have a look at these:

  1. http://social.msdn.microsoft.com/Forums/en-US/0892c0d5-1bea-4218-8a1b-57aad704f06c/crystal-report-in-vbnet-2012?forum=winformsapplications
  2. http://www.sattsoft.com/tutorials/contents/1/20/generate-report-using-crystal-report-and-vb-net-tutorial.html

Microsoft Reports

For more information on Microsoft Reports, have a look here.

Access Reports

To See how you can show an Access report from within your program, have a look here.

Conclusion

I am always sad when I get to the end of an article. I just hope that you have learned from this article and that you have enjoyed it. Until next time, cheers!

Hannes DuPreez
Hannes DuPreez
Ockert J. du Preez is a passionate coder and always willing to learn. He has written hundreds of developer articles over the years detailing his programming quests and adventures. He has written the following books: Visual Studio 2019 In-Depth (BpB Publications) JavaScript for Gurus (BpB Publications) He was the Technical Editor for Professional C++, 5th Edition (Wiley) He was a Microsoft Most Valuable Professional for .NET (2008–2017).

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read