0

我目前正在从事一个涉及对现有数据库中的几列进行加密的项目。已经针对当前模式编写了很多代码,其中很多是自定义 linq-to-sql 查询的形式。查询的数量大约是 5 位数,因此修改和重新测试每个人的成本都太高了。

我们发现的另一种方法是保持 DB 模式相同——仅稍微改变列长度,这意味着我们不需要更改当前的实体类定义——而是即时更改表达式树,在他们到达 l2sql 之前IQueryProvider,对我需要的列应用解密函数。我通过使用自定义实现包装我的相关Table<TEntity>属性来做到这一点,这允许我预览系统中的每个查询。DataContextIQueryable<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是一个实例变量,它引用了指向当前数据上下文的表达式(我在前一遍中查找)。

我的问题是,如果我有如下查询:

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不是参数,但访问者绝对应该考虑到它!

4

1 回答 1

1

MemberExpression表示数据库行的A将是 a 的后代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);
}
于 2013-05-31T20:21:08.987 回答