确定LINQ表达式树中MemberExpression的根对象

时间:2013-05-31 16:03:31

标签: linq linq-to-sql

我目前正在开展一个涉及加密现有数据库中几列的项目。已经针对当前模式编写了相当多的代码,其中很多是以自定义linq-to-sql查询的形式。查询的数量在5位数的附近,因此修改和重新测试每个查询都会太昂贵。

我们发现的一个替代方案是保持DB模式相同 - 只是略微改变列长度,这意味着我们不需要更改当前的实体类定义 - 而是更改表达式树 - -fly,在它们到达l2sql IQueryProvider之前,并在我需要的列上应用解密函数。我通过使用自定义Table<TEntity>实现包装DataContext的相关IQueryable<TEntity>属性来实现此目的,这允许我预览系统中的每个查询。

在我目前的实施中,说我有这个问题:

var mydate = new DateTime(2013, 1, 1);
var context = new DataContextFactory.GetClientsContext();

Expression<Func<string>> foo = context.MyClients.First(
    c => c.BirthDay < mydate).EncryptedColumn;

但是当我抓住查询时,我将其更改为:

Expression<Func<string>> foo = context.Decrypt(
    context.MyClients.First(c => c.BirthDay < mydate).EncryptedColumn);

我使用ExpressionVisitor类来完成此操作。在VisitMember方法中,我检查并查看当前MemberExpression是否指向加密列。如果是,我将表达式替换为方法调用:

private const string FuncName = "Decrypt";

protected override Expression VisitMember(MemberExpression ma)
{
    if (datactx != null && IsEncryptedColumnReference(ma))
        return MakeCallExpression(ma);
    }

    return base.VisitMember(ma);
}

private static bool IsEncryptedColumnReference(MemberExpression ma)
{
    return ma.Member.Name == "EncryptedColumn"
        && ma.Member.DeclaringType == typeof(MyClient);
}

private Expression MakeCallExpression(MemberExpression ma)
{
    const BindingFlags flags = BindingFlags.Instance | BindingFlags.Public;
    var mi = typeof(MyDataContext).GetMethod(FuncName, flags);
    return Expression.Call(datactx, mi, ma);
}

datactx是一个实例变量,引用指向当前datacontext的表达式(我在上一次传递中查找)。

我的问题是,如果我有一个查询,例如:

var qbeClient = new MyClient { EncryptedColumn = "FooBar" };

Expression<Func<MyClient>> dbquery = () => context.MyClients.First(
    c => c.EncryptedColumn == qbeClient.EncryptedColumn);

我希望它变成:

Expression<Func<MyClient>> dbquery = () => context.MyClients.First(c =>
    context.Decrypt(c.EncryptedColumn) == qbeClient.EncryptedColumn);
相反,我得到的是:

Expression<Func<MyClient>> dbquery = () => context.MyClients.First(c => 
    context.Decrypt(c.EncryptedColumn) == context.Decrypt(qbeClient.EncryptedColumn));

我不想要,因为当我有一个内存中的对象时,数据已经是未加密的(此外,我不希望对我的对象进行令人讨厌的db函数调用!)

所以,基本上我的问题是:拥有一个MemberExpression实例,如何确定它是引用内存中对象还是数据库中的行?

提前致谢

  

编辑:

     

@Shlomo的代码实际上解决了我发布的案例,但现在我之前的一个测试被打破了:

var context = new DataContextFactory.GetClientsContext();

Expression<Func<string>> expr = context.MyClients.First().EncryptedColumn;

Expression<Func<string>> expected = context.Decrypt(
    context.MyClients.First().EncryptedColumn);

var actual = MyVisitor.Visit(expr);

Assert.AreEqual(expected.ToString(), actual.ToString());
     

在这种情况下,对EncryptedColumn的引用不是参数,但访客必须考虑它!

1 个答案:

答案 0 :(得分:1)

表示数据库行的MemberExpression将是ParameterExpression的后代。内存中的对象不会,它们很可能来自某种形式的FieldExpression

在您的情况下,类似的内容适用于大多数情况(为您的代码添加一种方法,并修改您的VisitMember方法:

private bool IsFromParameter(MemberExpression ma)
{
    if(ma.Expression.NodeType == ExpressionType.Parameter)
        return true;

    if(ma.Expression is MemberExpression)
        return IsFromParameter(ma.Expression as MemberExpression);

    return false;
}


protected override Expression VisitMember(MemberExpression ma)
{
    if (datactx != null && IsEncryptedColumnReference(ma) && IsFromParameter(ma))
        return MakeCallExpression(ma);
    }

    return base.VisitMember(ma);
}