Click here to Skip to main content
Click here to Skip to main content

Automatic Script SQL Server 2005 Objects and Commit under Subversion

By , 9 Oct 2009
 

Introduction

Recently we changed our source control from Sourcesafe (I know it's prehistoric!) to SubVersion and except for some issues (mainly training ones), we are pretty happy with it. I really like the branching and merging functionalities that give us a big help in our deployment process.

Unfortunately this helps us only for the C# code but because our application is Database-Centric and a lot of our business logic is in tables, views and stored procedures, effectively everything is not under source control, and even more important, we have no history of changes.

The first solution I suggested to solve this issue was to buy Microsoft Visual Studio 2008 Database Edition that effectively give us a very powerful environment to handle the database changes like C# project and have a lot of functionalities like database reverse engineering, schema and data comparison. The first issue with this solution is that Microsoft Visual Studio 2008 Database Edition (VSDE) is strongly linked to Team Foundation Server (TFS), and TFS is not really cheat.

The second issue is that the developers need to change the way they work with databases. In fact, instead of opening SQL Management Studio and just changing the database directly, with VSDE, they will need to create the script to alter the database and then run it against a local temporary database. To be honest, this seems to me the best way to work but it could be an issue for some developers.

The second solution I suggested to solve the issue was to periodically script the database objects and commit these scripts into SubVersion.

This solution will give me the history about database changes without changing the way we work with databases. This process can be automatic and I can setup on the server machine, using scheduler tasks, to run periodically every 30 minutes. If you like to have more granularity on your history, you should setup the scheduler to run every 15 minutes or less. Consider that if the database has not changes, the process is not going to commit anything.

This tutorial will explain how to create this automatic process.

Background

When I start to investigate on this issue, I was looking for a command line tool that will give me the ability to script all the database objects. I found a lot of nice open source projects that do this in different ways:

Unfortunately scripting the entire database every time for a big project is very slow and for this reason, I tried to create a custom console application that using Microsoft.SqlServer.Management.Smo scripts only the differences between the current run and the previous one.

The Implementation

The console application is divided in two classes:

  • Program.cs: Entry point of the application that handles the input parameters and calls the helper class to script the database objects.
  • ScripterHelper.cs: The helper class that uses Microsoft.SqlServer.Management.Smo scripts the database objects.

The helper class stores the last modified object date in a text file named <Server>-<Database Name>.txt under the application's path. This gives the ability to the class for the next run to detect the changed objects and script just the differences. Embedded into the helper class I implemented a logic to add/delete script files for new/deleted objects in the database.

In this way, using a simple batch file, it is easy to automatically commit the changes in SubVersion.

The most interesting part of the code is how to use Microsoft.SqlServer.Management.Smo to script the database objects.

The class used to do that is Scripter:

Server server = new Server("(local)"); // Server name of the database to script
Database db = server.Databases["AdventureWorks"] 	// Database name of the 
						// database to script

Scripter scripter = new Scripter(); // Class script the database objects
scripter.Server = server;
scripter.Options.IncludeHeaders = true;
scripter.Options.SchemaQualify = true;
scripter.Options.SchemaQualifyForeignKeysReferences = true;
scripter.Options.NoCollation = true;
scripter.Options.DriAllConstraints = true;
scripter.Options.DriAll = true;
scripter.Options.DriAllKeys = true;
scripter.Options.DriIndexes = true;
scripter.Options.ClusteredIndexes = true;
scripter.Options.NonClusteredIndexes = true;
scripter.Options.ToFileOnly = true;

foreach (Table table in db.Tables) // Script all tables
{
   if (!table.IsSystemObject) // Skip the system objects
   {
      scripter.Options.FileName = 
	Path.Combine("C:\Scripts", table.Name + ".sql"); // Filename of the script
      scripter.Script(new Urn[] { table.Urn }); // Object to script
   }
}

The integration with SubVersion is done with a small batch file:

rem Script the database objects AdventureWorks under the 
	local SQL Server into the folder C:\Scripts
SQLScripter.exe (local) AdventureWorks "C:\Scripts"

rem Change current Folder
C:\Scripts
rem Use for command to detect the file status and eventually 
	add or delete the files from svn
for /f "tokens=2*" %%i in ('svn status ^| find "?"') do svn add %%i
for /f "tokens=2*" %%i in ('svn status ^| find "!"') do svn delete %%i

rem Commit the changes in svn
svn commit -m "Automatic commit" "C:\Scripts"

Using the Code

The console application accepts the following parameters:

  • Server (Host of the database to script)
  • Database Name (Database name of the database to script)
  • Scripts Path (Path where the application will store the scripts)
  • Object Types (The filter parameter to restrict the object types will be scripted: t=Tables,v=Views, s=Stored Procedures, a=User Defined Aggregates, f=User Defined Functions, Empty=All)

The following screenshots show the result structure created and the tables folder for AdventureWorks database:

Screenshot2.jpg

Screenshot3.jpg

Points of Interest

This solution can be improved to script the data of the lookup tables and put it under source control. Another idea could be to use it to generate a deployment script and simplify the deployment process.

Any suggestions are welcome! :)

History

  • 09 October 2009 - First release 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Ferreri Gabriele (Megasoft78)
Software Developer (Senior) sparesFinder
Italy Italy
Member
I'm an Italian Software Developer from about 15 years.
I worked a long time in south Italy (where I was born) and after 2 years in Milan and an year in UK, I'm working remotely from Italy as Senior ASP.NET C# Developer using ASP.NET Ajax technology for a UK company.
 
Check out my personal blog:
http://megasoft78.wordpress.com/
 
and my first Android game (Fifteen Puzzle X):
https://play.google.com/store/apps/details?id=it.megasoft78.fifteenpuzzlex

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralHow to use this code ? [modified]membermaboharb18 Jul '12 - 6:54 
Thank you for the code, i did change the server name and database name and the path, How is going to work ? ican't see where the " SQLScripter.exe " are when i download it the code ?

modified 18 Jul '12 - 13:37.

GeneralRe: How to use this code ?memberFerreri Gabriele (Megasoft78)18 Jul '12 - 8:24 
Hi,
to use SQLScripter.exe you need to compile the code with Visual Studio.
 
Cheers,
Gabriele
Your mind is like a parachute. It works best when open.

QuestionRequest to provide encoding as argumentmemberChris Rokusek10 Apr '12 - 11:56 
Request to pass through an 'encoding' argument into the script.Options.Encoding for each of the files. This is helpful if the source control system treats unicode files as binary and one must use ASCII for the near term. Thanks for your consideration.
AnswerRe: Request to provide encoding as argumentmemberFerreri Gabriele (Megasoft78)10 Apr '12 - 19:21 
Hi Chris,
thank you for all your suggestions.
 
Best Regards,
Gabriele
Your mind is like a parachute. It works best when open.

QuestionGreat stuff, request to control use of timestamp filememberChris Rokusek10 Apr '12 - 9:26 
Thanks for this effort. In case anyone updates the source, we added an [changes|all] argument to specify whether or not to read from the previous timestamp file (a new file is generated regardless).
 
Syntax : SqlScripter.exe
<Server>
<Database Name>
<Scripts Path>
[changes|all]
[t=Tables,v=Views, s=Stored Procedures, a=User Defined Aggregates, f=User Defined Functions, Empty=All]
QuestionServer name with embedded slashmemberChris Rokusek9 Apr '12 - 11:24 
Our server name includes a slash which was causing the timestamp file write to fail. Changed the ScripterHelper.cs:~49 as follows:
 
_lastUpdateFilename = Path.Combine(
    Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location),
    serverName.Replace("\\", "_") +     // replace slash with '_' in server name
    "-" +
    databaseName +
    ".txt");
 
We also made the SetDefaultInitFields() change mentioned in other comments to speed up.
AnswerRe: Server name with embedded slashmemberWernerCD1 May '12 - 7:02 
+1 First thing I ran into myself. Was going to do something similar.
QuestionSQL 2008?memberScottPiersallJax17 Oct '11 - 4:13 
Is there any way to modify this to work with SQL 2008?
AnswerRe: SQL 2008?memberFerreri Gabriele (Megasoft78)17 Oct '11 - 5:55 
Hi,
most of the work is done by Microsoft.SqlServer.Management.Smo and I think it should work fine with 2008 too.
Have you tried?
 
Cheers,
Gabriele
Your mind is like a parachute. It works best when open.

QuestionSuggestionmemberVijay Chandra Sekhar Parepalli20 Sep '11 - 18:48 
It would have been better to have SVN check-in on behalf of the DB-User who made the change so it would be easier to track the respective change-owner. Appreciate if you can share your ideas on implementing this.
 
Thanks
 
VC
AnswerRe: SuggestionmemberFerreri Gabriele (Megasoft78)20 Sep '11 - 22:34 
Hi,
I'm not sure but I think SQL Server 2005 doesn't hold that information about who made that change.
Unless I'm wrong and that information is in SQL Server 2005 I can't see how to do that.
Actually in our company we are planning to use a product from RedGate called SQL Source Control that it should make everything easier but we haven't test it yet.
 
Best Regards,
Gabriele
Your mind is like a parachute. It works best when open.

QuestionSQL Scripter do not generating GO statement while writing to file....memberPrasanth.PV18 Aug '11 - 0:50 
Hi Folks,
 
I need to generate an sql script for a database directly to a file.
For some instance, i am not setting the Filename property of the Scripter.Options class.
Rather I store the list of Urn to a stirng List and later on writing to a file
using the StreamWriter Class. The file is writing successfully but on executing the script,
some errors are found.
 
ERROR:
CREATE TRIGGER must be the first statement in a query batch.
CREATE/ALTER PROCEDURE must be the first statement in a query batch.
 
The file trying to execute is not generating the "GO" statement between the query batch.
Is it the problem for error???
 
[The above senario works fine if i do it using the FileName property of Scripter class,
since it generates the "GO" statement by default]
AnswerRe: SQL Scripter do not generating GO statement while writing to file....memberFerreri Gabriele (Megasoft78)18 Aug '11 - 2:55 
I'm not sure I understood your issue but if what you want is to have a single file with all the scripts, than you can generate them separatelly and merge in a single file using a simple procedure that iterate under a specific folder, read all files and merge in a single one. In that case you can add the go statement in your procedure. Be aware that this is not going to work if you don't script them in the right order because of foreign keys.
 
Cheers,
Gabriele
Your mind is like a parachute. It works best when open.

GeneralRe: SQL Scripter do not generating GO statement while writing to file....memberPrasanth.PV25 Aug '11 - 7:08 
Hi Gabriele,
Thank you for your time and interest..
Problem was solved. Smile | :)
GeneralAdded functionality so if arg[1] is *, loops through all databasesmemberTamusRoyce14 Jan '11 - 9:38 
First, I have to thank you for the great program you have designed. It helps tremendously to be able to see revisions into the past.
 
I figured I would leave a comment on a small feature I added.
 
string serverName = args[0];
string databaseName = args[1];
string path = args[2];
string sqlObjectTypes = "tvsaf";
 
if (args.Length == 4)
  sqlObjectTypes = args[3];
 
if (databaseName == "*")
{
  Server _server = new Server(serverName);
 
  foreach (Database db in _server.Databases)
  {
    if (!db.IsSystemObject)
    {
      ScriptDatabase(serverName, db.Name, path + "\\" + db.Name, sqlObjectTypes);
    }
  }
} else {
  ScriptDatabase(serverName, databaseName, path, sqlObjectTypes);
}
 
static void ScriptDatabase(string serverName, string databaseName, string path, string sqlObjectTypes)
{
  /* Performs the normal stuff with try/catch located here */
}
 
I only use this for my small SQLEXPRESS internal database. But it's handy considering I no longer have to write in each individual database name.
 
Hopefully helpful,
 
TamusJRoyce
 
p.s. This is kind of pseudo/example-code, since I have getting the db names in the ScripterHelper class so _server isn't being used in two separate locations.
GeneralRe: Added functionality so if arg[1] is *, loops through all databasesmemberFerreri Gabriele (Megasoft78)15 Jan '11 - 20:48 
Hi,
thank you for your suggestion. I think it could be very useful if you need to script every databases in a server.
I'm actually using a batch file to script just some databases but it absolutely useful! Smile | :)
 
Cheers,
Gabriele
Your mind is like a parachute. It works best when open.

QuestionHow to connect to SQLServer db at Remote machines (with UserId and Password) . [modified]memberRami.P14 Nov '10 - 23:35 
Hi Gabriele,
 
Your tool is really very nice one. It works fine for me in Local machine, But when i try to connect to the DB in Remote machine it is not connecting.
 
It is giving me the connectivity error.
 
Please provide me the syntax for connecting a DB in remote machine with userid and password.
 
Can you please help me on this. ASAP.
 
Thanks,
Ramani.P

modified on Monday, November 15, 2010 5:55 AM

AnswerRe: How to connect to SQLServer db at Remote machines (with UserId and Password) .memberFerreri Gabriele (Megasoft78)15 Nov '10 - 0:11 
Hi,
I think in my class is not implemented to pass username and password and probably is using Windows Authentication to connect to the database. I think you can change it very quickly.
You need to go line 32 in file ScripterHelper.cs and change the following line:
_server = new Server(serverName);
 
If you go in the MSDN documentation about Server class you'll see the constructor overloads:
http://msdn.microsoft.com/en-us/library/microsoft.sqlserver.management.smo.server.aspx[^]
 

It should be something like this:
_server = new Server(new ServerConnection("[ConnectionString]"));
 
After that everything should work fine. Smile | :)
 
Regards,
Gabriele
Your mind is like a parachute. It works best when open.

GeneralRe: How to connect to SQLServer db at Remote machines (with UserId and Password) .memberRami.P15 Nov '10 - 19:13 
Thanks Gabriele, Now it works fine for Remote DB.
Can you tell me how to generate a .exe file of this upgraded SqlScripter tool.
 
With Thanks and Regards,
Ramani.P
GeneralRe: How to connect to SQLServer db at Remote machines (with UserId and Password) .memberFerreri Gabriele (Megasoft78)15 Nov '10 - 22:16 
Hi,
If you run it from Visual Studio you should have a bin folder under the project folder with Debug and Release folder where you already have the exe file.
 
Regards,
Gabriele
Your mind is like a parachute. It works best when open.

GeneralGreat script - thanksmemberger0nim07 May '10 - 22:49 
Exactly what I was looking for. Grazie mille.
GeneralMore good news Scripting DatamemberGaben4 Dec '09 - 9:31 
Maybe you already knew this, but SMO can output the data in tables as insert statements
 
the key here is dataScripter.Options.ScriptData, the other strange thing is that the Script function will not work with the ScriptData option you need to call EnumScript.
 

below is a simple block of code that spits out all the data tables. My program just takes a string array of table names and only outputs the ones that are interesting to know when changed in svn.
 
    Sub OutputAllTableData()
        Dim dataScripter As New Scripter(_server)
        dataScripter.Options.ScriptData = True
        dataScripter.Options.ScriptSchema = False
        dataScripter.Options.ToFileOnly = True
 
        For Each table As Table In _db.Tables
            dataScripter.Options.FileName = Path.Combine("c:\temp", table.Schema & "." & table.Name & ".sql")
            'you must use the enumscript function if you want data
            dataScripter.EnumScript(New SqlSmoObject() {table})
        Next
 
    End Sub

GeneralRe: More good news Scripting DatamemberFerreri Gabriele (Megasoft78)4 Dec '09 - 19:41 
Hi,
Very nice! I think I can use that to script the lookup tables! Smile | :)
 
Thanks,
Gabriele
 
Your mind is like a parachute. It works best when open.

GeneralIsSystemObject Speed IssuememberGaben3 Dec '09 - 11:40 
Gabriele,
 
I am working on something similar to this, and I found something to make your code run faster. At least it did for me.
 
after you initialize your server object do this
 
vb code:
_server.SetDefaultInitFields(GetType(Microsoft.SqlServer.Management.Smo.StoredProcedure), "IsSystemObject")
 
I guess from what I understand when you check the IsSystemObject property it goes back to the db and fetches the property from the database. If you call the above code it will pre-load it and it runs much faster
 
You will want to do this for each object type you are checking this property for.
 
Hope it helps.
 
Also that FOR /f is great stuff.
 
Gabe
GeneralRe: IsSystemObject Speed IssuememberFerreri Gabriele (Megasoft78)3 Dec '09 - 19:40 
Hi Gabe,
I think you're right! I want to test it as soon as I can!
Thank you very much! Smile | :)
 
Gabriele
 
Your mind is like a parachute. It works best when open.

GeneralRe: IsSystemObject Speed IssuememberGaben4 Dec '09 - 9:13 
here is what I ended up doing in my code
 
vb code:
_server.SetDefaultInitFields(GetType(Table), "IsSystemObject", "DateLastModified")
_server.SetDefaultInitFields(GetType(View), "IsSystemObject", "IsEncrypted", "DateLastModified")
_server.SetDefaultInitFields(GetType(StoredProcedure), "IsSystemObject", "IsEncrypted", "DateLastModified")
_server.SetDefaultInitFields(GetType(Trigger), "IsSystemObject", "IsEncrypted", "DateLastModified")
_server.SetDefaultInitFields(GetType(UserDefinedFunction), "IsSystemObject", "IsEncrypted", "DateLastModified")

GeneralRe: IsSystemObject Speed IssuememberFerreri Gabriele (Megasoft78)4 Dec '09 - 19:42 
Hi,
I tested it and it works faster!
 
Thanks,
Gabriele
 
Your mind is like a parachute. It works best when open.

GeneralI did the same :-)memberunick23 Nov '09 - 5:57 
Hello,
I did (almost) the same but also automated the svn commands (I suppose you commit manually? How do you keep track of deletes ?)
 
Here is my solution: http://blog.boxedbits.com/archives/133[^]
GeneralRe: I did the same :-)memberFerreri Gabriele (Megasoft78)23 Nov '09 - 7:26 
Hi,
I don't need to implement it in c# because I can do it easily with a batch file:
 

SQLScripter.exe (local) AdventureWorks "C:\Scripts"
 
C:\Scripts
for /f "tokens=2*" %%i in ('svn status ^| find "?"') do svn add %%i
for /f "tokens=2*" %%i in ('svn status ^| find "!"') do svn delete %%i
 
svn commit -m "Automatic commit" "C:\Scripts"

 
Your mind is like a parachute. It works best when open.

Generalgood idea :)memberacarpio197512 Oct '09 - 17:29 
i feel your sentiments. i've been facing the same problem ever since i started using source control. we are currently (still) using VSS because the VSTS approach is too costly. and VSS integrates nicely with Visual Studio 2005 and SQL Server Management Studio 2005 (but no data scripting like Visual Studio 2003 Frown | :( .
one problem with full scripting is you will still need to create alter scripts specially if you are updating a live db. one trick i use when changing db objects is to generate change script before saving. this way i can check-in the with the current state the update script. Smile | :)
GeneralRe: good idea :)memberFerreri Gabriele (Megasoft78)12 Oct '09 - 20:48 
Hi,
I think you need alter script for table only for deployment, but most of the time you have to add additional queries to convert the existing data. I don't think it's possibile to make automatic this. If it was possible I think there was no need for developers! Smile | :)
The main target of this tutorial is just provide you a simple way to have the history of your changes (mainly Stored Procedures) without affect the way developers work.
 
Cheers,
Gabriele
 
Your mind is like a parachute. It works best when open.

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web02 | 2.6.130523.1 | Last Updated 10 Oct 2009
Article Copyright 2009 by Ferreri Gabriele (Megasoft78)
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid