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.
GeneralRe: Problems when using external database servermemberlexugax5 Feb '10 - 4:37 
Well, I found a place on the code where a server object was created only with the server name, and no user id and password. I modified it to take the id and password and it worked.
GeneralProblem with nvarchar(MAX)memberLinosoft25 Nov '09 - 7:50 
I would have liked to try your tool but as did it I got an error: a string.format exception...
I've looked into your code and found out the problem is within the following method:
 
private DataType ParseDbType(string dbType)
{
string dbTypeName = dbType.Replace("(", "").Replace(")", "");
var member = typeof(DataType).GetMember(dbTypeName, BindingFlags.IgnoreCase | BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public).FirstOrDefault();
var dbTypePrms = new ArrayList();
if (member == null)
{
var i1 = dbType.IndexOf("(");
var i2 = dbType.IndexOf(")");
if (i1 == -1)
{
dbTypeName = dbType.Split(' ').First();
}
else
{
dbTypeName = dbType.Substring(0, i1);
var prms = dbType.Substring(i1 + 1, i2 - i1 - 1).Split(',');
foreach (var prm in prms)
{
dbTypePrms.Add(int.Parse(prm)); }
}
dbTypePrms.Reverse();//because scale is before precision. we get (precision, scale)
}
object res;
member = typeof(DataType).GetMember(dbTypeName, BindingFlags.IgnoreCase | BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public).First();
try
{
if (member is PropertyInfo)
res = ((PropertyInfo)member).GetValue(null, null);
else
{
var method = (MethodInfo)member;
if (dbTypeName == "Decimal" && dbTypePrms.Count == 1)
dbTypePrms.Insert(0, 0);
res = method.Invoke(null, dbTypePrms.ToArray());
}
}
catch (Exception e)
{
throw new Exception("Error while trying to ParseDbType(" + dbType + ")", e);
}
return (DataType)res;
}

 
The problem is at the line: dbTypePrms.Add(int.Parse(prm)) where you parse the content of the datatype. In my case I have as a datatype nvarchar(MAX). Of course, it tries to parse "MAX" and get the exception.
I don't have time to fix that...but I thought to make you aware of it.
 
Paolo
GeneralRe: Problem with nvarchar(MAX)memberDan-el Khen25 Nov '09 - 8:14 
Thanks Paolo,
 
You're absolutely correct, I will fix it and upload a newer version soon.
 
In the meantime, you can simply modify the code like this:
private DataType ParseDbType(string dbType)
{
  string dbTypeName = dbType.Replace("(", "").Replace(")", "").ToLower();
  if(dbTypeName=="nvarchar(max)")
    return DataType.NVarCharMax;
  //... rest of the code
}
 
Cheers
Dan-El
GeneralMuchas Gracias and Thank You 10xmemberclintonG6 Jun '09 - 5:54 
Dan-el you are the man. This type of functionality is what many web developers need to synch the local db with the db on the host provider's server.
 
Commercial products are very useful of course but in today's markets way too expensive to purchase because they often do not pay for themselves.
 
The real problem as I see it is because many of us "developers" have to learn and relearn the same things over and over because we do not use the same language or techniques every day and can't remember everything.
 
I'm looking forward to making the time to download and use what you have developed Dan-el and hope I don't have to come back here to complain because it doesn't work Wink | ;-)
 
clintonG
GeneralDittomentorNick Butler1 Jun '09 - 11:26 
I was thinking about writing this, but now I don't have to Smile | :)
 
Thanks for the implementation!
 
Nick
 
----------------------------------
Be excellent to each other Smile | :)

GeneralNicememberMember 27270981 Jun '09 - 7:15 
I always have pondered about that why to generate entity classes from database (almost all O/R mappers works like that)... and in Linq to Sql case, it's double job to write sql scripts for database, database definition is ready in dbml/entities/xml mapping file.
 
I have also written similar system like this, but it is property of my employer so I can't publish it here (not open source, sorry folks). System that I have developed is highly customized for our needs. Working purely using T-SQL.
 
I understand why MS guys has only written method CreateDatabase() and not also UpdateDatabase(); it is huge task if you want that it handles all bells and whistles that relational database contains... or are they just lazy?
 
But, this article is very good start Smile | :)

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 2 Jun 2009
Article Copyright 2009 by Dan-el Khen
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid