A database project as of Visual Studio template is a series of .SQL scripts that run sequentially and create the schema of the project. My experience shows that in most real world scenarios, this is not enough. I list generic requirements of a database project here and take further steps of developing one:
- There should be a choice to initially create database from the schema script or load it from a basic backup file.
- Pre-requisits on the server should be checked.
- Should be cross platform. Basically anything that works with running scripts (not only SQL) should be runnable on this.
- Should be extensible. (As any other project should be)
Let’s create the solution. A console application should be good for this. We create a new solution called SDP (Simple Database Project).
For the 4th point, we use MEF (Managed Extensibility Framework). MEF is an opensource project supported by Microsoft and I think is a great framework. In this project, we additionally use MEF for IoC (Inversion of Control). In order to use MEF, we add System.ComponentModel.Composition to our solution. Note that a version of MEF is shipped with .Net Framework and Silverlight 4.0 and you don’t have to download anything.
The first step is to generate (create or restore) the initial database. For this purpose we define IDatabaseGenerator which has a method GenerateDatabase(). Any database generator need to do something with a database system which can be a server like SQL Server or can be an application like excel. Whatever the database system is, we do not introduce the requirement of database connection, etc. to our project. To keep the project extensible and simple we assume everything is possible with running a script using some tool or API. Hence, let’s define IScriptExecuter which executes a given script. It has a method ExecuteScript(). Any script executer needs a list of scripts to execute, so let’s define the generic interface IScriptProvider<T>. We keep this interface generic since we don’t know what type of script should we provide. It has a method GetScripts() that returns IEnumerable<T>.
So let’s implement our classes to run a sql script with a commandline tool and generate a database. We need a context to put essentials together, so let’s define SdpContext class. Before that we should have a script class defined to use with our context:
public class Script: IComparable<Script>
{
public virtual string Path { get; protected set; }
public virtual string Title { get; protected set; }
public virtual int Order { get; set; }
public int CompareTo(Script other)
{
return Order.CompareTo(other.Order);
}
public Script(String path, String title, int Order)
{
Path = path;
Title = title;
Order = Order;
}
}
public partial class SdpContext
{
[ImportMany]
public IEnumerable<Lazy<IDatabaseGenerator>> DbGenerators {get; set;}
[ImportMany]
public IEnumerable<Lazy<IScriptProvider<Script>>> ScriptProviders { get; set; }
[Import(AllowDefault=true, RequiredCreationPolicy = CreationPolicy.Shared)]
public IVersionController VersionController { get; set; }
public SdpContext(Assembly[] plugins)
{
ComposeFromAssemblies(plugins);
}
public void ComposeFromAssemblies(Assembly[] plugins)
{
var container = new CompositionContainer();
var catalog = new AggregateCatalog();
foreach (var plugin in plugins)
{
catalog.Catalogs.Add( new AssemblyCatalog(plugin) );
}
container.ComposeParts();
}
}
You have spotted some new stuff in the creation of SdpContext class. SdpContext is supposed to be self sufficient, meaning that you it should run by itself. Ofcourse you need to feed it with a bunch of plugins.
SdpContext is a partial class because I wanted to seperate the basic functionality of the context with other extended functionalities.
Import and ImportMany attributes are MEF attributes. They mean that our context needs a bunch of DbGenerators and ScriptProviders. MEF Framework then magically extracts any DbGenerator and ScriptProvider from the plugins and instantiates them for us. They are all defined as Lazy to avoid their instantiation before when we really need them. The interface VersionController is new here and we still don’t know how it should look like.