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

LINQ to SQL Database Synchronizer

By , 1 Jun 2009
 

Introduction

LINQ to SQL Database Synchronizer (csdb.exe) is a tool that provides database structure synchronization from a LINQ to SQL model. It supports both creating new databases and updating existing ones. It is important to note that this tool never deletes any data or structure; it simply adds missing tables, columns and indexes and modifies existing columns.

Main Benefits

  • Single click O\R model synchronization
  • Better team productivity, streamline database changes between your team members
  • Easier product installations and upgrades, no complicated SQL scripts needed
  • Simpler maintenance of a single model instead of two

Background

The LINQ to SQL Framework provides a simple and convenient way to communicate with databases, using simple C# classes and LINQ, without writing any sophisticated SQL commands. While working with it, I've stumbled upon a major drawback: The DataContext.CreateDatabase method only supports creating new databases. This means that in order to modify an existing LINQ to SQL model over an existing database, one must apply the structure changes manually on the database, or delete the database entirely and invoke the CreateDatabase method again. This drawback may really slow a team down, when each member of the team has his own database for testing, every feature that another member adds that requires a database structure change, forces the other members to run various scripts or perform manual changes on their own database. The same goes for live servers that need to be updated with a new product version.

The DataContext.CreateDatabase method I've mentioned earlier uses SQL scripts to create the database and the tables inside it. This tool uses the SQL Server Management Objects (SMO) library instead. It analyzes a LINQ to SQL model, compares it to an existing database structure and finally applies all of the necessary changes to the database structure.

Inside the Code

We basically have two models we want to synchronize - one is the LINQ to SQL model, and the other is the database model. So let's start off by loading the LINQ to SQL model:

var asm = Assembly.LoadFrom(Options.AssemblyFile);       //Load an assembly file
var type = asm.GetType(Options.TypeName, true);          //Find the DataContext class
						  //using reflection
var model = new AttributeMappingSource().GetModel(type); //Load the LINQ to SQL mapping
                                                         //model from the specified type

Review this in an online IDE here and here.

Now, we'll load the database model using SMO and the connection string we have.

var sb = new SqlConnectionStringBuilder(ConnectionString);   //Parse the 
						      //connection string
var server = new Server(sb.DataSource);                      //Connect to the 
                                                             //database server
var db = server.Databases[sb.InitialCatalog];                //Get the database

Review this in an online IDE here and here.

Now we have all the data we need, and it's simply a matter of iterating the LINQ to SQL model, and locating the corresponding database objects. If they don't exist, we simply create them, otherwise we verify their definition.

foreach (MetaTable table4 in model.GetTables())
{
  string schemaName;
  string tableName;
  ParseFullTableName(mt.TableName, out schemaName, out tableName);  //Split the schema 
							    //and table name
  var table = Database.Tables[tableName, schemaName];               //Find the table
  if (table == null)                                                //If the table 
                                                                    //doesn't exist
  {
    table = new Table(Database, tableName, schemaName);             //Create the table
    Database.Tables.Add(table);
  }
  //Now we can synchronize the table columns...
}

Review this in an online IDE here and here.

Using the Code

This tool works as a simple command line tool (*.exe), simply specify an assembly and a type name of a LINQ to SQL DataContext class and run it, your database structure will be synchronized by this model.

Prerequisites

This tool uses SQL Server Management Objects (SMO), the latest version of it can be found at the Microsoft SQL Server 2008 Feature Pack download page, but here are the direct links:

Usage

This tool is used like any standard command line tool, with the following syntax:

csdb.exe /assembly:[AssemblyFile] /type:[TypeName] /autocreate 
	/autoupdate /cs:[ConnectionString] /dbfilename:[DatabaseFilename]
	
  /assembly:[AssemblyFile]    The assembly filename that contains the LINQ to SQL
			   (http://msdn.microsoft.com/en-us/library/bb425822.aspx) 
			   DataContext class, e.g.: bin\debug\MyApp.exe
  /type:[TypeName]            Optional, The type name of the LINQ to SQL
			   (http://msdn.microsoft.com/en-us/library/bb425822.aspx) 
			   DataContext class, e.g.: MyApp.MyDataContext. 
			   if not provided, 
			   any class that inherits from the DataContext 
			   class will be processed.
  /autocreate                 When specified, the tool will create the database 
			   if it doesn't exist.
  /autoupdate                 When specified, the tool will update the existing 
			   database structure.
  /cs:[ConnectionString]      Optional, The connection string of the database 
			   to be synchronized, if not provided, the default 
			   connection string will be used if it exists 
			   in your settings.
  @[Arguments Filename]       Read the command line arguments from an 
			   external text file.

Example

This statement will synchronize (create and update) the MyDb database in the local machine's SqlExpress instance using the MyApp.MyDataContext class located in the MyApp.exe assembly.

csdb.exe /assembly:"bin\debug\MyApp.exe" /type:"MyApp.MyDataContext" 
	/autocreate /autoupdate /cs:"Data Source=.\SQLEXPRESS;Initial 
	Catalog=MyDb;Integrated Security=True"

Recommendations

  • Specify both the /autocreate and /autoupdate options for maximum automation.
  • Create a batch file that executes this tool, and include it in your project.
  • In the early stages of development, run this batch file as a post-build step in your project file.

Running the Sample

The sources include a sample project with a LINQ to SQL model of the well known Northwind database. To run the sample:

To change the LINQ to SQL model and synchronize the database:

  • Modify the LINQ to SQL model by modifying the MyNorthwind.dbml file using Visual Studio, you can add a column, add a table, change the data type of a column, allow nulls on a column, etc...
  • Build the Samples project to reflect your changes.
  • Run the SyncMyNorthwindDb.bat batch file to synchronize your database.

Points of Interest

I think that this tool answers a basic need when using the LINQ to SQL Framework. We use it extensively at CodeRun in both development and deployment. In this way, modifying the database is as simple as adding a property.

License

This article, along with any associated source code and files, is licensed under The Microsoft Public License (Ms-PL)

About the Author

Dan-el Khen
Architect SharpKit
Israel Israel
Member
Founder of SharpKit

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   
GeneralAn Easier Way to synchronize database table updates using LINQmemberBobRI29 Apr '11 - 4:59 
After trial and error attempting to get Visual Studio 2008 to recognize my added column in the database table, I discovered you can synchronize the LINQ to SQL database. As we know, when you create the DataContext for LINQ to SQL, the dbml and the designer.cs files are automatically created. You can update it later in Visual Studio. How?
 
Update the [project name].dbml file in Visual Studio. When you edit the dbml file, you are presented a GUI with all the tables in your LINQ database. Right click the table properties and ADD a property to the table. The "Property" is the table column. Add the Column values exactly as they exist on the SQL Server side. Then Rebuild your application. The designer.cs file should contain your new column.
GeneralMy vote of 5membermobtadi12 Dec '10 - 3:12 
thanks
GeneralMy vote of 5memberDo Quang2 Nov '10 - 18:44 
Good
GeneralSome key improvements [modified]memberAlphons van der Heijden4 Jun '10 - 8:43 
When running this program directly on my datacontext, it throws exceptions.
After some debugging, I found assumptions on primary key names and foreign key names. The names composed by Visual Studio are not reflected by this program, but maybe they should not. Anyway, I changed some things, now it takes the default names on keys when composed by Visual Studios internal db manager. Also some problems on using uniqueidentifiers solved. Therefore change the code a bit:
 
private void BuildPrimaryKey(MetaTable table)
{
  string schemaName;
  string tableName;
  ParseFullTableName(table.TableName, out schemaName, out tableName);
 
  var pkName = "PK_" + tableName;
  var pk = CurrentTable.Indexes[pkName];
  if (pk == null)
  ........
}
 
========
In method: BuildForeignKeys(MetaType type)
......
  string otherSchemaName;
  string otherTableName;
  ParseFullTableName(association.OtherType.Table.TableName, out otherSchemaName, out otherTableName);
 
  var mappedName = dm.MappedName;
  if (otherTableName == dm.Name)
    mappedName = string.Format("FK_{0}_{1}", tableName, otherTableName );
  var fkName = mappedName;
  var fk = table.ForeignKeys[fkName];
......
 
========
In Method BuildFieldDeclarations(MetaType type, Dictionary<object, string> memberNameToMappedName)
......
col.DataType = dataType;
col.Nullable = member.CanBeNull;
col.Identity = member.IsDbGenerated && member.IsPrimaryKey;
if (created && !col.Identity && !col.Nullable && dataType.SqlDataType != SqlDataType.UniqueIdentifier)
......
 
I hope this is useful to others Wink | ;-)
 
ps. Thanks for the program, saved me tons of time.
sometimes I think, therefore sometimes I am
modified on Friday, June 4, 2010 4:11 PM

QuestionDate type "Time" not supported?memberAlexander Quinte17 Mar '10 - 12:18 
Hi,
the Database Synchronizer is very helpfull for my project.
For my project I use SQL Server 2008 and in one of my Database Table I have two collumns with the type "time(7)".
In my datacontext are this the type timespan.
 
I want to use the Database Synchronizer for updating the database, but it stopped with the follow error:
 
(0.0): error : Error during csdb. System.Exception: Error while building member: Event.TimePerRun ---> System.Exception: Error while trying to ParseDbType(Time) ......
 
I look in the code and in the method GetDbType I can not find the type time.
 
What can I do now?
 
Thank you very much
 
Alex
AnswerRe: Date type "Time" not supported?memberAlexander Quinte27 Mar '10 - 1:40 
Hi,
I fix my problem in the follow way:
 
In the class DatabaseBuilder I change the method ParseDbType like follow:
 
private DataType ParseDbType(string dbType)
{
if (dbType == "time") dbType = "time(7)"; <-- added this code
string dbTypeName = dbType.Replace("(", "").Replace(")", "");
......
 
The ParseDbType-method need the value in the bracket for the correct parsing.
This solution is not perfect. Its work only for dbType == "time".
The same problem exist for the dbType dateTime2 too.
 

Also I change the method GetDbType in the follow way:
 
else
{
switch (Type.GetTypeCode(type))
{
case TypeCode.Object:
//if (type != typeof(Guid))
//{
// if (type == typeof(byte[]))
// {
// builder.Append("VarBinary(8000)");
// }
// else
// {
// if (type != typeof(char[]))
// {
// throw Common.CouldNotDetermineSqlType(type);
// }
// builder.Append("NVarChar(4000)");
// }
//}
//else
//{
// builder.Append("UniqueIdentifier");
//}
if (type == typeof(Guid))
{
builder.Append("UniqueIdentifier");
break;
}
if (type == typeof(byte[]))
{
builder.Append("VarBinary(8000)");
break;
}
if (type == typeof(char[]))
{
builder.Append("NVarChar(4000)");
break;
}
if (type == typeof(TimeSpan))
{
builder.Append("time");
break;
}
 
// Datentyp konnte nicht ermittelt werden
throw Common.CouldNotDetermineSqlType(type);
 
break;
 
Alex
GeneralMore problems...memberlexugax11 Feb '10 - 23:29 
Hi, I don't know why I seem to be the only one having problems with this. I really really want it to work because it would make my life so much easier.
Is this the last version you have?
 
The problem I have right now is as follows:
I have an existing database that I want to update with this application.
My problem starts in this function:
private void ProcessPendingActions()
{
foreach (var table in DatabaseBuilder.MarkedForCreate)
{
table.Create();
}
foreach (var table in DatabaseBuilder.MarkedForAlter)
{
table.Alter();
}
DatabaseBuilder.MarkedForAlter.Clear();
DatabaseBuilder.MarkedForCreate.Clear();
}
the "table.Alter();" always returns an error saying that the tabla already has a primary key assigned.
 
I'm writing this message because I find it hard to believe that I'm the only one having problems with this.
 
Thanks.
GeneralRe: More problems...memberDan-el Khen11 Feb '10 - 23:39 
The database synchronizer doesn't handle renaming / adding primary columns. I never got down to these sort of changes, because I never make these kind of changes in databases... Smile | :) , I'll add it as a feature request for the next version...
 
In the meantime, you will need to perform the primary key changes manually on your table. If you don't have data in your table, you can just delete it, and the database synchronizer will create a new one for you.
 
Cheers
Dan-El
GeneralRe: More problems...memberlexugax11 Feb '10 - 23:57 
OK, thanks. I'll see what I can do.
GeneralProblems when using external database servermemberlexugax2 Feb '10 - 23:58 
Hi. I tried your program and it works great when using my local database server, but when I try to connect to a different server, I always get a connection timeout... I'm using a connection string that I use on other programs, so there shouldn't be a problem with that.
Example:
csdb.exe /assembly:"Linq.exe" /type:"Linq.DataClasses1DataContext" /autocreate /autoupdate /cs:"Data Source=.\SQLEXPRESS;Initial Catalog=Test;Persist Security Info=True;User ID=sa;Password=sa"
Works fine!
 
csdb.exe /assembly:"Linq.exe" /type:"Linq.DataClasses1DataContext" /autocreate /autoupdate /cs:"Data Source=10.0.0.67\SQLEXPRESS;Initial Catalog=Test;Persist Security Info=True;User ID=sa;Password=sa"
Doesn't work!
 
I too have tried \10.0.0.67\SQLEXPRESS and many other variants to no success.

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

Permalink | Advertise | Privacy | Mobile
Web04 | 2.6.130523.1 | Last Updated 2 Jun 2009
Article Copyright 2009 by Dan-el Khen
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid