Key take away:
In my last posts, I have described ways to insert multiple rows worth of data in a SQL Server database via a Comma Separated List and XML. In this post I will describe how to insert multiple rows from a .Net application using the most preferred way, a native feature in SQL Server, Table Valued Parameters.
Read on:
A short introduction to Table Valued Parameters:
Parameters are place holder for values that are passed from various constructs outside of a programming artifact. We declare parameters all the time, in various technologies, and in various forms - command line arguments, query string values in ASP.NET, sql parameters that carry information to database in a type safe way, etc. In SQL Server parameters are the way to pass and receive information that needs to be dealt with, and information can come from either within SQL Server or outside of SQL Server like from a .Net application. Parameters are generally made of a primal data type like int, char, varchar etc. When such parameters are modeled on some pre-defined table type, such parameters are called Table Valued Parameters. The important words to note here are “table type”. Not table, but table type, which means a user defined type based on some table structure.
Table Valued Parameters have been supported by SQL Server since version 2008.
To demonstrate the concept of TVPs, I will be using a very simple data model consisting of three tables: Student, Course and an association table supporting many-to-many relationship between Student and Course, StudentCourse.
The Entity-Relationship diagram will clarify the relationship between the tables:
ERD diagram:
According the ER diagram, a student can take many courses and a course can be taken by many students. The insertion of courses that a student is interested in the association table is an ideal application of this technique.
Now consider the following code to make a table type. this type will be used to model a table valued parameter on the table type:
1: CREATE TYPE StudentCoursesTableType AS Table
2: ([StudentID] [int] NULL,
3: [CourseID] [int] NULL);
Making a table type makes the type available in the User-Defined Table Types sub-section of Types section.
Execution of the following code should give you some result depending on what you have available in the StudentCourse table.
1: Declare @StudentCoursesTVP as StudentCoursesTableType;
2:
3: Insert into @StudentCoursesTVP(StudentID, CourseID)
4: Select [StudentID], [CourseID] from [dbo].[StudentCourse];
5:
6: Select * from @StudentCoursesTVP;
So now that we are through with the basics of TVP, let’s see how we can use them in our main purpose of processing multiple rows of data. To demonstrate this, I will be using a simple web form application powered by the same data model discussed above. The application helps a student get enroll in the course(s) of his/her choice.
The coding for the webpage is very simple. The pseudo-code is as follows:
1. Select a student from the drop down.
2. Select from the available courses that the student needs enrollment for.
3. Click on the submit button.
I am going to leave it up to the reader to understand the programming in the web application. It is straight forward and the domain model powering the application is a reflection of the data model depicted above.
The main work is being done at two places:
1. Web application’s repository method which parses the incoming object data into a datatable.
2. The stored procedure responsible for receiving the data in the form of a table valued parameter and using it to feed data into the desired table.
Consider the following repository method in the StudentSQLRepository class:
1: public int GetEnrolled(List<Course> courses, int studentID)
2: {
3: DataTable table = new DataTable("data");
4: table.Columns.Add("StudentID");
5: table.Columns.Add("CourseID");
6: foreach (Course course in courses)
7: {
8: table.Rows.Add(new object[] { studentID, course.CourseID});
9: }
10: string sql = @"dbo.EnrollStudentInCourses";
11: int result = 0;
12: SqlParameter studentCoursesTVP = new SqlParameter("StudentCoursesTVP", table);
13: studentCoursesTVP.SqlDbType = SqlDbType.Structured;
14: using (SqlConnection connection = new SqlConnection(connectionString))
15: {
16: using (SqlCommand command = new SqlCommand(sql, connection))
17: {
18: command.CommandType = CommandType.StoredProcedure;
19: command.Parameters.Add(studentCoursesTVP);
20:
21: connection.Open();
22: result = command.ExecuteNonQuery();
23: connection.Close();
24: }
25: }
26: return result;
27: }
There are two noteworthy points in the code above:
1. We are passing the datatable to the SQL Server database to be processed. The datatable is modeled on the table type present in the SQL Server database, used by the table valued parameter.
2. The SqlDbtype of the SqlParameter is set to Structured type. This is strictly done to improve the readability of our code and convey meaning in an explicit way. ADO.NET will perfectly parse the datatable correctly had we not declared the SqlDbType to Structured explicitly.
Following is the SQL script of the dbo.EnrollStudentInCourses responsible for parsing the data in the @StudentCoursesTVP table valued parameter correctly.
1: CREATE PROCEDURE [dbo].[EnrollStudentInCourses]
2:
3: @StudentCoursesTVP StudentCoursesTableType readonly
4:
5: AS
6: BEGIN
7:
8: Insert into StudentCourse (StudentID, CourseID)
9: Select StudentID, CourseID from @StudentCoursesTVP
10:
11: END
12:
13: GO
If you go and read my two other posts on inserting multiple rows using a comma separated values list and XML, you surely will come to the conclusion that using table valued parameters is the cleanest approach, that doesn’t include any code acrobatics either in .Net code or in SQL code. Not only that, table valued parameters code is highly performant as long as there’s reasonable data involved.
The sample code for this post can be downloaded from the following location:
References:
1. Table Valued Parameters at TechNet