Friday, 12 December 2008

Change LINQ to SQL Mapping at Run-time to change Table attribute

The Problem

There are a number of tables for an application in SQL server database. Allow to differ the table name or schema name in order to create another same function application.

LINQ to SQL use attribute to map the table and other database object. The attribute of the partial class generated by the LINQ to SQL designer can not be changed at the runtime. Because the attribute are part of the class metadata and are complied in assembly, and read-only at runtime.

[Table(Name="dbo.customtable")]
public partial class customtable

Solution

LINQ to SQL use different MappingSource to create MetaModel from XML-based or Attribute-based Mapping.

One of the solution uses XML-based Mapping. Change the mapping xml file or xml string at run-time, then load that again using XmlMappingSource.FromXml(RuntimeXmlStr), and pass the MappingSource object to DataContext constructor The overhead is extra xml operation.

The other solution is creating a wrapper for AttributeMappingSource to wrap up the mapping information and customize some of the mapping info at runtime, such as changing the table name etc. It doesn't need to create a new xml string and MetaModel each time the table name need to be changed. It use wrapped properties to change that. Only one MetaModel is required, when changing the table name. I only talk about this one. Lots of people have talked about the above method

Method

First, create a customized MappingSource class to wrap the original one. So when construct the DataContext, it can use the customized MappingSource. This need to implment CreateModel method to create a object model.

public class CustomAttributeMapping : MappingSource {
AttributeMappingSource mapping = new AttributeMappingSource();
protected override MetaModel CreateModel(Type dataContextType)

{
MetaModel oldmodel = mapping.GetModel(dataContextType);
CustomMetaModel newmodel = new CustomMetaModel(oldmodel);
return newmodel;
}
}


Second, create a customized metamodel object to wrap the original metamodel and provide the customized mapping information based on the original attribute and the runtime.

public class CustomMetaModel : MetaModel
{
private static CustomAttributeMapping _mapping = new CustomAttributeMapping();
public CustomMetaModel(MetaModel org)

{
_orgmodel = org;
}

private MetaModel _orgmodel;

public override Type ContextType
{
get { return _orgmodel.ContextType; }
}
// the other code is similar to implement the abstrcat methods, and being cutted

public override MetaTable GetTable(Type rowType)
{
return new CustomMetaTable(_orgmodel.GetTable(rowType) );
}

public override IEnumerable GetTables()
{
List newtable = new List();

// wrap the table metadata with the new one
foreach (MetaTable table in _orgmodel.GetTables())
{
newtable.Add( new CustomMetaTable(table) );
}
return newtable;
}

// link back to the custom mapping source
public override MappingSource MappingSource
{
get { return _mapping; }
}
}

Next, create custom metadata for table information, and change the table name at runtime in the TableName property.

public class CustomMetaTable : MetaTable
{
private MetaTable _orgtable;
private MetaModel _model;

public CustomMetaTable(MetaTable table, MetaModel model)
{
_orgtable = table;
_model = model

}

public override MethodInfo DeleteMethod
{
get { return _orgtable.DeleteMethod; }
}

// the other code is similar to implement abstract methods and being cutted

public override MetaModel Model

{
get { return _model; }
}

// real thing is change the Table Name here at runtime, using your rules or something to
// create the new table name
public override string TableName

{
get{
string oldname = _orgtable.TableName;

// change the table name here

string newname = oldname;
return newname;
}

}
}

Last, when creating the DataContext, pass the new customized MappingSource to the constructor. So the DataContext can use the new wrapper to provide customized run-time mapping information.

string conStr = "...";
CustomDataContext context = new CustomDataContext(conStr, new CustomAttributeMapping() );


The other mapping information can be changed by using a wrapper, such as change column setting. This is a workaround for the situation to change the table name at runtime.

Suggestion for LINQ to SQL ( just an idea )

Since the attribute-based mapping is easy to use and have nice GUI designer to create mapping, it is good to keep that. Allow user change the attribute setting is a nice feature. The suggestion is, in the AttributedMetaModel (which created by the AttributeMappingSource), using the TypeDescriptor to fetch the attribute, instead of type reflection API. The TypeDescriptor allows the user to manipulate the Metadata dynamically, which has already been used in DataGridView. So if user want to customize their attribute setting, only need to implement ICustomTypeDescriptor interface to provide run-time metadata information. That is more flexible, and also keep the attribute-based mapping nicely. ( just an idea )

No comments: