如何在 LINQ to Entities 查询中实现查询拦截? (c#)
我正在尝试在 EF4 中实现加密列,并使用 CTP5 功能来允许简单地使用 POCO 来查询数据库。抱歉,这是很多话,但我希望下面的内容足以解释需求和问题!
所以,一些背景知识和我到目前为止的进展:
目的是,如果您在不使用我们的 DAL 的情况下查询表,那么数据就是垃圾,但我不希望开发人员担心数据是否/何时/如何加密的。
为简单起见,现阶段我假设任何字符串列都将被加密。
现在,我已经成功实现了使用 Objectmaterialized 事件返回数据以及使用 SavingChanges 事件提交数据的功能。
因此,给出以下类:
public class Thing
{
public int ID { get; set; }
[Required]
public string Name { get; set; }
public DateTime Date { get; set; }
public string OtherString { get; set; }
}
以下查询返回所有必需的值,并且 POCO 物化中包含清晰的数据。
var things = from t in myDbContext.Things
select t;
其中 myDbContext.Things 是 DbSet
同样,将 Thing
的实例传递给 Things.Add()
(名称和/或其他字符串值中包含清晰的字符串数据)
然后调用 myDbContext.SaveChanges()
在字符串到达数据存储之前对其进行加密。
现在,我遇到的问题是在这个查询中:
var things = from t in myDbContext.Things
where t.Name == "Hairbrush"
select t;
这会导致将未加密的值与数据库中的加密值进行比较。显然我不想从数据库中获取所有记录,具体化它们,然后根据任何提供的Where子句过滤结果......所以我需要做的是:拦截该查询并重写它加密Where子句中的任何字符串。 所以我研究过:
- 编写一个查询提供程序,但这似乎不是正确的解决方案...(是吗?)
- 为 DbSet 编写我自己的 IQueryable 包装器,它将捕获表达式,使用表达式树访问者运行它,然后将新表达式转发到 DbSet...
这两种尝试都让我有些迷失!我认为我更喜欢第二种解决方案,因为它感觉更简洁,并且将来对其他开发人员来说可能更清晰。但我很乐意选择其中一个或另一个更好选项!
我遇到的主要问题是 LINQ 表达式何时/如何应用于对象...我想我自己对表达式在 IQueryable 对象中执行的位置有点困惑,因此我不确定哪种方法我需要在我的包装器中实现,然后抓取和操作传入的表达式...
我确信我在这里遗漏了一些相当明显的东西,我正在等待那个灯泡时刻...但它没有来! !
任何帮助将非常感激!
I'm trying to implement encrypted columns in EF4 and using the CTP5 features to allow simple use of POCO's to query the database. Sorry that this is a lot of words, but I hope the below gives enough to explain the need and the problem!
So, bit of background, and my progress so far:
The intention is that if you query the tables without using our DAL then the data is rubbish, but I don't want the developers to worry about if/when/how the data is encrypted.
For simplicity, at this stage I'm working on the assumption any string column will be encrypted.
Now, I have successfully implemented this for returning the data using the Objectmaterialized event, and for data commits using the SavingChanges event.
So given the following class:
public class Thing
{
public int ID { get; set; }
[Required]
public string Name { get; set; }
public DateTime Date { get; set; }
public string OtherString { get; set; }
}
The below query returns all the required values and the POCO materialized has clear data in it.
var things = from t in myDbContext.Things
select t;
where myDbContext.Things is a DbSet<Thing>
Likewise, passing an instance of Thing
to Things.Add()
(with clear string data in the Name and/or OtherString values)
and then calling myDbContext.SaveChanges()
encrypts the strings before it gets to the data store.
Now, the problem I have is in this query:
var things = from t in myDbContext.Things
where t.Name == "Hairbrush"
select t;
This results in the unencrypted value being compared to the encrypted value in the DB. Obviously I don't want to get all the records from the database, materialize them, and then filter the results based on any supplied Where clause... so what I need to do is: intercept that query and rewrite it by encrypting any strings in the Where clause.
So I've looked at:
- writing a query provider, but that doesn't seem like the right solution... (is it?)
- writing my own IQueryable wrapper for the DbSet which will capture the expression, run over it using an expression tree visitor and then forward the new expression to the DbSet...
Attempts at both have left me somewhat lost! I prefer the second solution i think since it feels a bit neater, and is probably clearer to other developers in future. But I'm happy to go with either or another better option!!
The main thing I am struggling with is when/how the LINQ expression is applied to the object... I think i've got myself a bit confused as to where the expression executes in the IQueryable object thus I'm not sure which method I need to implement in my wrapper to then grab and manipulate the expression being passed in...
I'm sure I'm missing something fairly obvious here and I'm waiting for that light bulb moment... but its not coming!!
Any help will be very gratefully received!
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
我想让你知道我的最终解决方案是什么。
最后,我使用了一个包装类,它实现了Where 方法,但没有达到完全实现IQueryable 的程度。 LINQ 仍将针对该类执行(至少在我想要/需要的范围内),并将使用 LINQ 中的表达式调用Where 方法。
然后,我遍历此表达式树并用加密值替换字符串,然后将新的表达式树转发到内部 DbSet。然后返回结果。
它相当粗糙,并且有其局限性,但对于我们的特定情况来说没有问题。
谢谢,
本
Thought I'd let you know what my final solution was.
In the end I have gone a wrapper class which implements a Where method, but without going to the extent of implementing IQueryable entirely. LINQ will still execute against the class (at least to the extent that I want/need it to) and will call the Where method with the expression from the LINQ.
I then traverse this ExpressionTree and replace my strings with encrypted values before forwarding the new expressiontree to the internal DbSet. and then returning the result.
Its pretty crude, and has its limitation, but works for our particular circumstance without problem.
Thanks,
Ben
您应该使用
QueryInterceptor
属性,在 SO 或 google 中搜索,您可以找到有关如何使用它的示例。一个片段:
you should use the
QueryInterceptor
attribute, search here in SO or in google and you find examples on how to use it.a snippet:
您可以使用 David Fowler 的查询拦截器:
https://github.com/davidfowl/QueryInterceptor
其一个示例使用:
IQueryable q = ...;
IQueryable 修改 = q.InterceptWith(new MyInterceptor());
在类 MyInterceptor 上:
protected override Expression VisitBinary(BinaryExpression node) {
if (node.NodeType == ExpressionType.Equal) {
// 将 == 更改为 !=
return Expression.NotEqual(node.Left, node.Right);
}
返回base.VisitBinary(节点);
}
You can use David Fowler's Query Interceptor:
https://github.com/davidfowl/QueryInterceptor
One example of its use:
IQueryable q = ...;
IQueryable modifed = q.InterceptWith(new MyInterceptor());
And on class MyInterceptor:
protected override Expression VisitBinary(BinaryExpression node) {
if (node.NodeType == ExpressionType.Equal) {
// Change == to !=
return Expression.NotEqual(node.Left, node.Right);
}
return base.VisitBinary(node);
}