Table-Valued Parameters: The Secret Weapon for High-Performance Data Transfer in SQL Server and C#

Leveraging IEnumerable<SqlDataRecord> for Efficient Bulk Data Inserts and Updates Using TVPs in .NET

Md hasanuzzzaman
4 min readOct 12, 2024

Introduction

When handling large data transfers between your C# application and SQL Server, performance becomes a critical concern. A common solution involves passing individual records one at a time, which can lead to slow performance due to repeated database calls. Instead, you can leverage Table-Valued Parameters (TVP) to pass multiple records in a single query.

In this blog, we’ll reveal how to use Table-Valued Parameters (TVP) in SQL Server with C#, focusing on the use of IEnumerable<SqlDataRecord>, which is extra green than DataTable for high-overall performance facts transfer.

1. What is a Table-Valued Parameter (TVP)?

Table-Valued Parameters (TVPs), which were added in SQL Server 2008. It’s allows you to pass multiple rows of data to a stored procedure or query in a single database call. This significantly cuts down the number of times the database is accessed, which can boost the speed of handling large amounts of data.

2. Creating a Table-Valued Parameter in SQL Server

To use TVP, you first need to define a user-defined table type and a stored procedure in SQL Server.

Step 1: Define a User-Defined Table Type

CREATE TYPE dbo.MyTableType AS TABLE
(
Id INT,
Name NVARCHAR(50),
Quantity INT
);

This type will allow us to pass structured data (i.e., rows and columns) as a parameter to our stored procedures.

Step 2: Create a Stored Procedure to Use the TVP

CREATE PROCEDURE dbo.InsertMyTable
@MyTable MyTableType READONLY
AS
BEGIN
INSERT INTO MyTable (Id, Name, Quantity)
SELECT Id, Name, Quantity FROM @MyTable;
END

This stored procedure takes the TVP as an input parameter (@MyTable) and inserts the records into the actual table within the database.

3. Using IEnumerable<SqlDataRecord> in C#

Now, let’s enforce the C# code on the way to send statistics to the saved system the use of IEnumerable<SqlDataRecord>, that is a greater reminiscence-efficient way to handle massive data units than DataTable.

Step 1: Creating the Data Source as IEnumerable<SqlDataRecord>
We can represent every row of the TVP as a SqlDataRecord. Below is an instance of how to create a set of SqlDataRecord gadgets that represent the statistics to be inserted.

public static IEnumerable<SqlDataRecord> GetSqlDataRecords(List<Product> products)
{
// Define the schema for the SqlDataRecord (same as MyTableType)
SqlMetaData[] metaData = new SqlMetaData[]
{
new SqlMetaData("Id", SqlDbType.Int),
new SqlMetaData("Name", SqlDbType.NVarChar, 50),
new SqlMetaData("Quantity", SqlDbType.Int)
};

// Populate the records
foreach (var product in products)
{
SqlDataRecord record = new SqlDataRecord(metaData);
record.SetInt32(0, product.Id); // Set Id
record.SetString(1, product.Name); // Set Name
record.SetInt32(2, product.Quantity); // Set Quantity

yield return record;
}
}

Here, Product is a simple class:

public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public int Quantity { get; set; }
}

This approach creates an IEnumerable<SqlDataRecord> object, which may be used to send data to SQL Server.

Step 2: Passing IEnumerable<SqlDataRecord> to SQL Server
Once we’ve got the records within the shape of IEnumerable<SqlDataRecord>, we are able to bypass it to the saved technique using a SqlParameter of type SqlDbType.Structured.

public void InsertDataUsingTVP(IEnumerable<SqlDataRecord> dataRecords)
{
string connectionString = "Your SQL Server connection string here";

using (SqlConnection connection = new SqlConnection(connectionString))
{
using (SqlCommand command = new SqlCommand("dbo.InsertMyTable", connection))
{
command.CommandType = CommandType.StoredProcedure;

// Create the TVP parameter
SqlParameter tvpParam = new SqlParameter();
tvpParam.ParameterName = "@MyTable";
tvpParam.SqlDbType = SqlDbType.Structured;
tvpParam.TypeName = "dbo.MyTableType"; // Specify the table type
tvpParam.Value = dataRecords; // Pass the IEnumerable<SqlDataRecord>

// Add parameter to the command
command.Parameters.Add(tvpParam);

// Execute the command
connection.Open();
command.ExecuteNonQuery();
}
}
}

In this case, we create a SqlParameter with a based type (SqlDbType.Structured), assign it to the TVP kind (dbo.MyTableType), and skip the IEnumerable<SqlDataRecord> as the cost.

4. Handling Bulk Updates with IEnumerable<SqlDataRecord>

TVP may be used not handiest for bulk inserts however also for bulk updates. Below is an instance of a stored system for updating statistics.

SQL Server Stored Procedure for Update:

CREATE PROCEDURE dbo.UpdateMyTable
@MyTable MyTableType READONLY
AS
BEGIN
UPDATE MyTable
SET Name = src.Name, Quantity = src.Quantity
FROM MyTable tgt
INNER JOIN @MyTable src ON tgt.Id = src.Id;
END

This stored procedure updates current records based totally on the Id column.

C# Code for Bulk Update:
The C# code to handle updates is sort of equal to the insert instance, however you will call the UpdateMyTable system alternatively.

public void UpdateDataUsingTVP(IEnumerable<SqlDataRecord> dataRecords)
{
string connectionString = "Your SQL Server connection string here";

using (SqlConnection connection = new SqlConnection(connectionString))
{
using (SqlCommand command = new SqlCommand("dbo.UpdateMyTable", connection))
{
command.CommandType = CommandType.StoredProcedure;

SqlParameter tvpParam = new SqlParameter();
tvpParam.ParameterName = "@MyTable";
tvpParam.SqlDbType = SqlDbType.Structured;
tvpParam.TypeName = "dbo.MyTableType";
tvpParam.Value = dataRecords;

command.Parameters.Add(tvpParam);

connection.Open();
command.ExecuteNonQuery();
}
}
}

This process effectively updates more than one statistics in a single name, making it a great answer for bulk operations.

5. Performance Comparison

Here’s a short look at how this technique compares to standard row-by using-row insertion.

Traditional Row-through-Row Insertion:

public void InsertDataRowByRow(List<Product> products)
{
string connectionString = "Your SQL Server connection string here";

using (SqlConnection connection = new SqlConnection(connectionString))
{
connection.Open();

foreach (var product in products)
{
using (SqlCommand command = new SqlCommand("INSERT INTO MyTable (Id, Name, Quantity) VALUES (@Id, @Name, @Quantity)", connection))
{
command.Parameters.AddWithValue("@Id", product.Id);
command.Parameters.AddWithValue("@Name", product.Name);
command.Parameters.AddWithValue("@Quantity", product.Quantity);

command.ExecuteNonQuery();
}
}
}
}

TVP Insertion with IEnumerable<SqlDataRecord>:

public void InsertDataUsingTVP(IEnumerable<SqlDataRecord> dataRecords)
{
// Code as shown above for TVP insertion
}

For massive datasets, using TVP with IEnumerable<SqlDataRecord> drastically reduces the quantity of database calls, making it some distance more performant than row-by-row insertion.

Conclusion

Table-Valued Parameters (TVP), combined with IEnumerable<SqlDataRecord>, are a notably green way to carry out bulk facts operations among a C# utility and SQL Server. This approach minimizes database round journeys and may substantially decorate performance in information-intensive applications.

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

Md hasanuzzzaman
Md hasanuzzzaman

Written by Md hasanuzzzaman

Software Architect | Senior Software Engineer | Backend Developer | Tech Lead | Azure | ASP.NET | Blazor | C# | AI

No responses yet

Write a response