Click here to monitor SSC
Av rating:
Total votes: 29
Total comments: 7


Leidago !Noabeb
Using Table Valued Parameters with VB.NET
03 November 2010

SQL Server's Table-Valued  Parameters are useful, and easy to use, but you should be aware that something that looks loke a table, and seems to behave like a table, can't necessarily be used just like any other table. Leidago gives a developer's guide on how, and why, you should use TVPs in your application.

Microsoft has finally solved the problem of how to send multiple rows of data to a database without having to do it one row at a time. No more looking around for inefficient workarounds or using time consuming methods to meet requirements; Table Value Parameters, or TVPs as they are commonly known, have come to the rescue! In layman’s terms, you can now send a whole bunch of rows in one go.

Why was there a problem? If you are forced to send multiple rows of data from a client application to SQL Server by doing several trips to and from the client application, then your application’s performance was likely to suffer and you wouldn’t be able to easily wrap it all in a single transaction. Sometimes you need to be sure that all alterations to the database are done in one transaction so that, if there is an error, the operation can be entirely rolled back rather than leave the data within the database in an inconsistent state.

Before Table-Valued Parameters were introduced in SQL Server 2008, programmers had to pass multi-row data as serialized, delimited, strings, long parameter lists, or XML. This made both the application and the server more difficult to program reliably. Erland covers all these painful techniques here: Arrays and Lists in SQL Server 2005 and Beyond: When TVPs Do Not Cut it

So what are Table Value Parameters? Table-valued parameters provide an easy way to send. You simply pass a table value parameter through a stored procedure to the server. This feature is available in SQL server 2008 only. Earlier versions of SQL Server do not have this functionality.

How does it work?

Although this recent feature sounds useful, a number of steps to be taken first before you use it. Table-Valued Parameters are declared using user-defined table types. It is not as complicated as it sounds. Firstly, we need to create a table type. Secondly, we create a variable of the table type that we just created and populate it. The variable will actually contain the structure of a database table. Once the table is populated with data, we can manipulate it as we would any other table, until it gets passed as a parameter.

Let’s put this in practice, first let’s create the database and the table.

Our sample database is just going to contain the names of our contacts just do demonstrate the use of TVPs. The code will first create a database and then a table. SQL Server provides SQL Management Studio (SSMS) that makes it simple to create databases and tables by using the Object Browser. I will show both ways of creating a table type for our next step. Firstly, I’ll use SQL to create both the database and table. In SSMS add this query and execute it:

USE master

Go

CREATE DATABASE contacts

go

USE contacts

GO--create table

CREATE TABLE contact (

contactID INT PRIMARY KEY,

fname VARCHAR(30),

lname VARCHAR(30))

GO

Once your database and table is created, then the next thing we do is to create the table type.

-- ================================

-- Create User-defined Table Type

-- ================================

USE Contacts

GO

 

-- Create the data type

CREATE TYPE dbo.contactType AS TABLE

(

      ContactID INT NOT NULL,

      fName VARCHAR(30) NOT NULL,

      lname VARCHAR(30) NOT NULL,

    PRIMARY KEY (ContactID)

)

GO

You’ll see that this syntax is pretty close to the CREATE TABLE one. You can define PRIMARY KEY, UNIQUE but not CHECK constraints, you can use IDENTITY and DEFAULT definitions, and you can define computed columns, but there are restrictions.  You cannot use ALTER TABLE statements to change the design of table-valued parameters

If you prefer, you can expand the database then select and expand the Programmability option. Do the same with the Types option and find the User-Defined Table Types options:

Right click on it and select the ‘New User Defined Table Type’ option. The following code should appear in the query window:

-- ================================

-- Create User-defined Table Type

-- ================================

USE <database_name,sysname,AdventureWorks>

GO

 

-- Create the data type

CREATE TYPE <schema_name,sysname,dbo>.<type_name,sysname,TVP> AS TABLE

(

      <columns_in_primary_key, , c1> <column1_datatype, , int> <column1_nullability,, NOT NULL>,

      <column2_name, sysname, c2> <column2_datatype, , char(10)> <column2_nullability,, NULL>,

      <column3_name, sysname, c3> <column3_datatype, , datetime> <column3_nullability,, NULL>,

    PRIMARY KEY (<columns_in_primary_key, , c1>)

)

GO

This is a template. You need just hit Control Shift M and fill in the form that is then shown. This will generate the same code as above:

After you’ve executed the code, you can verify that the data type has been created by checking in the object Explorer:

Now that we have this table type defined, we have to make it useable. For example if we want to insert data or use any other T-SQL, we have to create a variable of the new table type and populate it. For those familiar with Object Oriented Programming, this is a bit like instantiating a class or object. Once this new variable is created it will inherit the table structure of the new type. This is how it is done:

First of all we declare a variable of table type:

DECLARE @Mtabletype contactType

 

/* The variable name @Mtabletype can be called anything you want but the table type has to reference an existing table definition type. Now we insert the data: */

 

INSERT  INTO @Mtabletype (contactID, fname, lname)

VALUES  (1, 'Dantago', '!Noabes'),

        (2, 'Hadago', '!Nuwuseb'),

        (3, '!Garibasen', 'Siebeb'),

        (4, 'Mubasen', '//Gaseb'),

        (5, 'Amase', '//Noabes'),

        (6, 'Axaro', 'Xamiseb'),

        (7, 'Xoagu', '//Gowases'),

        (8, 'Tia', 'Doeses'),

        (9, 'Kamro', 'Araes') ;

       

/* Once the table is populated, we can manipulate the data like any other table, provided the variable is in scope: */

 

SELECT * FROM @Mtabletype

Here’s the result when you run the above code:

The next step is to declare a stored procedure that has a parameter of our new table type that we have defined. We’ll then pass our variable as a parameter to a stored procedure. We’ve already got a regular table that we can populate; the one we created at the beginning, when we created the database. All we need now is to create a stored procedure that will populate that table:

CREATE PROC insertTVP

  @MParam contactType READONLY

AS

INSERT  INTO dcontacts (contactID, fname, lname)

        SELECT  contactID, fname, lname

        FROM    @MParam

Go

So what does the code above do? Well, first of all, we define the stored procedure by adding our table type contactType then we add a READONLY qualifier. This qualifier is very important and MUST be included when a stored procedure is created. This is mainly because Transact-SQL passes table-valued parameters to routines by reference. Basically, a pointer is passed to the stored procedure and not as we would have expected, the user defined table variable. It therefore isn’t possible to do DML operations that update a table-valued parameter, such as UPDATE, DELETE, or INSERT within a routine. Table-valued parameters are read-only in Transact-SQL code. If you need to modify the data that is passed to a stored procedure or parameterized statement in a table-valued parameter, you will need to insert the data into a temporary table or into a table variable first. It isn’t actually much of a problem to do this, and takes little time.

Now that we have created the code for the stored procedure we can run it. After running it, you should be able to see it in the project explorer as below:

Now all that remains is for us to pass our variable to the stored procedure. It will have to be a stored procedure rather than a function since MSDN says that you cannot pass table-valued parameters to a user-defined function and, anyway, you wouldn’t be able to do an INSERT into a table from within it since DML statements to tables are invalid in scalar UDFs. Lets populate the contact table that we created earlier, add the following code to a query window in SSMS and execute it:

DECLARE @Mtabletype contactType

DELETE FROM contact

INSERT  INTO @Mtabletype (contactID, fname, lname)

VALUES  (1, 'Dantago', '!Noabes'),

        (2, 'Hadago', '!Nuwuseb'),

        (3, '!Garibasen', 'Siebeb'),

        (4, 'Mubasen', '//Gaseb'),

        (5, 'Amase', '//Noabes'),

        (6, 'Axaro', 'Xamiseb'),

        (7, 'Xoagu', '//Gowases'),

        (8, 'Tia', 'Doeses'),

        (9, 'Kamro', 'Araes') ;

 

EXEC inserttvp @MParam = @Mtabletype

SELECT * FROM contact

go

A large part of the code above should be familiar to you. We’ve just added the code to execute the stored procedure.

At which point did we pass the table as a parameter? First we declare a variable of our table type:

DECLARE @Mtabletype contactType

At this point our contacts are stored in the @Mtabletype variable. Then we simply pass that variable to the stored procedure that we created:

EXEC inserttvp @MParam = @contact

When I run the query on the base table...

SELECT * FROM contact

...I get exactly the same result as from the TVP. The physical table is now populated with the data from the TVP! We have demonstrated that it is possible to use a Table-Valued Parameter to send multiple rows of data to a Transact-SQL statement or  a stored procedure  without creating a temporary table, pass an XML variable,  or having the give the procedure a whole lot of parameters.

Why is this useful? It is because we can pass tables from the client application and add them, via a Stored Procedure, to the database, in just one round trip. This is far quicker than any other way of passing multiple rows to the database. We’ll move on to show how this is done, using a simple illustration in VB NET.

Creating a VB.NET Client Application

So far we’ve been using the SQL 2008 SSMS as if it were a client that was interacting with the database. Now we’ll illustrate the same technique from application code. We will be creating a client application using Visual Studio 2008 VB.NET. To start with, make sure that you have dot net framework 3.5 installed. Remember how we created a temporary table to hold our data earlier in the article? That is exactly what we are going to do in our application. Then we will send that data straight to our ‘contacts’ database. First create a windows application and add a button. We will be using stored procedure that we created earlier, so we don’t need an output control on our form. Just a button will do. Double click on the button and add the following code:

Imports System.Data.SQLClient

In this code, I’ve imported the SQLclient name space. This namespace contains a data type that we need in order to use Table-Valued Parameters. Next we create the temporary table that is going to hold our data. I’ve commented the code very heavily so that it is self explanatory:

Public Class Form1

 

    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click

        ''create table

        Dim Table1 As DataTable

        'create a table named tmptbl

        Table1 = New DataTable("tmptbl")

 

        Dim Row1, Row2, Row3 As DataRow

        'declaring three rows for the table

        Try

            'declare a column named contactID

            Dim contactID As DataColumn = New DataColumn("contactID")

            'setting the datatype for the column

            contactID.DataType = System.Type.GetType("System.String")

            'adding the column to table

            Table1.Columns.Add(contactID)

 

 

            'repear process for the remaining two columns

            Dim fname As DataColumn = New DataColumn("fname")

            fname.DataType = System.Type.GetType("System.String")

            Table1.Columns.Add(fname)

 

 

            Dim lname As DataColumn = New DataColumn("lname")

            lname.DataType = System.Type.GetType("System.String")

            Table1.Columns.Add(lname)

 

            'declaring a new row

            Row1 = Table1.NewRow()

            'filling the row with values. Item property is used to set the field value.

            'filling the row with values. adding a contactID

            Row1.Item("contactID") = "10"

            'filling the row with values. adding a lname

            Row1.Item("fname") = "Dantago"

            'filling the row with values. adding a fname

            Row1.Item("lname") = "!Noabes"

            'adding the completed row to the table

            Table1.Rows.Add(Row1)

 

            'Repeat the process for all rows you want to add

 

            Row2 = Table1.NewRow()

            Row2.Item("contactID") = "11"

            Row2.Item("fname") = "Xoagu"

            Row2.Item("lname") = "//Gowases"

            Table1.Rows.Add(Row2)

            Row3 = Table1.NewRow()

            Row3.Item("contactID") = "12"

            Row3.Item("fname") = "Kamro"

            Row3.Item("lname") = "Araes"

            Table1.Rows.Add(Row3)

        Catch

        End Try

We then make a connection to the database:

        'establishing connection. you need to provide password for SQL server

        Dim myConnection = New SQLConnection()

 

        myConnection.ConnectionString = “Your connection string here”

        Try

 

            myConnection.Open()

Next we set the stored procedure that we will be using and add the parameter that we used earlier:

            Dim myCommand As New SQLCommand("inserttvp", myConnection)

            Dim myparam As SQLParameter = myCommand.Parameters.Add("@Mparam", SQLDbType.Structured)

 

            'create parameter

            myCommand.CommandType = CommandType.StoredProcedure

Then we run the stored procedure after adding the table name:

            myparam.Value = table1

 

            myCommand.ExecuteNonQuery()

 

        Catch ex As SQLException

 

            MsgBox(ex.Message)

        End Try

    End Sub

End Class

To use TVPs in this way, you have to use System.Data.SqlClient. We’ve show it used with a DataTable, but you can use the technique from a list that is instantiated from the SqlDataRecord class, or from a DbDataReader. As far as I know, none of the major ORMs support this technique, not even Entity Framework. The ODBC driver for SQL Server Native Client allows you to use it but it is best to check out the rather more complex code here Readme_Table-Valued Parameters (ODBC).

Conclusion

Using table valued parameters enables database applications to perform much better than they otherwise would have, by reducing the amount of trips the application makes to the server. You can now just send multiple rows to the server with one call. An OLTP transaction that involves many rows of data can be contained within a single stored procedure call. Without table-valued parameters, you would need to hold the transaction open whilst several calls are made to the server. TVPs are far simpler to process than delimited lists, and there is better type-checking. It is faster, and more efficient than using XML variables to do the same thing.

It isn’t necessarily a panacea in all cases. It certainly makes it simple to pass whole tables of strongly-typed data to the application in one call, where it can be absorbed easily in one transaction without causing the generation of a lot of locks. However, there are restrictions: the data is read-only and, because statistics aren’t generated on TVPs, could be slow to process within the receiving routine if there is a lot of complexity involved. On balance, though, it might very well work for you.

Further reading

Arrays and Lists in SQL Server 2008

Table-Valued Parameters (Database Engine)

Table-Valued Parameters in SQL Server 2008 (ADO.NET)



This article has been viewed 12631 times.
Leidago !Noabeb

Author profile: Leidago !Noabeb

Leidago is the owner of a Software Company called Gain Technology Services based in Namibia, the company specializes in developing communications software and has over ten years experience in software development. He have made many contributions to various online websites dedicated to web and software development.

Search for other articles by Leidago !Noabeb

Rate this article:   Avg rating: from a total of 29 votes.


Poor

OK

Good

Great

Must read
 
Have Your Say
Do you have an opinion on this article? Then add your comment below:
You must be logged in to post to this forum

Click here to log in.


Subject: Typo in article
Posted by: Sam Van Brussel (not signed in)
Posted on: Monday, November 08, 2010 at 1:29 AM
Message: There should be 'EXEC inserttvp @MParam = @Mtabletype' instead of 'EXEC inserttvp @MParam = @contact', a few lines above the paragraph 'Creating a VB.NET Client Application'.

Subject: Excellent Article!
Posted by: Anonymous (not signed in)
Posted on: Wednesday, November 10, 2010 at 9:39 AM
Message: Thanks alot! This helped me alot

Subject: Erland's name is misspelled
Posted by: Alex_Kuznetsov (view profile)
Posted on: Friday, November 12, 2010 at 2:15 PM
Message: in the following: "Erlang covers all these painful techniques here:"

Subject: re: Erland's name is misspelled
Posted by: Andrew Clarke (view profile)
Posted on: Tuesday, November 16, 2010 at 7:31 AM
Message: Oops! fixed. Sorry Erland.

Subject: typo
Posted by: vadimas (view profile)
Posted on: Monday, November 22, 2010 at 10:30 AM
Message: should be:
INSERT INTO contacts ...
(not INSERT INTO dcontacts) in the stored proc.
Thanks, very useful!

Subject: my typo
Posted by: vadimas (view profile)
Posted on: Monday, November 22, 2010 at 10:32 AM
Message: INSERT INTO contact in the above...

Subject: problem..
Posted by: cyberdaemon (view profile)
Posted on: Thursday, May 17, 2012 at 9:50 PM
Message: I have problem regarding this post.. i used C# as my language and i used SQL server 2008 as my back end. i already created table type as my parameter.. but i want to insert record that will come from gridview via checkbox. how could i add my multiple record using a single click...

 






recommended site pinvoke

PInvoke.net is a user-driven wiki which provides .NET developers with native method signatures, so they don't have to spend time writing them from scratch.




Top rated articles
C# Async: What is it, and how does it work?
 The biggest new feature in C#5 is Async, and its associated Await (contextual) keyword. Anybody who is... Read more...

Towards the Perfect Build
 An automated build and deployment system is no longer a dream. Now that PowerShell has matured as a... Read more...

Practical PowerShell: Pruning File Trees and Extending Cmdlets
 One of the most radical features of PowerShell is amongst the least known. It is possible to extend... Read more...

TortoiseSVN and Subversion Cookbook Part 4: Sharing Common Code
 Michael Sorens continues his series on source control with Subversion and TortoiseSVN by describing... Read more...

Feature Usage Reporting in Early Access Programs
 After doing Web development, you can get very used to the luxury of having basic information about your... Read more...

Most viewed articles
A Complete URL Rewriting Solution for ASP.NET 2.0
 Ever wondered whether it's possible to create neater URLS, free of bulky Query String parameters?... Read more...

Visual Studio Setup - projects and custom actions
 This article describes the kinds of custom actions that can be used in your Visual Studio setup project. Read more...

.NET Application Architecture: the Data Access Layer
 Find out how to design a robust data access layer for your .NET applications. Read more...

Web Parts in ASP.NET 2.0
 Most Web Parts implementations allow users to create a single portal page where they can personalize... Read more...

Calling Cross Domain Web Services in AJAX
 The latest craze for mashups involves making cross-domain calls to Web Services from APIs made publicly... Read more...

Over 400,000 Microsoft professionals subscribe to the Simple-Talk technical journal. Join today, it's fast, simple, free and secure.

Join Simple Talk