【问题标题】:How to I define/change the mappings for Linq To Sql in code如何在代码中定义/更改 Linq To Sql 的映射
【发布时间】:2010-04-29 15:15:47
【问题描述】:

我希望能够在运行时更改类映射到的表,如果所有映射都用属性定义,我不能这样做。因此有没有办法在代码中在运行时定义映射。

(我宁愿不必维护 xml 映射文件。)


假设我有两张桌子:

  • 旧数据
  • 新数据

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


另见“How to map an Entity framework model to a table name dynamically

【问题讨论】:

  • 没有转至 EF 的选项?

标签: .net linq-to-sql orm


【解决方案1】:

为了使其真正透明,您必须跳过一些非常疯狂的环节,但可以通过使用您自己的派生类型覆盖所有 Meta*** 类来完成。

对于像 Castle 这样的代理/方法拦截库,这实际上是相当简单的,但假设这里的公分母最低,实现每个元方法来包装原始类型基本上是一个漫长而无聊的考验,因为你可以' t 直接派生自任何属性映射类。

我会尽量坚持这里的重要覆盖;如果您在下面的代码中没有看到特定的方法/属性,则意味着该实现实际上是一个单行代码,它包装了“内部”方法/属性并返回结果。我已经在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 个属性/方法,我已经排除了仅 return 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) 对此进行了测试。有可能,实际上很有可能,我在非常基本的测试中遗漏了一些东西。哦,这只有在两个表字面上完全相同、列名和所有内容都相同的情况下才有效。

如果这个表有任何关联(外键),它可能会搞砸,因为您也必须在 both 两端覆盖关联名称。我将把它作为练习留给读者,因为想想它会让我头疼。您最好从这个特定的表中删除任何关联,这样您就不必处理那个令人头疼的问题。

玩得开心!

【讨论】:

  • 谢谢,因为我已经被其他从事该项目的人告诉我使用“原始 sql”,因为他认为 LinqToSql 很慢,并且只是重写了任何 LingToSql 代码以使用行 sql 而没有试图在第一时间了解 LinqToSql 发生了什么。这可能是这里使用任何 ORM 系统的最后一颗钉子了……
  • @Ian:你意识到这些不是相互排斥的选择,对吧?我有一些相当大的项目使用 Linq to SQL 作为后端,但是大约 20-30% 的功能是通过存储过程和通过 ExecuteQueryExecuteCommandExecuteMethodCall 的即席查询进行路由的,其中如果您只有少量查询需要进行“动态”处理,也可以在此处用作您的解决方法。在剩下的时间里,我仍然使用 L2S 节省了大量时间。不要把婴儿和洗澡水一起扔出去;为了让 L2S 与“原始 SQL”更好地配合,微软费了很大的力气。
【解决方案2】:

使用 GenericList 来分类查询这个

公共类 Querybuilder where T: Class {

}

【讨论】:

  • 这在 .net 3.5 中不起作用,但可能适用于我们迁移到 .net 4 的情况
【解决方案3】:

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

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

【讨论】:

  • 谢谢,随着项目的进行,我还没有对此进行测试,但这看起来是一个可行的答案。
  • 有没有办法将表名重置为默认值?当我做 MyModelDataContext.Create() 我已经改变了那里的表......
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2010-12-28
  • 2023-03-14
  • 1970-01-01
  • 1970-01-01
  • 2010-11-26
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多