logo

Passing Tables of Data Directly To Stored Procedures With SQL Server

Ever wondered how to create multiple rows in a SQL Server table using ASP.NET?

You could of course do a loop inside C# code that adds a row at a time by calling a simple INSERT statement. But what if something goes wrong half way through the loop and you want to delete all the rows already added (rollback) so you can try it all again later?

A better way to add many rows at once is to pass a DataTable object directly to a Stored Procedure. Yes, you can do that. And it's fairly easy. Since discovering this approach I've used it in quite a few places.

Let's look at how simple it is.

Last week I mentioned a C# function to create unique voucher codes. Now, let's say we have two tables in a database to store these voucher codes - one called "VoucherRequests" - to store details of who, why and when the codes were requested and one called "VoucherCodes" to store the actual codes generated for each request. The tables are linked together by VoucherRequest row's ID column as a foreign key.

Now, assume our application POSTs the following information to the server:

description=some+vouchers+to+hand+out&quantity=50&value=100&expires=2011-11-23

The server then uses this information to create the right number of vouchers in our tables.

The C# code would look something like this:

requestsTableAdapter rta = new requestsTableAdapter();
vouchersTableAdapter vta = new vouchersTableAdapter();

DataTable vouchers = new DataTable();
vouchers.Columns.Add("VoucherCode");
vouchers.Columns.Add("VoucherValue");
vouchers.Columns.Add("VoucherExpires");

int count = Convert.ToInt32(context.Request["Quantity"]);
int value = Convert.ToInt32(context.Request["Value"]);

DateTime validUntil = DateTime.ParseExact(context.Request["Expires"], "dd/MM/yyyy", CultureInfo.InvariantCulture);
validUntil = validUntil.AddDays(1).AddSeconds(-1); //Adjust time to 23:59:59 of the date in question

int i=1;

do
{
 string code = CreateRandomString(8);

 if (vta.TestCodeUniqueness(code)<1){
  DataRow dr = vouchers.NewRow();
 
  dr[0] = code; // System.Guid.NewGuid().ToString("D");
  dr[1] = value;
  dr[2] = validUntil.ToString("yyyy-MM-dd HH:mm:ss");

  vouchers.Rows.Add(dr);

  i++;
 }
} while (i <= count);

rta.AddVouchers(context.Request["Description"], vouchers);

You should be able to see what this is doing. Even if some of it makes no sense - the principal is what's important.

What it does is build a temporary DataTable with 3 columns. Then it keeps trying to create a unique code to add to this table until it has added as many as was requested. Then it passes this table as-is, along with a description of the request, to a Stored Procedure (SP) via a TableAdapter.

The key to making this work is having a User-Defined Table Type in SQL Server, so that it knows how many column to expect and what data type they should be.

Here's the SQL to create this Table Type:

CREATE TYPE [dbo].[VouchersTableType] AS TABLE(
        [VoucherCode] [nvarchar](36) NOT NULL,
        [VoucherValue] [int] NOT NULL,
        [VoucherExpires] [datetime] NULL
)

Once the Table Type is in place we can create a SP that uses it. Here's the SQL for SP to add multiple rows in one go:

CREATE PROCEDURE [dbo].[VoucherRequestADD]
(
        @description NVARCHAR(255),
        @TVP VouchersTableType READONLY
)       
AS 
SET NOCOUNT ON

DECLARE @RequestID INT;
            
BEGIN TRANSACTION
    INSERT INTO dbo.voucher_requests
        ([description], [created])
        VALUES (@description, GETDATE());
                
    SET @RequestID = SCOPE_IDENTITY();
        
    INSERT INTO dbo.vouchers
           ([voucher_request_id]
           ,[voucher_code]
           ,[valid_until]
           ,[value])
       SELECT @RequestID, VoucherCode, VoucherExpires, VoucherValue
       FROM  @TVP;

     IF @@ERROR != 0 
        BEGIN
           ROLLBACK TRANSACTION --Roll back transaction on error
           SET @RequestID=0;
           GOTO handler
        END
                
COMMIT TRANSACTION

handler:
SELECT @RequestID;
GO

The key part of this SP is that the INSERT statement uses a SELECT to get its data values. The SELECT statement refers to the Table Type passed in to it.

Notice the INSERT part of the SP is wrapped in a TRANSACTION so that, if anything goes wrong, we can rollback to a previous state and abort the code.

This example is quite specialised, but the principle is the same and can be applied in lots of other scenarios. For example - a shopping cart which has multiple rows per item and one row in the main table to store data about the user and other bits.

I'm posting this here primarily as I hope it will help people Googling in the future, but also as an insight in to a different world for my regular (Domino developer) readers.

Comments

    • avatar
    • Nick
    • Tue 30 Nov 2010 06:11 AM

    There is 'SQLTransaction' that you can use, that it retains the information, and can be rolled back through the vb.net or C#.

    Here is a little snippet.

    Public Sub Save()

    Dim conn As New SqlConnection(connstring)

    Try

    conn.Open()

    Dim trans As SqlTransaction = conn.BeginTransaction()

    Save(trans)

    If trans.Connection IsNot Nothing Then

    trans.Commit()

    End If

    Finally

    If conn.State <> ConnectionState.Closed Then

    conn.Close()

    End If

    End Try

    End Sub

    Public Sub Save(ByVal trans as SQLTransaction)

    Dim result As Integer

    Try

    result = cmd.ExecuteNonQuery()

    If IsNew Then

    _ID = Integer.Parse(cmd.Parameters("@ID").Value.ToString)

    Else

    End If

    Catch ex As Exception

    trans.Rollback()

    Throw ex

    End Try

    End If

    End Sub

      • avatar
      • Jake Howlett
      • Tue 30 Nov 2010 07:10 AM

      Interesting. Didn't know about that. Like everything - there's always more than one way to do the same thing.

      I've grown used to using table adapters and tend not to use connections to SQL from my code.

      Show the rest of this thread

    1. I don't want to sound like a smartarse, but it is never good idea to mix code from different places into your main program (i.e. SQL into C# etc.). I don't want to compile my program every time I need to fix a bug in SQL statement.

      Call me old fationed, but I prefer code separation. SQL statements should be in stored procedures and only amount of SQL that you cannot avoid should be kept in your software. If nothing else, for maintenance reasons.

    • avatar
    • Nick
    • Tue 30 Nov 2010 07:49 AM

    I used the book "Visual Basic .NET Business Objects" by Rockford Lhotka, as a basis for my classes and business objects. I am sure there is probably a C# version as well.

    • avatar
    • Toby Samples
    • Tue 30 Nov 2010 08:50 AM

    Nice Post, I tend to do this sort of thing using an xml type that way I do not have to create the table type up front, I can just do it on the fly, It would be an interesting study to look at a performance comparison though.

    • avatar
    • Mark
    • Thu 2 Dec 2010 05:17 AM

    I would recommend using the Microsoft Enterprise Library.

    http://msdn.microsoft.com/en-us/library/ff632023.aspx

    I know you prefer coding everything by yourself but this framework is really mature these days. And you don't have to use all blocks of the framework but can start using only the Data Access Application Block.

    Regards,

    Mark

      • avatar
      • Jake Howlett
      • Fri 3 Dec 2010 04:42 AM

      Thanks Mark. Hadn't seen that before and it looks very interesting.

      It's not that I prefer to write my own code it's more a case of wanting to learn ASP.NET from the "ground up". I don't want to dive straight in to using frameworks before I've even got used to and comfortable with the basics.

Your Comments

Name:
E-mail:
(optional)
Website:
(optional)
Comment:


About This Page

Written by Jake Howlett on Tue 30 Nov 2010

Share This Page

# ( ) '

Comments

The most recent comments added:

Skip to the comments or add your own.

You can subscribe to an individual RSS feed of comments on this entry.

Let's Get Social


About This Website

CodeStore is all about web development. Concentrating on Lotus Domino, ASP.NET, Flex, SharePoint and all things internet.

Your host is Jake Howlett who runs his own web development company called Rockall Design and is always on the lookout for new and interesting work to do.

You can find me on Twitter and on Linked In.

Read more about this site »

More Content