LINQ to SQL Dynamic Mapping
The problem
You have SQL Servers for each of your environments (development, test, pre-prod, production) and in each one, the table names are different. In fact, it’s not the table names that are different but the schema names but since the schema name is part of the table name, it must be specified.
Ex : dev.ZeTable et prod.ZeTable
Easy to solve, right? Just put the schema value in the config file and do a simple concatenation at runtime. Well, it’s not as easy as it seams because the table name is stored in an attribute of the partial class generated by the LINQ to SQL designer and Microsoft didn’t provide a way or method to change it at runtime.
[Table(Name="dev.ZeTable")]
public partial class TheTable
Solution
The solution is to dynamically load at runtime a mapping specific for each environment. Here’s how to do it:
Initial Mapping
Use the LINQ to SQL designer to create the initial mapping. The .dbml file will act as the starting point for our specialized mappings.
SqlMetal
Next, use the SqlMetal.exe tool to generate a mapping file but instead of pointing the tool to the database and redo all our customization in XML, the trick is to point it to the .dbml file we created earlier using the LINQ to SQL designer. This way, all our initial customizations are preserved. Nice!
From the Visual Studio 2008 Command Prompt, invoque SqlMetal like this :
C:\>SqlMetal /map:"MyApp.PROD.map" "C:\Visual Studio 2008\Projects\MyDataClasses.dbml" /code:"test.cs"
Microsoft (R) Database Mapping Generator 2008 version 1.00.21022
for Microsoft (R) .NET Framework version 3.5
Copyright (C) Microsoft Corporation. All rights reserved.
SqlMetal.exe will generate a C# or VB class. We won’t use it so you can delete it.
Next, open the mapping file and change the name of the tables to reflect the right schema:
<Table Name="prod.ZeTable" Member="TheTables">
A good practice might be to include the schema name in the mapping file name:
Ex : MyApp.PROD.map
DataContext
Next, we need to dynamically load the mappings at runtime by specifying a connection string and the mapping info in the DataContext constructor. The Microsoft’s documentation is located here :
http://msdn.microsoft.com/en-us/library/bb534562.aspx
You can store that info in the config file and load it dynamically:
<appSettings>
<add key="MyAppConnectionString" value=" MyAppDev" />
<add key="MyAppLinqMappings" value="c:\MyApp.DEV.map" />
</appSettings>
<connectionStrings>
<add name="MyAppDev"
connectionString="Data Source=MyDevServer;Initial Catalog=MyDatabase;Integrated Security=True" providerName="System.Data.SqlClient" />
</connectionStrings>
//Read the ConnectionStrings from the config file
TheConnectionString = ConfigurationManager.ConnectionStrings[ConfigurationManager.AppSettings.Get("MyAppConnectionString")].ConnectionString;
//Read the LINQ mapping and store it in memorySystem.Data.Linq.Mapping.XmlMappingSource
TheLinqMappings = System.Data.Linq.Mapping.XmlMappingSource.FromReader(XmlReader.Create(ConfigurationManager.AppSettings.Get("MyAppnLinqMappings")));
Next, feed the DataContext’s constructor :
TheDataClassesDataContext ctx = new TheDataClassesDataContext(TheConnectionString, TheLinqMappings)
That's it!