如何在代码中定义/更改Linq To Sql的映射

3
我希望能够在运行时更改类映射到的表,如果所有映射都是用属性定义的,则无法实现此目标。因此,是否有一种方法可以在代码中动态定义映射。
(我不想维护XML映射文件。)

假设我有两个表:

  • OldData
  • NewData

有时我想查询OldData,有时我想查询NewData。我希望使用相同的代码来构建这两种情况下的查询。


另请参阅 "如何动态地将实体框架模型映射到表名"。


没有移动到EF的选项? - Ian Mercer
3个回答

4
为了使其真正透明,您需要跳过一些非常疯狂的障碍,但可以通过使用自己派生的类型覆盖所有 Meta*** 类来完成。
这实际上可以使用像Castle这样的代理/方法拦截库相当简单,但假设这里是最低公共分母,基本上是一个冗长而乏味的过程,需要实现每个元方法以包装原始类型,因为您不能直接从任何属性映射类派生。
我将尝试坚持重要的覆盖内容;如果您在下面的代码中看不到特定的方法/属性,则意味着实现实际上是一个一行代码的包装器 "inner" 方法/属性并返回结果。我已经发布了整个东西,包括一行代码的方法和所有东西,PasteBin 可以供测试/实验剪切/粘贴。
您需要的第一件事是快速覆盖声明,它看起来像这样:
class TableOverride
{
    public TableOverride(Type entityType, string tableName)
    {
        if (entityType == null)
            throw new ArgumentNullException("entityType");
        if (string.IsNullOrEmpty(tableName))
            throw new ArgumentNullException("tableName");
        this.EntityType = entityType;
        this.TableName = tableName;
    }

    public Type EntityType { get; private set; }
    public string TableName { get; private set; }
}

现在讲解元类。从最低层开始,您需要实现一个MetaType包装器:
class OverrideMetaType : MetaType
{
    private readonly MetaModel model;
    private readonly MetaType innerType;
    private readonly MetaTable overrideTable;

    public OverrideMetaType(MetaModel model, MetaType innerType,
        MetaTable overrideTable)
    {
        if (model == null)
            throw new ArgumentNullException("model");
        if (innerType == null)
            throw new ArgumentNullException("innerType");
        if (overrideTable == null)
            throw new ArgumentNullException("overrideTable");
        this.model = model;
        this.innerType = innerType;
        this.overrideTable = overrideTable;
    }

    public override MetaModel Model
    {
        get { return model; }
    }

    public override MetaTable Table
    {
        get { return overrideTable; }
    }
}

再次强调,您需要实现约30个属性/方法,我已经排除了那些只是返回innerType.XYZ的方法。还跟上我吗?好的,接下来是MetaTable

class OverrideMetaTable : MetaTable
{
    private readonly MetaModel model;
    private readonly MetaTable innerTable;
    private readonly string tableName;

    public OverrideMetaTable(MetaModel model, MetaTable innerTable,
        string tableName)
    {
        if (model == null)
            throw new ArgumentNullException("model");
        if (innerTable == null)
            throw new ArgumentNullException("innerTable");
        if (string.IsNullOrEmpty(tableName))
            throw new ArgumentNullException("tableName");
        this.model = model;
        this.innerTable = innerTable;
        this.tableName = tableName;
    }

    public override MetaModel Model
    {
        get { return model; }
    }

    public override MetaType RowType
    {
        get { return new OverrideMetaType(model, innerTable.RowType, this); }
    }

    public override string TableName
    {
        get { return tableName; }
    }
}

是的,有点无聊。好的,接下来是MetaModel本身。在这里,事情变得更加有趣,这是我们真正开始声明覆盖的地方:

class OverrideMetaModel : MetaModel
{
    private readonly MappingSource source;
    private readonly MetaModel innerModel;
    private readonly List<TableOverride> tableOverrides = new 
        List<TableOverride>();

    public OverrideMetaModel(MappingSource source, MetaModel innerModel,
        IEnumerable<TableOverride> tableOverrides)
    {
        if (source == null)
            throw new ArgumentNullException("source");
        if (innerModel == null)
            throw new ArgumentNullException("innerModel");
        this.source = source;
        this.innerModel = innerModel;
        if (tableOverrides != null)
            this.tableOverrides.AddRange(tableOverrides);
    }

    public override Type ContextType
    {
        get { return innerModel.ContextType; }
    }

    public override string DatabaseName
    {
        get { return innerModel.DatabaseName; }
    }

    public override MetaFunction GetFunction(MethodInfo method)
    {
        return innerModel.GetFunction(method);
    }

    public override IEnumerable<MetaFunction> GetFunctions()
    {
        return innerModel.GetFunctions();
    }

    public override MetaType GetMetaType(Type type)
    {
        return Wrap(innerModel.GetMetaType(type));
    }

    public override MetaTable GetTable(Type rowType)
    {
        return Wrap(innerModel.GetTable(rowType));
    }

    public override IEnumerable<MetaTable> GetTables()
    {
        return innerModel.GetTables().Select(t => Wrap(t));
    }

    private MetaTable Wrap(MetaTable innerTable)
    {
        TableOverride ovr = tableOverrides.FirstOrDefault(o => 
            o.EntityType == innerTable.RowType.Type);
        return (ovr != null) ?
            new OverrideMetaTable(this, innerTable, ovr.TableName) : 
            innerTable;
    }

    private MetaType Wrap(MetaType innerType)
    {
        TableOverride ovr = tableOverrides.FirstOrDefault(o =>
            o.EntityType == innerType.Type);
        return (ovr != null) ?
            new OverrideMetaType(this, innerType, Wrap(innerType.Table)) :
            innerType;
    }

    public override MappingSource MappingSource
    {
        get { return source; }
    }
}

我们快要完成了!现在你只需要映射源:

class OverrideMappingSource : MappingSource
{
    private readonly MappingSource innerSource;
    private readonly List<TableOverride> tableOverrides = new
        List<TableOverride>();

    public OverrideMappingSource(MappingSource innerSource)
    {
        if (innerSource == null)
            throw new ArgumentNullException("innerSource");
        this.innerSource = innerSource;
    }

    protected override MetaModel CreateModel(Type dataContextType)
    {
        var innerModel = innerSource.GetModel(dataContextType);
        return new OverrideMetaModel(this, innerModel, tableOverrides);
    }

    public void OverrideTable(Type entityType, string tableName)
    {
        tableOverrides.Add(new TableOverride(entityType, tableName));
    }
}

现在我们终于可以开始使用这个了(呼):
var realSource = new AttributeMappingSource();
var overrideSource = new OverrideMappingSource(realSource);
overrideSource.OverrideTable(typeof(Customer), "NewCustomer");
string connection = Properties.Settings.Default.MyConnectionString;
using (MyDataContext context = new MyDataContext(connection, overrideSource))
{
    // Do your work here
}

我已经用查询和插入(InsertOnSubmit)测试过了。实际上,很可能在我的基本测试中错过了一些东西。哦,而且只有当两个表完全相同,包括列名时,这才能起作用。
如果此表具有任何关联(外键),它可能会出现问题,因为您还必须覆盖两端的关联名称。我将其留给读者自己思考,因为想到它会让我头痛。您最好只是从此特定表中删除任何关联,以便不必处理这个问题。
祝玩得愉快!

谢谢,考虑到另一个项目成员告诉我使用“原始SQL”,因为他认为LinqToSql很慢,并且只是重写任何LingToSql代码以使用行SQL而不尝试理解LinqToSql的情况,这可能是在此处使用任何ORM系统的最后一根稻草... - Ian Ringrose
2
@Ian:你知道这些选项并不是互相排斥的,对吧?我有一些使用 Linq to SQL 作为后端的大型项目,但其中约 20-30% 的功能是通过存储过程和临时查询路由的,可以通过 ExecuteQueryExecuteCommandExecuteMethodCall 进行解决。如果你只需要进行少量的“动态”查询,这也可以作为一种解决方法。我仍然节省了大量时间来处理其余部分的 L2S。不要把孩子和洗澡水一起倒掉;微软花了很多心思让 L2S 能够与“原始 SQL”很好地协作。 - Aaronaught

1
请使用GenericList类来查询:

公共类QueryBuilder where T: Class {

}


这在 .net 3.5 中不起作用,但当我们转移到 .net 4 时可能会起作用。 - Ian Ringrose

1

我编写了一个扩展方法,可以在运行时更改表名。它是通过反射完成的,因此不是真正受支持的,但它确实有效。希望它有所帮助。

Dim table As MetaTable = ctx.Mapping.GetTable(GetType(TLinqType))
table.SetTableName("someName")


<Extension()> _
    Public Sub SetTableName(ByVal table As MetaTable, ByVal newName As String)
        Try
            'get the FieldInfo object via reflection from the type MetaTalbe
            Dim tableNameField As FieldInfo = table.GetType().FindMembers(MemberTypes.Field, BindingFlags.NonPublic Or BindingFlags.Instance, Function(member, criteria) member.Name = "tableName", Nothing).OfType(Of FieldInfo)().FirstOrDefault()

            'check if we found the field
            If tableNameField Is Nothing Then
                Throw New InvalidOperationException("Unable to find a field named 'tableName' within the MetaTable class.")
            End If

            'get the value of the tableName field
            Dim tableName As String = TryCast(tableNameField.GetValue(table), [String])

            If String.IsNullOrEmpty(tableName) Then
                Throw New InvalidOperationException("Unable to obtain the table name object from the MetaTable: tableName field value is null or empty.")
            End If

            'set the new tableName
            tableNameField.SetValue(table, newName)
        Catch ex As Exception
            Throw New ApplicationException(String.Format("Error setting tablename ({0}) for entity {1}!", newName, table), ex)
        End Try
    End Sub

谢谢,我还没有测试过这个答案,因为项目已经进行了,不过看起来这是一个可行的答案。 - Ian Ringrose
有没有办法将表名重置为默认值?当我执行MyModelDataContext.Create()时,我已经更改了表格... - Elastep

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接