在LinqtoSQL中使用主键

3

我正在尝试编写一些通用的 Linq to SQL 代码,基于实体的主键执行操作。我所处理的一些实体具有组合主键。

基本上,我想能够执行以下操作:

if( PrimaryKey(foo) == PrimaryKey(bar) ) ...
或者,
from oldEntity in oldTableOrCollection
join newEntity in newTableOrCollection
on PrimaryKeyOf(oldEntity) equals PrimaryKeyOf(newEntity)
select new {oldEntity, newEntity}

只要实体具有主键,就可以满足要求。

到目前为止,我找到了以下内容:

可以进行以下操作:

var query = from row in oneTableOfRows
            join otherRow in anotherTableOfRows on
            new { row.Column1, row.Column2 }
            equals
            new { otherRow.Column1, otherRow.Column2}
            select ...;

在其中,linqtosql将使用匿名类型进行比较(前提是属性名称匹配)。

然后,我可以抽象出用于选择要连接的列的方法,以允许通用行类型:

void DoQuery<RowType,KeyType>(Table<RowType> oneTableOfRows,
                         Table<RowType> anotherTableOfRows,
                         Func<RowType, KeyType> keySelector)
{
    var query = from row in oneTableOfRow
            join otherRow in anotherTableOfRows on
            keySelector(row)
            equals
            keySelector(otherRow)
            select ...;
}

...

Func<rowType, keyType> myKeySelector = row => new { row.Column1, row.Column2 };

DoQuery(table, otherTable, myKeySelector);
我现在尝试转移到的是,keySelector将选择任何RowType的主键。我正在使用基于http://www.codeplex.com/l2st4的自定义模板来生成我的实体(它本身与VS中的默认模板相当相似)。 我希望能够为每个生成的RowType提供从dbml中获取其主键的能力。到目前为止,输出如下:
public interface IPrimaryKeyEntity<PrimaryKeyType>
{
    PrimaryKeyType PrimaryKey { get; }
}

//Here, Product is a table with composite primary key based on its ProductID and it's ProductPriceVersionID columns

//I've tried using both class and struct as the primary key type for entities
public class ProductPrimaryKey
{
    public Guid ProductID;
    public Guid ProductPriceVersionID;
}

public partial class Product : IPrimaryKeyEntity<ProductPrimaryKey>,
                               INotifyPropertyChanging, INotifyPropertyChanged
{
    #region IPrimaryKeyEntity Implementation
public ProductPrimaryKey PrimaryKey
    {
        get
        { return new ProductPrimaryKey()
                     {
                         ProductID = this.ProductID,
                         ProductPriceVersionID = this.ProductPriceVersionID
                     };
        }
    }
#endregion

    ...
    //Rest is mostly standard LinqToSql entity generation
}

回到最初的目标,我现在可以为所有主键实体编译以下内容:

from oldEntity in oldTableOrCollection
join newEntity in newTableOrCollection
on oldEntity.PrimaryKey equals newEntity.PrimaryKey
select new {oldEntity, newEntity}
然而,在运行时,我遇到了臭名昭著的[PrimaryKey]“没有受支持的SQL翻译”的异常。 我知道要翻译成 SQL,需要使用,但我对非常不熟悉,尝试将其应用于我的情况还没有取得任何进展... 如果有人能改进我目前的做法或者有更好的方法,我将不胜感激。 谢谢。

1
在调试器中探索您(工作)查询生成的 Expression 应该是一个很好的起点。它位于“非公共成员”>“queryExpression”下。我也是新手,但如果我发现什么,我会告诉您:) - Vladislav Zorov
@VladislavZorov:感谢您的建议!明天我回到办公室时一定会试试看是否有帮助。 - EdF
1个回答

2
问题在于你在这里调用了一个函数

join ... on oldEntity.PrimaryKey equals newEntity.PrimaryKey

属性获取是一个函数调用。Linq to SQL 不知道这个函数,因此无法翻译它。

唯一的解决方法是构建一个对应于此 SQL 的表达式:

        join otherRow in anotherTableOfRows on
        new { row.Column1, row.Column2 }
        equals
        new { otherRow.Column1, otherRow.Column2}

我可以向您保证,没有其他方法。您可以按照以下方式操作:首先,声明一个自定义元组类:

class CustomTuple2<T1,T2>
{
 public T1 Item1 { get; set; }
 public T2 Item2 { get; set; }
}

您需要对所有可能的成员数量(例如8或16)进行此操作。

让我们看一下如何将连接翻译:

IQueryable<T1> t1 = ...; //table 1
IQueryable<T2> t2 = ...; //table 2
var joined = t1.Join(t2, _t1 => _t1.Key, _t2 => _t2.Key);

lambda参数是表达式。你需要使用表达式树构建这两个表达式。这就是魔法所在!

例如,第一个lambda:

var param = Expression.Parameter(typeof(T1), "_t1");
var key = Expression.Property(param, "Key");
var lambda1 = Expression.Lambda(param, key);

对于第二个 lambda,也要执行相同的操作。(请记住,这只是伪代码,我不太记得每个东西的具体名称。)

最后,调用 Join 方法:

var joined = typeof(Queryable).GetMethod("Join").Invoke(t1, t2, lambda1, lambda2);

这就是如何在运行时建立连接的过程!请注意,所有这些都是伪代码。你需要研究API并使其工作,但你肯定能做到。

此外,如果你有一个组合键,你需要使用提到的元组类来将键成员投影出来。


谢谢,看起来很有前途。不知道这种方法是否适用于明确定义的 FooKey 结构/类,因为我目前正在生成它们,而不是元组?另外,有没有一种好的方式将表达式树与属性结合起来,以便 LinqToSql 使用与常规运行时属性访问相同的接口?比如拥有一个属性,根据需要完全评估或返回表达式树? - EdF
a) 它也可以与显式类一起使用。重要的是,您的DTO类具有默认构造函数和可分配的自动属性。 b) 我知道你的意思。这很困难。你想要的是在L2S和L2Objects中都能工作的“计算列”。可能最好的方法是在数据上下文的IQueryable实现前面放置一个外观,并在它到达L2S之前重写表达式树。但这可能超出了你的范围。 - usr

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