Here's how to use table typed arguments to author a stored procedure that inserts multiple master-detail records into tables that use identity columns for primary key. Consider the following Accounts and Transactions tables:
These tables represent a typical parent-child relationship where identity columns are used for the primary keys. Inserting records into such tables is usually problematic for performance, because client applications are required to perform multiple round-trips to the server... first inserting an account record, then retrieve its new primary key value, then using that value to insert a transaction record.
By using table types, we can define arguments to pass to a stored procedure that allow multiple account and transaction records to be described, without the caller needing to know what the identity values of the inserted records will be.
First, we'll define two table types which describe the records which we want our new stored procedure to handle:
Now we can create a stored procedure which takes arguments of these table types as follows:
The stored procedure we define will take both account and transaction table variables, and will perform the work of first inserting account records, then retrieving the new record's primary keys (the identity values), and then correlating those new identity values to the pseudo values given in the @Transactions argument. We will call this stored procedure as follows:
In the calling code (above), we define type typed variables and populate them with the records which we want inserted into the database Account & Transaction tables. Note that we are assigning pseudo values to the AccountID column, so as to describe which transactions correlate to which accounts.
With the above in mind, here's our stored procedure:
Here's what the above stored procedure is doing:
- @IdentityLink is declared as a table variable which store the inserted identity values for AccountID. We will use this table variable to later match transaction records to account records.
- We insert all account records, outputting the new identity values into the @IdentityLink variable.
- Update the @IdentityLink table variable so that every inserted AccountID value has a corresponding pseudo (submitted) AccountID value:
- We use a common table expression to define OrderedRows, which provides a correlation between the submitted pseudo AccountID values and the row order in which they appear in the submitted table type variable @Accounts.
- We join the submitted AccountID to the actual inserted AccountID value by using this row numbering. Put differently we're using the order in which records where submitted to correlate the inserted AccountID values back to the pseudo AccountID values.
- Insert records into the transaction table, using the @IdentityLink table variable to cross walk from the pseudo (submitted) AccountID values over to the actual identity values of the inserted Account records.
Perhaps the most interesting part of this technique is the support that's available for it in ADO.NET. The SqlCommand object in .NET allows developers to assign in-memory DataTables to the Value property of a SqlParameter – meaning that the above stored procedure can be called by a client application without writing any T-SQL code to populate the table variables before passing them as arguments to the stored procedure.