Having fun with code
  • Blog
  • Favorites
    • Favorite Books
    • Favorite Libraries
    • Favorite Tools
  • Utilities
    • SP 2010 Colors
  • Contact
  • Home /
  • .NET /
  • Scripting your SQL database (using SMO and the command line)

Scripting your SQL database (using SMO and the command line)

April 21, 2012 / Matt C. / .NET
When I’m developing, I often start with a “database first” design, so having a utility that can generate my database scripts and dump them into a directory comes in really handy. During a development cycle, I can then build into my application the ability to rebuild the database at runtime and seed the database with data. Once the application moves into production, on restart, it compares the directory of sql scripts with all the scripts it has already executed (stored in a table), and executes any new scripts as needed. If you’re interested in this, there are some good open-source applications that do this already. As of this writing for example, the nopCommerce application does this for example, although it uses EF CodeFirst and relies on the built-in CodeFirst script generator to create the SQL scripts. Personally, I prefer to have full control over the SQL schema generation process, so generating databases on the fly using EF CodeFirst is not something I’m personally very fond of at this time, but that’s a personal preference - the overall concept however is similar. With that out of the way, this post is about generating SQL scripts, so on with it.

Requirements

  • Create a command line application that generates the needed SQL scripts to re-create a database (something that can be added as an external tool either to SQL Server Management Studio, or Visual Studio)
  • Provide the following argument options:
    • Server and Database/Catalog to script
    • Directory to dump the files into
    • Override default names for the various SQL files: Tables, Stored Procedures, UDFs, and Data

The Console Application

When creating a simple command line application, the first thing you need is a way to parse arguments and to return to the user some “help” documentation on how to use the application commands. There’s no use in re-inventing the parsing algorithms for a simple application, there are plenty out there already written for you. In this case, check out the following two links:
  • THANKS TO Richard Lopes: http://www.codeproject.com/Articles/3111/C-NET-Command-Line-Arguments-Parser
  • IMPROVED BY Jake Ginnivan: http://jake.ginnivan.net/c-sharp-argument-parser
Once we wire this into our application, it’s just a matter of leveraging the argument parser for our specific needs. In our case, that looks something like this.
class Program
{
    static void Main(string[] args)
    {
        var program = new Program(new Arguments(args), Console.Out);
        if (!program.Validate())
        {
            program.OutputHelp();
        }
        else
        {
            program.Execute();
        }
    }

    private Arguments _arguments;
    private TextWriter _logger;

    public Program(Arguments arguments, TextWriter logger)
    {
        this._arguments = arguments;
        this._logger = logger;
    }

    private bool Validate()
    {
        if (string.IsNullOrEmpty(_arguments.Single("server")) && string.IsNullOrEmpty(_arguments.Single("s")))
            return false;

        if (string.IsNullOrEmpty(_arguments.Single("database")) && string.IsNullOrEmpty(_arguments.Single("d")))
            return false;

        if (string.IsNullOrEmpty(_arguments.Single("directory")) && string.IsNullOrEmpty(_arguments.Single("dir")))
            return false;

        return true;
    }

    private void Execute()
    {
        var stacktrace = _arguments.IsTrue("stacktrace");
        try
        {
            var server = _arguments.Single("server") ?? _arguments.Single("s");
            var database = _arguments.Single("database") ?? _arguments.Single("d");
            var directory = _arguments.Single("directory") ?? _arguments.Single("dir");

            var output = _arguments["output"] ?? new Collection<string> { "tables", "udfs", "procedures", "data" };
            var verbose = _arguments.Exists("verbose") ? _arguments.IsTrue("verbose") : (_arguments.Exists("v") && _arguments.IsTrue("v"));

            var tablesFileName = _arguments.Single("tf") ?? "tables.sql";
            var udfsFileName = _arguments.Single("tu") ?? "udfs.sql";
            var proceduresFileName = _arguments.Single("tp") ?? "procedures.sql";
            var dataFileName = _arguments.Single("td") ?? "data.sql";

            string[] files;
            DatabaseScripter.Execute(server, database, directory, output.Contains("tables"), output.Contains("udfs"),
            output.Contains("procedures"), output.Contains("data"), tablesFileName, udfsFileName,
            proceduresFileName, dataFileName, (verbose ? _logger : null), out files);

            foreach (var file in files)
                _logger.WriteLine(file);
        }
        catch (Exception ex)
        {
            _logger.WriteLine("Exception: {0}", ex.Message);
            if (stacktrace)
                _logger.WriteLine("StackTrace: \n{0}", ex);
        }
    }

    private void OutputHelp()
    {
        _logger.WriteLine("\nMandatory arguments\n");
        _logger.WriteLine("{0,-20}{1}", "-server", "The database server.");
        _logger.WriteLine("{0,-20}{1}", "-database", "The name of the database (catalog).");
        _logger.WriteLine("{0,-20}{1}", "-directory", "The directory to output the database scripts to.");
        _logger.WriteLine("\nOptional arguments\n");
        _logger.WriteLine("{0,-20}{1}", "-verbose", "Output information while processing.");
        _logger.WriteLine("{0,-20}{1}", "-stacktrace", "Include the stack trace in the error message.");
        _logger.WriteLine("{0,-20}{1}", "-output", "This command can be specified multiple times.");
        _logger.WriteLine("{0,-20}{1}", "", "Value options: tables, udfs, procedures, data");
        _logger.WriteLine("{0,-20}{1}", "", "(by default, all options are selected).");
        _logger.WriteLine("{0,-20}{1}", "-tf", "'tables' script output file (default: tables.sql).");
        _logger.WriteLine("{0,-20}{1}", "-tu", "'udfs' script output file (default: udfs.sql).");
        _logger.WriteLine("{0,-20}{1}", "-tp", "'procedures' script output file (default: procedures.sql).");
        _logger.WriteLine("{0,-20}{1}", "-td", "'data' script output file (default: data.sql).");
    }
}

Scripting the Database

SQL Server Management Studio has a very nice built-in wizard that does everything we need. [one_third]smo_wiz1[/one_third] [one_third]smo_wiz2[/one_third] [one_third last]smo_wiz3[/one_third] [clear] The goal is to use the SMO API to basically reproduce what the wizard does above, all from a command line. The API gives you pretty much everything you need with the Scripter and ScriptingOptions objects. Using these objects, we can easily script Tables, Stored Procedures, UDFs, and Data:
var dbServer = new Server(server);
var db = dbServer.Databases[database];
var scriptingOptions =  new ScriptingOptions
   {
       AppendToFile = true,
       AnsiFile = true,
       AnsiPadding = true,
       ChangeTracking = true,
       ClusteredIndexes = true,
       ContinueScriptingOnError = false,
       DriAll = true,
       IncludeHeaders = true,
       IncludeIfNotExists = true,
       Indexes = true,
       ToFileOnly = true,
       WithDependencies = true
   };
var scripter = new Scripter(dbServer) {Options = scriptingOptions};

var paths = new List<string>();
if (includeTables)
{
    var fName = Path.Combine(directory, tablesFileName);
    ScriptTables(fName, db, scripter, logger);
    paths.Add(fName);
}
if (includeUdfs)
{
    var fName = Path.Combine(directory, udfsFileName);
    ScriptUdfs(fName, db, scripter, logger);
    paths.Add(fName);
}
if (includeProcedures)
{
    var fName = Path.Combine(directory, proceduresFileName);
    ScriptProcedures(fName, db, scripter, logger);
    paths.Add(fName);
}
if (includeData)
{
    var fName = Path.Combine(directory, dataFileName);
    ScriptData(fName, db, scripter, logger);
    paths.Add(fName);
}

Final Output

Now that everything is wired up, it’s time to test it out. Let’s go with the aspnetdb, something everybody is already pretty familiar with. Here’s what the “help” screen looks like: gendbsql_help Now for the real thing: gendbsql_aspnetdb And there you go. Now, you can use a command line to easily script your databases. All the code is provided below. Use, experiment, extend as you please. [button link="/funcoding/files/2012/04/gendbsql1.zip" color="primary" target="_blank" size="large" title="Download Code" icon_before="download"]Download Code[/button] command line, console application, smo, sql

6 comments on “Scripting your SQL database (using SMO and the command line)”

  1. jasdeep says:
    February 12, 2013 at 8:36 am

    Hi,

    Can we edit smo script pasted in this link https://gist.github.com/1755925 to have scripts for primary key ddl and triggers and indexes in separate files too. I am new bee to smo .

    thanks for your help
    jass

  2. Vlad says:
    April 25, 2012 at 7:37 am

    I have only one thing to say: WOW

    that was fast, thank you very much, it was very helpful.

    I have a project where I want to script whole DB to store into SVN
    and we do not have any tools that can do that(small company not IT related industry. I am the only one here with SQL and C#. older guy is DBase and excel self taught programmer.).
    I am not good with Power Shell, never used it 🙁

    • Matt C. says:
      April 25, 2012 at 2:32 pm

      Vlad, I’m glad it’s of use to you. It sounds like you have a challenging environment to work in, but also may have opportunities to shine. Happy coding to you.

  3. Vlad says:
    April 23, 2012 at 9:48 am

    thanks, the code works like very well except few things that not obviose from the description.
    #1. when entering target path need to include “\\” double slash in the path and -verbose option does nothing. I tried it with and without it and got the same result (unless I am missing something)

    have a question though (I am doing the research right now but if you or someone know the answer well..)
    is it possible to set option to script one file per object in this setup ?
    like not all table scripts in one tables file but a file per table script?

    thanks

    • Matt C. says:
      April 23, 2012 at 12:13 pm

      Vlad, I updated the code to account for the verbose option. Not sure about the “\\” slash issue you’re having, maybe check the Arguments.Test.txt file which describes the various command line syntax options available for file paths – at least you got it working :-). I also added a “-if” option which will output each SMO object to a separate file instead (without dependencies). I also added a “database.ps1” file to the download which is a copy with a slight fix (with credits) of https://gist.github.com/1755925 by Vince Panuccio. You may want to check that out, as it does the same thing very nicely using powershell instead.

  4. Dew Drop – April 23, 2012 (#1,311) | Alvin Ashcraft's Morning Dew says:
    April 23, 2012 at 7:56 am

    […] Scripting your SQL database (using SMO and the command line) (Matt J. Cowan) […]

Categories

  • .NET (20)
  • ASP.NET MVC (4)
  • JAMstack (2)
    • Headless CMS (2)
  • Miscellaneous (2)
  • Python (1)
  • REST (3)
  • SharePoint (8)
  • WordPress (1)

Tags

.net ado.net autofac binding C# chrome code generation command line console application csv dapper di entity framework integration ioc job scheduling engine json jsv learning llblgen load balancing micro-orm mycorp odata orm people editor people picker picker controls picker dialog plugins pmp python Quartz.NET rest saf service application servicestack sharepoint smo soap sql sqlalchemy tornado web server validation web api

Archives

  • May 2020 (2)
  • November 2013 (1)
  • June 2013 (1)
  • May 2013 (1)
  • March 2013 (1)
  • December 2012 (1)
  • November 2012 (1)
  • October 2012 (3)
  • September 2012 (2)
  • June 2012 (2)
  • May 2012 (1)
  • April 2012 (1)
  • February 2012 (2)
  • January 2012 (1)
  • December 2011 (2)
  • September 2011 (2)
(c) 2013 Having fun with code - "FunCoding" theme designed by mattjcowan using the Theme Blvd framework