【问题标题】:Modify Entity Framework's expression tree as close as possible to T-SQL translation\execution修改 Entity Framework 的表达式树尽可能接近 T-SQL 翻译\执行
【发布时间】:2018-06-30 21:32:20
【问题描述】:

我已经能够使用ExpressionVisitor 和其他自定义Expressions 修改IQueryable 表达式。

我的问题是使用实体框架(例如 OData)的第三方框架,它们会在内部方法中修改查询,并且在我很难在它们完成后重新修改查询的地方修改查询。

在进程的最后,有一个IQueryable 代表一个表达式树。 Entity Framework 知道如何将该表达式树转换为 T-SQL 并执行它。

我希望修改 Expression\IQueryable 尽可能接近执行。

最好的方法是什么?

【问题讨论】:

  • 我能否澄清一下:您究竟想在这里做什么?为什么要对表达式树进行变异,难道不能在in的路上做吗?
  • 我有一个 xml 列,我想优化我的查询。所以我有一些代码知道如何调用 [xmlcolumn].value(xpath)。修改 SQL 后,我需要处理投影,所以我动态创建了一个匿名类型并将结果投影到它,然后我使用反射将它重新投影到我的原始实体。相当复杂,但效果很好。当我使用 OData ($select\$expand) 创建自定义类型(不是 IQueryable)并让我难以修改表达式树时,我的问题就开始了,尤其是当类是内部的时。跨度>

标签: c# entity-framework expression odata iqueryable


【解决方案1】:

您可以使用实体框架拦截。这允许您在执行之前拦截所有查询(包括导航属性查询)

实体框架允许我们在查询生成的不同方面指定不同类型的拦截器。每个拦截器都可以修改被执行的查询。 拦截器类型有:

  1. IDbCommandInterceptor 执行查询时将调用此拦截器的方法。查询将已在 SQL 中转换,并且将设置参数。

  2. IDbCommandTreeInterceptor 创建命令树时将调用此拦截器的方法。命令树是命令的 AST 表示。生成了两个命令树,一个以概念模型 (DataSpace.CSpace) 的形式表示,此命令树将更接近 LINQ 查询,另一个以存储模型 (DataSpace.SSpace) 的形式表示

  3. IDbConfigurationInterceptor 这个拦截器的方法在DbConfiguration 被加载时被调用。

  4. IDbConnectionInterceptor 在建立连接和发生事务时调用此拦截器的方法。

  5. IDbTransactionInterceptor 在事务提交或回滚时调用此拦截器的方法。

IDbCommandTreeInterceptor 提供了一个很好的方法来获取和更改命令。 AST 很容易理解,Entity Framework 已经提供了基于现有命令创建新命令 AST 的基础设施(命令 AST 是不可变结构,因此我们不能只更改现有结构)。

用法示例:

class CustomExpressionVisitor : DefaultExpressionVisitor
{
    // Override method to mutate the query 
}
class TestInterceptor : IDbCommandTreeInterceptor
{
    public void TreeCreated(DbCommandTreeInterceptionContext interceptionContext)
    {
        if (interceptionContext.Result.DataSpace == DataSpace.CSpace)
        {
            // We only process query command trees 
            // (there can be others such as insert, update or delete
            var queryCommand = interceptionContext.Result as DbQueryCommandTree;
            if (queryCommand != null)
            {
                // A bit of logging to see the original tree
                Console.WriteLine(queryCommand.DataSpace);
                Console.WriteLine(queryCommand);

                // We call the accept method on the command expression with our new visitor. 
                // This method will return our new command expression with the changes the 
                // visitor has made to it
                var newQuery = queryCommand.Query.Accept(new CustomExpressionVisitor());
                // We create a new command with our new command expression and tell 
                // EF to use it as the result of the query
                interceptionContext.Result = new DbQueryCommandTree
                (
                     queryCommand.MetadataWorkspace,
                     queryCommand.DataSpace,
                     newQuery
                 );
                // A bit of logging to see the new command tree
                Console.WriteLine(interceptionContext.Result);
            }

        }

    }
}

// In code before using any EF context.
// Interceptors are registered globally.
DbInterception.Add(new TestInterceptor());

注意:查询计划被缓存,因此第一次遇到查询并缓存时不会调用拦截(您指定的结果将被缓存而不是原始结果)。因此,这对于不依赖于上下文的更改(例如:用户、请求、语言)是安全的。

【讨论】:

  • 非常感谢您的回答。我一直在寻找 LINQ-Expression 解决方案,但这是一个有趣的方向,我将尝试。
  • 很久以前,我开始研究一个允许自动添加条件到执行查询的库。使用一些反射,我设法让 EF 将 LINQ 表达式转换为 DbExpression 并将其拼接到其他命令。 Mabe 里面的一些代码对你有帮助,随意看看github.com/dragomirtitian/Dionysos.Db.Autofilter
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-02-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多