我目前正在开展一个涉及加密现有数据库中几列的项目。已经针对当前模式编写了相当多的代码,其中很多是以自定义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
的引用不是参数,但访客必须考虑它!
答案 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);
}