如何将表达式树转换为部分 SQL 查询?
当 EF 或 LINQ to SQL 运行查询时,它:
- 从代码构建表达式树,
- 将表达式树转换为 SQL 查询,
- 执行查询,从数据库获取原始结果并将其转换为要使用的结果该应用程序。
查看堆栈跟踪,我无法弄清楚第二部分发生在哪里。
一般来说,是否可以使用 EF 的现有部分或(最好)LINQ to SQL 将 Expression
对象转换为部分 SQL 查询(使用 Transact-SQL 语法),否则我必须重新发明轮子?
更新:评论要求提供我正在尝试做的事情的示例。
实际上,Ryan 的答案下面的 Wright 完美地说明了我想要实现的结果,除了我的问题具体是关于我如何通过使用实际使用的 .NET Framework 的现有机制来做到这一点EF 和 LINQ to SQL,而不必重新发明轮子并自己编写数千行未经测试的代码来完成类似的事情。
这里还有一个例子。再次注意,没有 ORM 生成的代码。
private class Product
{
[DatabaseMapping("ProductId")]
public int Id { get; set; }
[DatabaseMapping("Price")]
public int PriceInCents { get; set; }
}
private string Convert(Expression expression)
{
// Some magic calls to .NET Framework code happen here.
// [...]
}
private void TestConvert()
{
Expression<Func<Product, int, int, bool>> inPriceRange =
(Product product, int from, int to) =>
product.PriceInCents >= from && product.PriceInCents <= to;
string actualQueryPart = this.Convert(inPriceRange);
Assert.AreEqual("[Price] between @from and @to", actualQueryPart);
}
预期查询中的名称Price
来自哪里?
可以通过查询的自定义
属性。DatabaseMapping
属性通过反射获得该名称Product
类的 Price
名称@from
和@to
在预期的查询中来自哪里?
这些名称是表达式参数的实际名称。
预期查询中Between … and
来自哪里?
这是二进制表达式的可能结果。也许 EF 或 LINQ to SQL 会坚持使用 [Price] >= @from 和 [Price] <= @to
来代替 Between … and
语句。也没关系,这并不重要,因为结果在逻辑上是相同的(我没有提到性能)。
为什么预期查询中没有 where
?
因为 Expression
中没有任何内容表明必须有 where
关键词。也许实际的表达式只是稍后与二元运算符组合以构建更大的查询并在 where
前面添加的表达式之一。
When EF or LINQ to SQL runs a query, it:
- Builds an expression tree from the code,
- Converts the expression tree into an SQL query,
- Executes the query, gets the raw results from the database and converts them to the result to be used by the application.
Looking at the stack trace, I can't figure out where the second part happens.
In general, is it possible to use an existent part of EF or (preferably) LINQ to SQL to convert an Expression
object to a partial SQL query (using Transact-SQL syntax), or I have to reinvent the wheel?
Update: a comment asks to provide an example of what I'm trying to do.
Actually, the answer by Ryan Wright below illustrates perfectly what I want to achieve as a result, except the fact that my question is specifically about how can I do it by using existent mechanisms of .NET Framework actually used by EF and LINQ to SQL, instead of having to reinvent the wheel and write thousands of lines of not-so-tested code myself to do the similar thing.
Here is also an example. Again, note that there is no ORM-generated code.
private class Product
{
[DatabaseMapping("ProductId")]
public int Id { get; set; }
[DatabaseMapping("Price")]
public int PriceInCents { get; set; }
}
private string Convert(Expression expression)
{
// Some magic calls to .NET Framework code happen here.
// [...]
}
private void TestConvert()
{
Expression<Func<Product, int, int, bool>> inPriceRange =
(Product product, int from, int to) =>
product.PriceInCents >= from && product.PriceInCents <= to;
string actualQueryPart = this.Convert(inPriceRange);
Assert.AreEqual("[Price] between @from and @to", actualQueryPart);
}
Where does the name Price
come from in the expected query?
The name can be obtained through reflection by querying the custom DatabaseMapping
attribute of Price
property of Product
class.
Where do names @from
and @to
come from in the expected query?
Those names are the actual names of the parameters of the expression.
Where does between … and
come from in the expected query?
This is a possible result of a binary expression. Maybe EF or LINQ to SQL would, instead of between … and
statement, stick with [Price] >= @from and [Price] <= @to
instead. It's ok too, it doesn't really matter since the result is logically the same (I'm not mentioning performance).
Why there is no where
in the expected query?
Because nothing indicates in the Expression
that there must be a where
keyword. Maybe the actual expression is just one of the expressions which would be combined later with binary operators to build a larger query to prepend with a where
.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(9)
是的,这是可能的,您可以使用访问者模式解析 LINQ 表达式树。您需要通过子类化 ExpressionVisitor 来构造查询转换器,如下所示。通过连接到正确的点,您可以使用翻译器从 LINQ 表达式构造 SQL 字符串。请注意,下面的代码仅处理基本的 where/orderby/skip/take 子句,但您可以根据需要填写更多内容。希望这是良好的第一步。
然后通过调用访问表达式:
Yes it is possible, you can parse a LINQ expression tree using the visitor pattern. You would need to construct a query translator by subclassing ExpressionVisitor like below. By hooking into the correct points you can use the translator to construct your SQL string from your LINQ expression. Note that the code below only deals with basic where/orderby/skip/take clauses, but you can fill it out with more as needed. Hopefully it serves as a good first step.
Then visit the expression by calling:
简短的回答似乎是,您不能使用 EF 或 LINQ to SQL 的部分作为翻译的快捷方式。您至少需要
ObjectContext
的子类才能访问内部受保护的
QueryProvider 属性,这意味着创建上下文的所有开销,包括所有元数据等。假设您对此表示同意,要获得部分 SQL 查询,例如,只需
WHERE
子句,您基本上需要查询提供程序并调用 IQueryProvider.CreateQuery() 正如 LINQ 在其 可查询。Where。要获得更完整的查询,您可以使用 ObjectQuery.ToTraceString()。至于发生这种情况的位置,LINQ 提供程序基础通常指出:
,并且
在调试器下检查EF来执行它是前者。
如果您不想完全重新发明轮子,并且 EF 和 LINQ to SQL 都不是选项,也许本系列文章会有所帮助:
以下是创建查询提供程序的一些来源,可能涉及更多内容实现您想要的内容需要您的繁重工作:
The short answer seems to be that you cannot use a part of EF or LINQ to SQL as a shortcut to translation. You need at least a subclass of
ObjectContext
to get at theinternal protected
QueryProvider property, and that means all the overhead of creating the context, including all the metadata and so on.Assuming you are ok with that, to get a partial SQL query, for example, just the
WHERE
clause you're basically going to need the query provider and call IQueryProvider.CreateQuery() just as LINQ does in its implementation of Queryable.Where. To get a more complete query you can use ObjectQuery.ToTraceString().As to where this happens, LINQ provider basics states generally that
and that
Checking EF under the debugger it's the former.
If you don't want to completely re-invent the wheel and neither EF nor LINQ to SQL are options, perhaps this series of articles would help:
Here are some sources for creating a query provider that probably involve much more heavy lifting on your part to implement what you want:
在搜索了几个小时的表达式树到 SQL 转换器的实现后,我没有找到任何有用的或免费的或以某种方式与 .NET Core 一起使用的东西。
然后我发现了这个。谢谢瑞安·赖特。
我拿了他的代码并进行了一些修改以满足我的需要。现在我把它回馈给社区。
当前版本可以执行以下操作:
批量更新
这将生成以下 sql
批量删除
生成的 sql
输出 SQL 语句
这生成 sql
另一个示例
sql
另一个带有局部变量的示例
生成的 sql
SimpleExpressionToSQL 类本身可以直接使用
代码
这里使用的评估器来自此处
SimpleExpressionToSQL
我会在评论中发布扩展
编辑:评论太长了...我会添加另一个答案。
在生产中谨慎使用它
随意使用它制作 Nuget 包:)
After searching for hours for an implementation of an Expression tree to SQL converter, I did not found anything usefull or free or somehow working with .NET Core.
Then I found this. Thank you Ryan Wright.
I took his code and modified it a bit to fit my needs. Now I am giving it back to the community.
Current version can do the following:
Bulk update
This will produce the following sql
Bulk delete
The produced sql
Outputing SQL Statements
This produces the sql
Another example
The sql
Another example with a local variable
The produced sql
The SimpleExpressionToSQL class itself can be used directly
The code
The evaluator used here come from here
SimpleExpressionToSQL
I will post the extension in the comments
Edit: too long for the comment... I'll add another answer.
Use it with caution on production
Feel free to make a Nuget package out of it :)
它并不完整,但如果您稍后遇到此问题,可以参考以下一些想法:
It isn't complete, but here are some thoughts for you to groove on if you come by this later:
在 Linq2SQL 中您可以使用:
In Linq2SQL you can use:
你基本上必须重新发明轮子。 QueryProvider 负责将表达式树转换为其存储本机语法。它也将处理特殊情况,例如 string.Contains()、string.StartsWith() 以及处理它的所有特殊函数。它还将处理 ORM 各个层中的元数据查找(在数据库优先或模型优先实体框架的情况下为 *.edml)。已经有用于构建 SQL 命令的示例和框架。但您正在寻找的听起来像是部分解决方案。
还要了解需要表/视图元数据才能正确确定什么是合法的。查询提供程序非常复杂,除了将简单的表达式树转换为 SQL 之外,还为您做了很多工作。
回应你的第二部分发生在哪里。第二部分发生在 IQueryable 枚举期间。 IQueryables 也是 IEnumerables,最终当调用 GetEnumerator 时,它将依次使用表达式树调用查询提供程序,该表达式树将使用其元数据生成 sql 命令。这并不完全是发生的事情,但它应该传达这个想法。
You basically have to re-invent the wheel. The QueryProvider is the thing that does the translation from expression trees to it's store native syntax. It's the thing that's going to handle special situations as well like string.Contains(), string.StartsWith(), and all the specialty functions that handle it. It's also going to handle metadata lookups in the various layers of your ORM (*.edml in the case of database-first or model-first Entity Framework). There are already examples and frameworks for building out SQL commands. But what you're looking for sounds like a partial solution.
Also understand that table/view metadata is required to correctly determine what is legal. The query providers are quite complex and do a lot of work for you beyond making simple expression tree conversions into SQL.
In response to your where does the second part happen. The second part happens during enumeration of the IQueryable. IQueryables are also IEnumerables and ultimately when GetEnumerator is called it in turn is going to call the query provider with the expression tree which is going to use its metadata to produce a sql command. It's not exactly what happens, but it should get the idea accross.
您可以使用以下代码:
查看以下信息:检索实体提供程序生成的 SQL。
You can use the following code:
Have a look at the following information: Retrieving the SQL generated by the Entity Provider.
SimpleExpressionToSQL 类的扩展
Extensions for the SimpleExpressionToSQL class
不确定这是否正是您所需要的,但看起来可能很接近:
Not sure if this is exactly what you need, but it looks like it might be close: