我目前正在从事一个涉及对现有数据库中的几列进行加密的项目。已经针对当前模式编写了很多代码,其中很多是自定义 linq-to-sql 查询的形式。查询的数量大约是 5 位数,因此修改和重新测试每个人的成本都太高了。
我们发现的另一种方法是保持 DB 模式相同——仅稍微改变列长度,这意味着我们不需要更改当前的实体类定义——而是即时更改表达式树,在他们到达 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
是一个实例变量,它引用了指向当前数据上下文的表达式(我在前一遍中查找)。
我的问题是,如果我有如下查询:
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
不是参数,但访问者绝对应该考虑到它!