![]() |
Database »
Database »
Utilities
Beginner
License: The GNU Lesser General Public License
A Tool to Compare SQL Database Schema VersionsBy Lindsey LewisAn article about a tool for comparing SQL database schema versions |
XML, SQL, XSLT, C# 2.0.NET 2.0, WinXP, Win2003, Vista, Win2008SQL 2000, SQL 2005, Architect, DBA, Dev, QA, Design
|
|
Advanced Search Add to IE Search |
|
|
|
||||||||||||||||
The challenge of developing code for any small or large development project that uses a SQL database, is that often the code and database schema change or get modified over the duration of the SDLC from what was previously delivered to the customer.
It is not always easy to ensure that the previously delivered schema stays in sync with the code unless rigorous project process controls are in place. I have worked on several projects, and every one had a different approach to ensuring that code and database schema stay in sync.
This tool which I call the SQL Schema Tool or SST, was created to assist in handling the problems of comparing or providing update scripts that work against the issue of synchronizing a customer database schema from a previous version when migrating to a new version.
One way that we can deliver schema changes for an existing database, is to create a new database with the updated schema and then use some ETL process to migrate data from the old database into the new one. However my experience with customers is that they are reluctant to have to swap out a production database for an entirely new one.
My approach to the problem using SST, was to provide a way to capture the delivered customer's schema as an XML snapshot that describes the schema. This snapshot of the customer's database schema can then be compared to the current development database schema without requiring active connections and provide an update SQL script to run against the customer database.
insert statements I worked on the QueryCommander SQL Editor project at one point. Some of the design approaches used in SST come from my experiences with working in code base.
The SQL Schema Tool or SST started life as a command line project to get DTS packages out of the database as XML. Additional code was added later to get schema changes made against development databases that did not exist in the QA databases and create update scripts to run against QA databases during the continuous build process.
The schema tool was then turned into a common set of classes that could be called from either a command line or a Winform GUI application. I had decided to try and sell the software, but never seemed to generate much interest. Therefore, I am writing this article and giving my code away under the attached license as well as any license used by the libraries contained within the code.
The code was written and tested against Microsoft SQL Server 2000 and SQL Server 2005. I have separated the classes such that it should be an easy modification to add support for other vendor databases, such as MYSQL or DB2, Oracle; although the DTS Package classes would only work with SQL 2000.
Architecture of the schema module has been designed to deal with the database at the object level. This allows individual groups of objects to be selected for the compare or generate schema operations. It also allows the user of the application to narrow their focus to the exact objects of a database that are in flux, or changing often.
When you examine the output of the XML snapshot, you can see this approach by noting the XML nodes or elements are grouped by object type, and sorted by object name.
All principal output from the schema module is in the form of XML, that is in turn transformed into the appropriate output, by the use of XSLT and XML transformations.
The SQL Schema Tool is designed to consider a master or source database, which is the current or latest database schema for use in a project. The destination or target database is considered by SST to be the older schema, or the schema that needs updating.
Shown below is a simplified sequence diagram of the SQLSchemaTool class.
The SQLSchemaTool class uses the SQLObjects namespace and the classes: Tables, Views, Sprocs, etc. that serialize the schema of those objects as XML. The code for the objects in the database makes use of Generics where appropriate.
public static string SerializeDB(
string SQLServer,
string DBName,
string UID,
string PWD,
string SQLfile,
bool Translate,
bool Primary,
object threaded,
byte objectsToSerialize,
string CustomXSLT,
string delimTableNames)
{
_threaded = threaded;
string _serverDB = SQLServer + ":" + DBName;
string outputFile = string.Format
(_OUTPUTFILE, SQLServer.Replace("\\", "_").Replace(":", "-"),
DBName.Replace("\\", "_").Replace(":", "-"));
try
{
// TODO: add threads if this takes a long while
SQLMethods.SQLConnections _connections;
if (UID != null && PWD != null)
{
_connections = new SQLMethods.SQLConnections
(SQLServer, DBName, UID, PWD, false);
}
else
{
_connections = new SQLMethods.SQLConnections(SQLServer, DBName);
}
if (_connections != null && _connections.Count > 0)
{
DataSet _ds = new DataSet("DataBase_Schema");
_ds.EnforceConstraints = false;
DataTable dt = _ds.Tables.Add("Database");
dt.Columns.Add("Name");
dt.Columns.Add("Date");
dt.Columns.Add("Time");
DataRow dr = dt.NewRow();
dr.ItemArray =
new object[] { DBName, DateTime.Now.ToShortDateString(),
DateTime.Now.ToShortTimeString() };
dt.Rows.Add(dr);
Sleep();
// get defaults, rules and UDDTs :
// in this order because of dependant behavior
if ((objectsToSerialize & Convert.ToByte(_NodeType.DEFAULT)) ==
(int)_NodeType.DEFAULT)
{
SQLObjects.Defaults _defaults = new SQLObjects.Defaults(DBName);
_defaults.GetObject<sqlobjects.defaults>(_connections[0].sqlConnection);
_ds.Merge(_defaults);
}
Sleep();
if ((objectsToSerialize & Convert.ToByte(_NodeType.RULE)) ==
(int)_NodeType.RULE)
{
SQLObjects.Rules _rules = new SQLObjects.Rules(DBName);
_rules.GetObject<sqlobjects.rules>(_connections[0].sqlConnection);
_ds.Merge(_rules);
}
Sleep();
if ((objectsToSerialize & Convert.ToByte(_NodeType.UDDT)) ==
(int)_NodeType.UDDT)
{
SQLObjects.UDDTs _uddts = new SQLObjects.UDDTs(DBName);
_uddts.GetObject<sqlobjects.uddts>(_connections[0].sqlConnection);
_ds.Merge(_uddts);
}
Sleep();
if ((objectsToSerialize & Convert.ToByte(_NodeType.TABLE)) ==
(int)_NodeType.TABLE)
{
SQLObjects.Tables _tables = new SQLObjects.Tables(DBName);
if (!string.IsNullOrEmpty(delimTableNames))
{
_tables.GetObject<sqlobjects.tables>(_connections[0].sqlConnection,
Primary, delimTableNames);
}
else
{
_tables.GetObject<sqlobjects.tables>
(_connections[0].sqlConnection, Primary);
}
// TODO: make work with DBs attached as MDF files to SQL 2005
_ds.Merge(_tables);
}
Sleep();
if ((objectsToSerialize & Convert.ToByte(_NodeType.VIEW)) ==
(int)_NodeType.VIEW)
{
SQLObjects.Views _views = new SQLObjects.Views(DBName);
_views.GetObject<sqlobjects.views>(_connections[0].sqlConnection);
_ds.Merge(_views);
}
Sleep();
if ((objectsToSerialize & Convert.ToByte(_NodeType.SPROC)) ==
(int)_NodeType.SPROC)
{
SQLObjects.Sprocs _sprocs = new SQLObjects.Sprocs(DBName);
_sprocs.GetObject<sqlobjects.sprocs>(_connections[0].sqlConnection);
_ds.Merge(_sprocs);
}
Sleep();
if ((objectsToSerialize & Convert.ToByte(_NodeType.FUNCTION)) ==
(int)_NodeType.FUNCTION)
{
SQLObjects.Funcs _funcs = new SQLObjects.Funcs(DBName);
_funcs.GetObject<sqlobjects.funcs>(_connections[0].sqlConnection);
_ds.Merge(_funcs);
}
Sleep();
if ((objectsToSerialize & Convert.ToByte(_NodeType.TRIGGER)) ==
(int)_NodeType.TRIGGER)
{
SQLObjects.Triggers _triggers = new SQLObjects.Triggers(DBName);
_triggers.GetObject<sqlobjects.triggers>(_connections[0].sqlConnection);
_ds.Merge(_triggers);
}
// TODO: add jobs, users, roles
Sleep();
// get rid of old files
if (File.Exists(outputFile)) File.Delete(outputFile);
// write out xml schema document
XmlDataDocument xmlData = new XmlDataDocument(_ds);
//xmlData.Save(outputFile);
// reload to xml schema to avoid the "deleted row" error
// when removing the dependant child nodes
XmlDocument xmlDoc = new XmlDocument();
//xmlDoc.Load(outputFile);
xmlDoc.LoadXml(xmlData.OuterXml);
Sleep();
// sort the dependencies for views, functions, and stored procedures
SortDependencies(_serverDB, VIEWPATH, VIEWDEP, ref xmlDoc);
SortDependencies(_serverDB, FUNCPATH, FUNCDEP, ref xmlDoc);
SortDependencies(_serverDB, SPROCPATH, SPROCDEP, ref xmlDoc);
foreach (Char c in Path.GetInvalidFileNameChars())
{
outputFile = outputFile.Replace(c, '_');
}
foreach (Char c in Path.GetInvalidPathChars())
{
outputFile = outputFile.Replace(c, '_');
}
xmlDoc.Save(outputFile);
// perform garbage collection to free up memory
GC.Collect();
if (Translate && outputFile != null && outputFile.Trim().Length > 0)
{
string createName = outputFile.ToLower().Replace(".xml", ".sql");
if (SQLfile != null && SQLfile.Length > 0)
{
createName = SQLfile.ToLower().Replace(".xml", ".sql");
}
if (!createName.EndsWith(".sql")) { createName += ".sql"; }
XsltHelper.SQLTransform(outputFile,
XsltHelper.SQLCREATEXSLT, createName);
outputFile += "," + createName;
logger.Info("\nSQL Create Schema has been saved to " +
createName + ".");
}
if (CustomXSLT != null && CustomXSLT.Trim().Length > 0)
{
FileInfo fi = new FileInfo(CustomXSLT);
File.WriteAllText("CustomOutput.XML",
XsltHelper.Transform(xmlDoc.OuterXml, fi));
logger.Info("\nThe Custom XSLT {0},
has been applied and saved as
CustomOutput.XML.", CustomXSLT);
}
}
}
catch (Exception ex)
{
if (ex is System.Data.SqlClient.SqlException)
{
logger.Error("\nSQL Error: {0}, DB Server {1}",
ex.Message, _serverDB);
}
else
{
logger.Error(ERRORFORMAT, ex.Message, ex.Source, ex.StackTrace);
}
}
return outputFile;
}
Once the objects of the database are serialized as XML, then the code can perform XSLT transformations against the XML to make SQL, or other output.
Additionally, if there are two such XML representations of databases, those serialized databases can be compared. The results of that comparison are output as XML which again can be transformed into the necessary SQL to update the target database.

Here is a screen shot of the XML output from the tool:
Here is a screen shot of the SQL output from the tool after the XSLT transformation get applied:
Here is a screen shot of the HTML difference report created from the tool after the XSLT transformation get applied:
The build process uses the ILMerge to merge the project's DLLs together into a single assembly in the pre/post build commands. See Microsoft Research for the download of this tool. This makes the deployment installer much easier to design.
For the SQLSchemaToolGUI project, edit the pre and post build events which are accessed from the project properties.
There is an initial line for both pre and post build events that has:
del "$(TargetDir)merge.log"
That line should be changed to:
IF EXIST "$(TargetDir)merge.log" del "$(TargetDir)merge.log"
This is required because I first ran the merge.bat file from the commandline, which creates the merge.log file. So I never thought about the fact that the log file would not be there.
The log file is the output log of the .NET iLMerge tool, which the batch file is using to merge all the individual DLLs into the single SSTassemblies.dll file.
ICSharpCode.TextEditor.
General
News
Question
Answer
Joke
Rant
Admin
|
PermaLink |
Privacy |
Terms of Use
Last Updated: 26 Feb 2009 Editor: Deeksha Shenoy |
Copyright 2009 by Lindsey Lewis Everything else Copyright © CodeProject, 1999-2009 Web22 | Advertise on the Code Project |