Use XSLT to Write a Simple Stored Procedure Generator

magine that you’ve just finished building a database for another Web-based system. Now it’s time to start thinking about writing all that SQL to move the data in and out of the database. Being a security-conscious developer, you decide against building dynamic SQL in your data-tier code. You choose to do the right thing and write stored procedures (SPs). Daunting task, isn’t it?

Stored procedures are much safer than dynamic SQL statements because the data coming from outside of the application is not mixed up with SQL DML (data manipulation language) statements. SPs pass the data as parameters, providing clear separation from the code:

spMySampleProc (@par1 int, @par2 nvarchar(100))

Writing stored procedures is not everyone’s cup of tea because of the amount of work they require, however. Suppose you have 10 tables with a total of 100 fields. You likely will end up with at least 10 SPs for inserting data, 10 SPs for modifying data, 10 SPs for deleting records, and every SP will have almost as many in-parameters as there are fields in the table. OK, shall we do some code generation then?


How do I manage the amount of time and work required to write stored procedures from scratch?


Use XSLT to produce simple stored procedure boilerplates.

SP Design Model

 
Figure 1. Dog Registration Database

Most Web-based applications use the back-end DB in a quite straightforward way. Consider this use case: a user submits some data (name, address, phone number, etc.) that is processed by some business logic and then stored in the database. Later, someone asks for a report on the user’s details and the data is retrieved and presented. The user may come back and change the details or sign out, resulting in the deletion of the user records altogether.

Look at the sample Dog Registration Database shown in Figure 1. Get all DDL for the DB here.

The initial set of stored procedures for the tAnimalOfficer table may look like this:

CREATE PROCEDURE spNewtAnimalOfficer (@OfficerID nvarchar(255), @Name  nvarchar(255), @StationName  nvarchar(255), @TerritorialAuthorityID  int)ASINSERT INTO tAnimalOfficer (OfficerID, Name, StationName, TerritorialAuthorityID)VALUES (@OfficerID, @Name, @StationName, @TerritorialAuthorityID)GOCREATE PROCEDURE spUpdatetAnimalOfficer (@OfficerID  nvarchar(255), @Name  nvarchar(255), @StationName  nvarchar(255), @TerritorialAuthorityID  int)ASIF @Name IS NOT NULL UPDATE tAnimalOfficer SET [email protected] WHERE [email protected] @StationName IS NOT NULL UPDATE tAnimalOfficer SET [email protected] WHERE [email protected] @TerritorialAuthorityID IS NOT NULL UPDATE tAnimalOfficer SET [email protected] WHERE [email protected] PROCEDURE [dbo].[spDeletetAnimalOfficer] (@OfficerID  nvarchar(255))ASDELETE FROM tAnimalOfficer WHERE [email protected]

Your other SPs for this DB are not going to be much different because they all share the same underlying idea: all are plain insert, update, or delete statements.

SQL Server Metadata Views

The next step is to get the list of tables and their columns from SQL Server and then somehow generate similar SPs. SQL server has a number of views and tables for this. To cut a long story short, try this statement:

select * from sys.objects

Anything with type=’U’ is a user table. User tables that have is_ms_shipped=1 are in fact system tables that the server itself generates and those with is_ms_shipped=0 are the ones you created. However, I noticed that the is_ms_shipped flag is not always set correctly. The following is the SQL query to get a list of user tables for SP generation:

select * from sys.objects where type='U' and is_ms_shipped=0

The list of columns sits in the information_schema.columns view. Use this SQL statement to retrieve the basic information you need to generate the rest of the SPs:

select * from information_schema.columns where table_name='tAnimalOfficer' order by ordinal_position

.NET Code

All you need the .NET code to do is go into the database, get a list of tables, and then get a list of columns for every table and apply an XSLT file to generate those SP boilerplates. The trick is to get the data from SQL Server in XML. One of the ways to do that is to use the MS SQLXML component, which builds XML on the client side and returns an XmlReader object quickly and efficiently.

First of all, get a list of the tables in question with the following function:

  Private Shared Function GetTables(ByVal xCS As String) As XmlReader    Dim strCom As String = "select * from sys.objects where type='U' and is_ms_shipped=0 for xml raw"    Dim sqlCom As New SqlXmlCommand(xCS)    sqlCom.CommandText = strCom : sqlCom.ClientSideXml = True    sqlCom.CommandType = SqlXmlCommandType.Sql : sqlCom.RootTag = "tables"    Return sqlCom.ExecuteXmlReader  End Function

The function will return XML data that looks like this:

<tables>	<row name="tDog" object_id="117575457" schema_id="1"parent_object_id="0" type="U " type_desc="USER_TABLE" _
create_date="2005-12-04T21:11:48.530" modify_date="2005-12-04T21:18:09.403" is_ms_shipped="0" _
is_published="0" is_schema_published="0"/> <row name="tOwner" object_id="165575628" schema_id="1" parent_object_id="0" type="U " type_desc="USER_TABLE" _
create_date="2005-12-04T21:14:38.810" modify_date="2005-12-04T21:17:49.403" is_ms_shipped="0" _
is_published="0" is_schema_published="0"/> <row name="tAnimalOfficer" object_id="277576027" schema_id="1" parent_object_id="0" type="U " type_desc="USER_TABLE" _
create_date="2005-12-04T21:18:09.310" modify_date="2005-12-04T21:18:09.403" is_ms_shipped="0" _
is_published="0" is_schema_published="0"/> <row name="sysdiagrams" object_id="2089058478" schema_id="1" parent_object_id="0" type="U " type_desc="USER_TABLE" _
create_date="2005-12-04T21:06:54.200" modify_date="2005-12-04T21:06:54.207" is_ms_shipped="0" _
is_published="0" is_schema_published="0"/>tables>

You produce a list of columns in a similar way:

  Private Shared Function GetColumns(ByVal xCS As String, ByRef TableName As String) As XmlReader    Dim strCom As String = "select * from information_schema.columns where table_name='" + TableName + "' order by ordinal_position for xml raw"    Dim sqlCom As New SqlXmlCommand(xCS)    sqlCom.CommandText = strCom : sqlCom.ClientSideXml = True    sqlCom.CommandType = SqlXmlCommandType.Sql : sqlCom.RootTag = "columns"    Return sqlCom.ExecuteXmlReader  End Function

The function returns the following:

<columns>	<row TABLE_CATALOG="DevX" TABLE_SCHEMA="dbo" TABLE_NAME="tAnimalOfficer" COLUMN_NAME="OfficerID" ORDINAL_POSITION="1"  _
IS_NULLABLE="NO" DATA_TYPE="nvarchar" CHARACTER_MAXIMUM_LENGTH="255" CHARACTER_OCTET_LENGTH="510" CHARACTER_SET_NAME="UNICODE" _
COLLATION_NAME="SQL_Latin1_General_CP1_CI_AS"/> <row TABLE_CATALOG="DevX" TABLE_SCHEMA="dbo" TABLE_NAME="tAnimalOfficer" COLUMN_NAME="Name" ORDINAL_POSITION="2" _
IS_NULLABLE="YES" DATA_TYPE="nvarchar" CHARACTER_MAXIMUM_LENGTH="255" CHARACTER_OCTET_LENGTH="510" CHARACTER_SET_NAME="UNICODE" _
COLLATION_NAME="SQL_Latin1_General_CP1_CI_AS"/> <row TABLE_CATALOG="DevX" TABLE_SCHEMA="dbo" TABLE_NAME="tAnimalOfficer" COLUMN_NAME="StationName" ORDINAL_POSITION="3" _
IS_NULLABLE="YES" DATA_TYPE="nvarchar" CHARACTER_MAXIMUM_LENGTH="255" CHARACTER_OCTET_LENGTH="510" CHARACTER_SET_NAME="UNICODE" _
COLLATION_NAME="SQL_Latin1_General_CP1_CI_AS"/> <row TABLE_CATALOG="DevX" TABLE_SCHEMA="dbo" TABLE_NAME="tAnimalOfficer" COLUMN_NAME="TerritorialAuthorityID" ORDINAL_POSITION="4" _
IS_NULLABLE="YES" DATA_TYPE="int" NUMERIC_PRECISION="10" NUMERIC_PRECISION_RADIX="10" NUMERIC_SCALE="0"/>columns>

Glue these two functions together with this code:

  Public Shared Function GenratedSPs(ByRef XsltFileName As String, ByRef DbConnectionString As String) As String    Dim oStringWriter As New IO.StringWriter    oStringWriter.NewLine = vbCrLf     Dim xslDoc As New Xml.Xsl.XslTransform     Dim xslResolver As New Xml.XmlUrlResolver     xslDoc.Load(XsltFileName)     Dim xTables As Xml.XmlReader = GetTables(DbConnectionString)    Do While xTables.Read      If xTables.LocalName = "row" Then         Dim sTableName As String = xTables.GetAttribute("name")         Dim xColumns As Xml.XmlReader = GetColumns(DbConnectionString, sTableName) '        Dim xC As New Xml.XPath.XPathDocument(xColumns)         Dim stOut As New IO.StringWriter         xslDoc.Transform(xC, Nothing, stOut, xslResolver)         oStringWriter.Write(stOut.ToString)         oStringWriter.WriteLine()       End If    Loop    oStringWriter.Flush()     Return oStringWriter.ToString   End Function 

The string returned by this last function contains the text for all the SPs it has generated. You will need to paste it in your MS SQL Management Studio Query Designer, check it, modify it where needed, and then run it. This is still a bit of manual work, but just think how much time it would take to type all those parameters and declarations from scratch.

The sample XSLT file included with the full source code download for this article is designed to deal with the XML formats shown above and produce simple SP boilerplates as described in the SP Design Model section.

Customization

The .NET code is unlikely to need any customization?it does what it does. The actual SP text is in the XSLT. So, if you prefer a different SP boilerplate, you need to look into the XSLT file.

Limitations

Keep in mind that this is only a 10-Minute Solution (even if I did spend half a day getting it all going), so has its limitations:

  • Not all SQL data types are used by the XSLT.
  • The very first field in the table is considered to be the Primary Key.
  • No composite keys are considered.
  • No special constraints are considered (e.g., usually you cannot do an insert into an identity column).

Share the Post:
Share on facebook
Share on twitter
Share on linkedin

Overview

The Latest

your company's audio

4 Areas of Your Company Where Your Audio Really Matters

Your company probably relies on audio more than you realize. Whether you’re creating a spoken text message to a colleague or giving a speech, you want your audio to shine. Otherwise, you could cause avoidable friction points and potentially hurt your brand reputation. For example, let’s say you create a

chrome os developer mode

How to Turn on Chrome OS Developer Mode

Google’s Chrome OS is a popular operating system that is widely used on Chromebooks and other devices. While it is designed to be simple and user-friendly, there are times when users may want to access additional features and functionality. One way to do this is by turning on Chrome OS

homes in the real estate industry

Exploring the Latest Tech Trends Impacting the Real Estate Industry

The real estate industry is changing thanks to the newest technological advancements. These new developments — from blockchain and AI to virtual reality and 3D printing — are poised to change how we buy and sell homes. Real estate brokers, buyers, sellers, wholesale real estate professionals, fix and flippers, and beyond may