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

Posted

技术标签:

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

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

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


假设我有两张桌子:

旧数据 新数据

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


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

【问题讨论】:

没有转至 EF 的选项? 【参考方案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() 我已经改变了那里的表......

以上是关于如何在代码中定义/更改 Linq To Sql 的映射的主要内容,如果未能解决你的问题,请参考以下文章

在使用 Linq To SQL 时,如果其他列没有更改,如何避免设置某些列?

如何在LINQ-To-SQL中排除Contex.InsertOnSubmit()上的用户定义字段?

如何在 Linq to SQL 中使用 NOEXPAND 提示?

LINQ to SQL 设计器错误

更改存储库以支持 IQueryable(LINQ to SQL 查询)

Linq to SQL 通过 BLL 问题更新 - 最佳实践