linq 动态表达式_动态构建LINQ表达式

本文深入探讨了.NET中的LINQ动态表达式,解释了如何使用它们来构建查询。通过示例展示了如何从JSON解析表达式树,并应用于内存对象和数据库查询。文章还介绍了如何利用动态表达式在数据库操作中实现延迟执行,以及如何在EF Core中工作。
摘要由CSDN通过智能技术生成

linq 动态表达式

Dynamically Build LINQ Expressions

LINQ stands for Language Integrated Query and is one of my favorite .NET and C# technologies. Using LINQ, developers can write queries directly in strongly typed code. LINQ provides a standard language and syntax that is consistent across data sources.

LINQ代表语言集成查询,是我最喜欢的.NET和C#技术之一。 使用LINQ,开发人员可以直接在强类型代码中编写查询。 LINQ提供了在数据源之间保持一致的标准语言和语法。

基础 (The Basics)

Consider this LINQ query (you can paste this into a console application and run it yourself):

考虑以下LINQ查询(您可以将其粘贴到控制台应用程序中并自己运行):

using System;
using System.Linq;
public class Program
{
    public static void Main()
    {
        var someNumbers = new int[]{4, 8, 15, 16, 23, 42};
        var query = from num in someNumbers
                    where num > 10
                    orderby num descending
                    select num.ToString();
        Console.WriteLine(string.Join('-', query.ToArray()));
        // 42-23-16-15
    }
}

Because someNumbers is an IEnumerable<int>, the query is parsed by LINQ to Objects. The same query syntax can be used with a tool like Entity Framework Core to generate T-SQL that is run against a relational database. LINQ can be written using one of two syntaxes: query syntax (shown above) or method syntax. The two syntaxes are semantically identical and the one you use depends on your preference. The same query above can be written using method syntax like this:

由于someNumbersIEnumerable<int> ,因此LINQ将查询解析为Objects 。 相同的查询语法可以与诸如Entity Framework Core之类的工具一起使用,以生成针对关系数据库运行的T-SQL。 可以使用以下两种语法之一来编写LINQ: 查询语法 (如上所示)或方法语法 。 两种语法在语义上是相同的,您使用哪种语法取决于您的偏好。 可以使用如下方法语法编写以上相同的查询:

var secondQuery = someNumbers.Where(n => n > 10)
                             .OrderByDescending(n => n)
                             .Select(n => n.ToString());

Every LINQ query has three phases:

每个LINQ查询都有三个阶段:

  1. A data source is set up, known as a provider, for the query to act against. For example, the code shown so far uses the built-in LINQ to Objects provider. Your EF Core projects use the EF Core provider that maps to your database.

    设置了一个数据源(称为提供者) ,以使查询可以采取行动。 例如,到目前为止显示的代码使用内置的LINQ to Objects提供程序。 EF Core项目使用映射到数据库的EF Core提供程序

  2. The query is defined and turned into an expression tree. I’ll cover expressions more in a moment.

    定义查询并将其转换为表达式树 。 一会儿我将介绍更多表情。

  3. The query is executed, and data is returned.

    执行查询,并返回数据。

Step 3 is important because LINQ uses what is called deferred execution. In the example above, secondQuery defines an expression tree but doesn’t yet return any data. In fact, nothing actually happens until you start to iterate the data. This is important because it allows providers to manage resources by only delivering the data requested. For example, let’s assume you want to find a specific string using secondQuery, so you do something like this:

第3步很重要,因为LINQ使用了所谓的延迟执行 。 在上面的示例中, secondQuery定义了一个表达式树,但尚未返回任何数据。 实际上,在开始迭代数据之前,实际上什么也没有发生。 这很重要,因为它允许提供商仅通过传递请求的数据来管理资源。 例如,假设您要使用secondQuery查找特定的字符串,那么您可以执行以下操作:

var found = false;
foreach(var item in secondQuery.AsEnumerable())
{
    if (item == "23")
    {
        found = true;
        break;
    }
}

A provider can handle the enumerator so that it feeds data one element at a time. If you find the value on the third iteration, it is possible that only three items were actually returned from the database. On the other hand, when you use the .ToList() extension method, all data is immediately fetched to populate the list.

提供程序可以处理枚举数,以便它一次将一个元素馈入数据。 如果您在第三次迭代中找到该值,则可能实际上只从数据库返回了三项。 另一方面,当使用.ToList()扩展方法时,将立即获取所有数据以填充列表。

挑战 (The Challenge)

For my role as PM for .NET Data, I frequently speak to customers to understand their needs. Recently, I had a discussion with a customer who wants to use a third-party control in their website to build business rules. More specifically, the business rules are “predicates” or a set of conditions that resolve to true or false. The tool can generate the rules in JSON or SQL format. SQL is tempting to pass along to the database, but their requirement is to apply the predicates to in-memory objects as a filter on the server as well. They are considering a tool that translates SQL to expressions (it’s called Dynamic LINQ if you’re interested). I suggested that the JSON format probably is fine, because it can be parsed into a LINQ expression that is run against objects in memory or easily applied to an Entity Framework Core collection to run against the database.

作为.NET Data的PM,我经常与客户交谈以了解他们的需求。 最近,我与一个客户进行了讨论,该客户希望使用其网站中的第三方控件来建立业务规则。 更具体地说,业务规则是“谓词”或一组解析为truefalse的条件。 该工具可以生成JSON或SQL格式的规则。 SQL很想传递给数据库,但是它们的要求是将谓词也应用到内存中对象,作为服务器上的筛选器。 他们正在考虑将SQL转换为表达式的工具(如果您感兴趣的话,称为Dynamic LINQ )。 我建议JSON格式可能很好,因为可以将其解析为针对内存中的对象运行的LINQ表达式,也可以轻松地将其应用于Entity Framework Core集合以针对数据库运行。

The spike I wrote only deals with the default JSON produced by the tool:

我写的峰值仅处理该工具生成的默认JSON:

{
   "condition":"and",
   "rules":[
      {
         "label":"Category",
         "field":"Category",
         "operator":"in",
         "type":"string",
         "value":[
            "Clothing"
         ]
      },
      {
         "condition":"or",
         "rules":[
            {
               "label":"TransactionType",
               "field":"TransactionType",
               "operator":"equal",
               "type":"boolean",
               "value":"income"
            },
            {
               "label":"PaymentMode",
               "field":"PaymentMode",
               "operator":"equal",
               "type":"string",
               "value":"Cash"
            }
         ]
      },
      {
         "label":"Amount",
         "field":"Amount",
         "operator":"equal",
         "type":"number",
         "value":10
      }
   ]
}

The structure is simple: there is an AND or OR condition that contains a set of rules that are either comparisons, or a nested condition. My goal was twofold: learn more about LINQ expressions to better inform my understanding of EF Core and related technologies, and provide a simple example to show how the JSON can be used without relying on a third party tool.

结构很简单:存在一个ANDOR 条件 ,其中包含一组比较或嵌套条件的规则。 我的目标是双重的:了解有关LINQ表达式的更多信息,以更好地了解我对EF Core和相关技术的了解,并提供一个简单的示例来说明如何在不依赖第三方工具的情况下使用JSON。

One of my earliest open source contributions was the NoSQL database engine I named Sterling because I wrote it as a local database for Silverlight. It later gained popularity when Windows Phone was released with Silverlight as the runtime and was used in several popular recipe and fitness apps. Sterling suffered from several limitations that could have easily been mitigated with a proper LINQ provider. My goal is to ultimately master enough of LINQ to be able to write my own EF Core provider if the need arises.

我最早的开源贡献之一是命名为Sterling的NoSQL数据库引擎,因为我将其编写为Silverlight的本地数据库。 后来,当Windows Phone与Silverlight作为运行时一起发布时,它开始流行起来,并被用于一些流行的食谱和健身应用程序中。 英镑遭受了一些限制,而这些限制可以通过适当的LINQ提供程序轻松缓解。 我的目标是最终掌握足够的LINQ,以便在需要时编写自己的EF Core提供程序。

解决方案:动态表达式 (The Solution: Dynamic Expressions)

I created a simple console app to test my hypothesis that materializing the LINQ from the JSON would be relatively straightforward.

我创建了一个简单的控制台应用程序来检验我的假设,即从JSON实现LINQ相对简单。

JeremyLikness/ExpressionGenerator

JeremyLikness / ExpressionGenerator

Set the startup project to ExpressionGenerator for the first part of this post. If you run it from the command line, be sure that rules.json is in your current directory.

在本文的第一部分,将启动项目设置为ExpressionGenerator 。 如果从命令行运行它,请确保rules.json在当前目录中。

I embedded the sample JSON as rules.json. Using System.Text.Json to parse the file is this simple:

我将示例JSON嵌入为rules.json 。 使用System.Text.Json解析文件非常简单:

var jsonStr = File.ReadAllText("rules.json");
var jsonDocument = JsonDocument.Parse(jsonStr);

I then created a JsonExpressionParser to parse the JSON and create an expression tree. Because the solution is a predicate, the expression tree is built from instances of BinaryExpression that evaluate a left expression and a right expression. That evaluation might be a logic gate (AND or OR), or a comparison (equal or greaterThan), or a method call. For the case of In i.e., we want property Category to be in one of several items in a list, I flip the script and use Contains. Conceptually, the referenced JSON looks like this:

然后,我创建了一个JsonExpressionParser来解析JSON并创建一个表达式树。 因为解决方案是谓词,所以表达式树是根据BinaryExpression实例构建的,该实例评估左表达式和右表达式。 该评估可能是一个逻辑门( ANDOR ),或比较( equalgreaterThan ),或一个方法调用。 对于In的情况,我们希望属性Category在列表中的多个项目之一中,我翻转脚本并使用Contains 。 从概念上讲,引用的JSON如下所示:

                       /-----------AND-----------\
                         |                         |
                      /-AND-\                      |
Category IN ['Clothing']   Amount eq 10.0        /-OR-\
                        TransactionType EQ 'income'  PaymentMode EQ 'Cash'

Notice that each node is binary. Let’s start parsing!

请注意,每个节点都是二进制的。 让我们开始解析!

交易介绍 (Introducing Transaction)

No, this isn’t System.Transaction. This is a custom class used in the example project. I didn’t spend much time on the vendor’s site, so I guessed at what the entity might look like based on the rules. I came up with this:

不,这不是System.Transaction 。 这是示例项目中使用的自定义类。 我没有在供应商的网站上花费很多时间,因此我根据规则猜测该实体的外观。 我想出了这个:

public class Transaction
{
  public int Id { get; set; }
  public string Category { get; set; }
  public string TransactionType { get; set; }
  public string PaymentMode { get; set; }
  public decimal Amount { get; set; }
}

I then added some additional methods to make it easy to generate random instances. You can see those in the code yourself.

然后,我添加了一些其他方法来简化生成随机实例的过程。 您可以自己在代码中看到它们。

参数表达式 (Parameter Expressions)

The main method returns a predicate function. Here’s the code to start:

main方法返回一个谓词函数。 这是开始的代码:

public Func<T, bool> ParsePredicateOf<T>(JsonDocument doc)
{
   var itemExpression = Expression.Parameter(typeof(T));
   var conditions = ParseTree<T>(doc.RootElement, itemExpression);
}

The first step is to create the predicate parameter. A predicate can be passed to a Where clause, and if we wrote it ourselves, it would look something like this:

第一步是创建谓词参数。 可以将谓词传递给Where子句,如果我们自己编写它,它将看起来像这样:

var query = ListOfThings.Where(t => t.Id > 2);

The t => is the first parameter and represents the type of an item in the list. Therefore, we create a parameter for that type. Then, we recursively traverse the JSON nodes to build the tree.

t =>是第一个参数,代表列表中项目的类型。 因此,我们为该类型创建一个参数。 然后,我们递归地遍历JSON节点以构建树。

逻辑表达式 (Logic Expressions)

The start of the parser looks like this:

解析器的开始看起来像这样:

private Expression ParseTree<T>(
    JsonElement condition,
    ParameterExpression parm)
    {
        Expression left = null;
        var gate = condition.GetProperty(nameof(condition)).GetString();

        JsonElement rules = condition.GetProperty(nameof(rules));

        Binder binder = gate == And ? (Binder)Expression.And : Expression.Or;

        Expression bind(Expression left, Expression right) =>
            left == null ? right : binder(left, right);

There’s a bit to digest. The gate variable is the condition, i.e., “and” or “or.” The rules statement gets a node that is the list of related rules. We are keeping track of the left and right side of the expression. The Binder signature is shorthand for a binary expression, and is defined here:

有一点需要消化。 gate变量是条件,即“和”或“或”。 Rules语句获取一个节点,该节点是相关规则的列表。 我们一直在跟踪表达式的左侧和右侧。 Binder签名是二进制表达式的简写,并在此处定义:

private delegate Expression Binder(Expression left, Expression right);

The binder variable simply sets up the top-level expression: either Expression.And or Expression.Or. Both take left and right expressions to evaluate.

binder变量仅设置顶级表达式: Expression.AndExpression.Or 。 两者都采用左右表达式进行评估。

The bind function is a little more interesting. As we traverse the tree, we need to build up the various nodes. If we haven’t created an expression yet (left is null) we start with the first expression created. If we have an existing expression, we use that expression to merge the two sides.

bind函数更加有趣。 遍历树时,我们需要构建各个节点。 如果尚未创建表达式( leftnull ),则从创建的第一个表达式开始。 如果我们有一个现有的表达式,则可以使用该表达式合并这两个方面。

Right now, left is null, and then we start enumerating the rules that are part of this condition:

现在, leftnull ,然后我们开始枚举属于此条件的规则:

foreach (var rule in rules.EnumerateArray())

属性表达式 (Property Expressions)

The first rule is an equality rule, so I’ll skip the condition part for now. Here’s what happens:

第一条规则是相等规则,因此我现在将跳过条件部分。 这是发生了什么:

string @operator = rule.GetProperty(nameof(@operator)).GetString();
string type = rule.GetProperty(nameof(type)).GetString();
string field = rule.GetProperty(nameof(field)).GetString();
JsonElement value = rule.GetProperty(nameof(value));
var property = Expression.Property(parm, field);

First, we get the operator (“in”), the type (“string”), the field (“Category”) and the value (an array with “Clothing” as the only element). Notice the call to Expression.Property. The LINQ for this rule would look like this:

首先,我们获得运算符(“ in ”),类型(“ string ”),字段(“ Category ”)和值(以“ Clothing ”作为唯一元素的数组)。 注意对Expression.Property的调用。 此规则的LINQ如下所示:

var filter = new List<string> { "Clothing" };
Transactions.Where(t => filter.Contains(t.Category));

The property is the t.Category part, so we create it based on the parent property (t) and the field name.

该属性是t.Category部分,因此我们根据父属性( t )和字段名称创建它。

常量和调用表达式 (Constant and Call Expressions)

Next, we need to build the call to Contains. To simplify things, I created a reference to the method here:

接下来,我们需要构建对Contains的调用。 为简化起见,我在这里创建了对该方法的引用:

private readonly MethodInfo MethodContains = typeof(Enumerable).GetMethods(
  BindingFlags.Static | BindingFlags.Public)
  .Single(m => m.Name == nameof(Enumerable.Contains)
      && m.GetParameters().Length == 2);

This grabs the method on Enumerable that takes two parameters: the enumerable to use and the value to check for. The next bit of logic looks like this:

这将获取Enumerable上的方法,该方法Enumerable两个参数:要使用的可枚举值和要检查的值。 接下来的逻辑如下所示:

if (@operator == In)
{
    var contains = MethodContains.MakeGenericMethod(typeof(string));
    object val = value.EnumerateArray().Select(e => e.GetString())
        .ToList();
    var right = Expression.Call(
        contains,
        Expression.Constant(val),
        property);
    left = bind(left, right);
}

First, we use the Enumerable.Contains template to create an Enumerable<string> because that is the type we are looking for. Next, we grab the list of values and turn it into a List<string>. Finally, we build our call, passing it:

首先,我们使用Enumerable.Contains模板创建一个Enumerable<string>因为这是我们要查找的类型。 接下来,我们获取值列表并将其转换为List<string> 。 最后,我们传递呼叫:

  • The method to call (contains)

    调用方法( contains )

  • The value that is the parameter to check for (the list with “Clothing”, or Expression.Constant(val))

    作为要检查的参数的值(带有“ Clothing ”或Expression.Constant(val) )

  • The property to check it against (t.Category).

    要对照( t.Category )进行检查的属性。

Our expression tree is already fairly deep, with parameters, properties, calls, and constants. Remember that left is still null, so the call to bind simply sets left to the call expression we just created. So far, we look like this:

我们的表达式树已经相当深,带有参数,属性,调用和常量。 请记住, left仍然为null ,因此对绑定的调用只是将left设置为我们刚刚创建的调用表达式。 到目前为止,我们看起来像这样:

Transactions.Where(t => (new List<string> { "Clothing" }).Contains(t.Category));

Iterating back around, the next rule is a nested condition. We hit this code:

反复循环,下一个规则是嵌套条件。 我们点击以下代码:

if (rule.TryGetProperty(nameof(condition), out JsonElement check))
{
    var right = ParseTree<T>(rule, parm);
    left = bind(left, right);
    continue;
}

Currently, left is assigned to the “in” expression. right will be assigned as the result of parsing the new condition. I happen to know it’s an OR condition. Right now, our binder is set to Expression.And so when the function returns, the bind call leaves us with:

当前, left被分配给“ in ”表达式。 解析新条件的结果将分配right 。 我碰巧知道这是一个OR条件。 现在,我们的资料夹设置为Expression.And因此当函数返回时, bind调用使我们拥有:

Transactions.Where(t => (new List<string> { "Clothing" }).Contains(t.Category) && <something>);

Let’s look at “something”.

让我们看一下“某物”。

比较表达式 (Comparison Expressions)

First, the recursive call determines a new condition exists, this time a logical OR. The binder is set to Expression.Or and the rules begin evaluation. The first rule is for TransactionType. It is set as boolean but from what I infer, it means the user in the interface can check to select a value or toggle to another value. Therefore, I implemented it as a simple string comparison. Here’s the code that builds the comparison:

首先,递归调用确定存在新条件,这次是逻辑OR 。 活页夹设置为Expression.Or ,规则开始评估。 第一条规则是针对TransactionType 。 它设置为boolean但是根据我的推断,这意味着界面中的用户可以检查选择一个值或切换到另一个值。 因此,我将其实现为简单的字符串比较。 这是构建比较的代码:

object val = (type == StringStr || type == BooleanStr) ?
    (object)value.GetString() : value.GetDecimal();
var toCompare = Expression.Constant(val);
var right = Expression.Equal(property, toCompare);
left = bind(left, right);

The value is deconstructed as a string or decimal (a later rule will use the decimal format). The value is then turned into a constant, then the comparison is created. Notice it is passed the property. The variable right now looks like this:

该值被解构为字符串或十进制(以后的规则将使用十进制格式)。 然后将值转换为常量,然后创建比较。 注意它是传递给属性的。 变量right现在看起来是这样的:

Transactions.Where(t => t.TransactionType == "income");

In this nested loop, left is still empty. The parser evaluates the next rule, which is the payment mode. The bind function turns it into this “or” statement:

在此嵌套循环中, left仍然为空。 解析器评估下一条规则,即付款方式。 bind函数将其转换为以下“ or ”语句:

Transactions.Where(t => t.TransactionType == "income" || t.PaymentMode == "Cash");

The rest should be self-explanatory. A nice feature of expressions is that they overload ToString() to generate a representation. This is what our expression looks like (I took the liberty of formatting for easier viewing):

其余的应该是不言自明的。 表达式的一个不错的功能是它们重载ToString()以生成表示形式。 这就是我们的表情(我为方便查看而采取了格式化的自由):

(
  (value(System.Collections.Generic.List`1[System.String]).Contains(Param_0.Category)
      And (
          (Param_0.TransactionType == "income")
          Or
          (Param_0.PaymentMode == "Cash"))
      )
  And
  (Param_0.Amount == 10)
)

It looks good… but we’re not done yet!

看起来不错…但是我们还没有完成!

Lambda表达式和编译 (Lambda Expressions and Compilation)

The expression tree represents an idea. It needs to be turned into something material. In case the expression can be simplified, I reduce it. Next, I create a lambda expression. This defines the shape of the parsed expression, which will be a predicate (Func<T,bool>). Finally, I return the compiled delegate.

表达式树表示一个想法。 它需要变成某种物质。 如果可以简化表达式,请减少它。 接下来,我创建一个lambda表达式。 这定义了解析表达式的形状,它将是一个谓词( Func<T,bool> )。 最后,我返回编译后的委托。

var conditions = ParseTree<T>(doc.RootElement, itemExpression);
if (conditions.CanReduce)
{
    conditions = conditions.ReduceAndCheck();
}
var query = Expression.Lambda<Func<T, bool>>(conditions, itemExpression);
return query.Compile();

To “check my math” I generate 1000 transactions (weighted to include several that should match). I then apply the filter and iterate the results so I can manually test that the conditions were met.

为了“检查我的数学”,我生成了1000个事务(加权后包括应该匹配的几个事务)。 然后,我应用过滤器并迭代结果,以便可以手动测试是否满足条件。

var predicate = jsonExpressionParser
                .ParsePredicateOf<Transaction>(jsonDocument);
var transactionList = Transaction.GetList(1000);
var filteredTransactions = transactionList.Where(predicate).ToList();
filteredTransactions.ForEach(Console.WriteLine);

As you can see, the results all check out (I average about 70 “hits” each run.)

如您所见,结果全部签出(我平均每次运行约70次“匹配”。)

A screenshot of various entities

超越内存到数据库 (Beyond Memory to Database)

The generated delegate isn’t just for objects. We can also use it for database access.

生成的委托不仅用于对象。 我们也可以将其用于数据库访问。

Set the startup project to DatabaseTest for the rest of this post. If you run it from the command line, be sure that databaseRules.json is in your current directory.

在本文的其余部分,将启动项目设置为DatabaseTest 。 如果从命令行运行它,请确保databaseRules.json在当前目录中。

First, I refactored the code. Remember how expressions require a data source? In the previous example, we compile the expression and end up with a delegate that works on objects. To use a different data source, we need to pass the expression before it is compiled. That allows the data source to compile it. If we pass the compiled data source, the database provider will be forced to fetch all rows from the database, then parse the returned list. We want the database to do the work. I moved the bulk of code into a method named ParseExpressionOf<T> that returns the lambda. I refactored the original method to this:

首先,我重构了代码。 还记得表达式如何需要数据源吗? 在前面的示例中,我们编译表达式并最终得到对对象起作用的委托。 要使用其他数据源,我们需要在编译表达式之前传递表达式。 这样就可以对数据源进行编译。 如果我们传递已编译的数据源,则将强制数据库提供程序从数据库中获取所有行,然后解析返回的列表。 我们希望数据库完成这项工作。 我将大量代码移到了一个名为ParseExpressionOf<T>的方法中,该方法返回lambda。 我将原始方法重构为:

public Func<T, bool> ParsePredicateOf<T>(JsonDocument doc)
{
    var query = ParseExpressionOf<T>(doc);
    return query.Compile();
}

The ExpressionGenerator program uses the compiled query. The DatabaseTest uses the raw lambda expression. It applies it to a local SQLite database to demonstrate how the expression is parsed by EF Core. After creating and inserting 1000 transactions into the database, the code retrieves a count:

ExpressionGenerator程序使用编译后的查询。 DatabaseTest使用原始的lambda表达式。 它将其应用于本地SQLite数据库,以演示EF Core如何解析该表达式。 在将1000个事务创建并插入到数据库中之后,代码将检索一个count

var count = await context.DbTransactions.CountAsync();
Console.WriteLine($"Verified insert count: {count}.");

This results in the following SQL:

这将导致以下SQL:

SELECT COUNT(*)
FROM "DbTransactions" AS "d"

If you’re wondering why there are two contexts, it is due to logging. The first context inserts 1000 records and if logging is turned on, it will run very slow as the inserts are written to the console. The second context switches on logging so you can see the evaluated statement.

如果您想知道为什么有两个上下文,那是由于记录。 第一个上下文插入1000条记录,如果打开了日志记录,则在将插入内容写入控制台时它将运行非常慢。 第二个上下文打开日志记录,因此您可以查看评估后的语句。

The predicate is parsed (this time from a new set of rules in databaseRules.json) and passed to the Entity Framework Core provider.

对该谓词进行解析(这次是从databaseRules.json中的一组新规则),然后传递给Entity Framework Core提供程序。

var parser = new JsonExpressionParser();
var predicate = parser.ParseExpressionOf<Transaction>(
    JsonDocument.Parse(
        await File.ReadAllTextAsync("databaseRules.json")));
  var query = context.DbTransactions.Where(predicate)
      .OrderBy(t => t.Id);
  var results = await query.ToListAsync();

With Entity Framework Core logging turned on, we are able to retrieve the SQL and see the items are fetched in one pass and evaluated in the database engine. Notice the PaymentMode is checked for “Credit” instead of “Cash”.

启用Entity Framework Core日志记录后,我们能够检索SQL并一目了然地获取项目并在数据库引擎中进行评估。 请注意,在PaymentMode中检查的是“ Credit ”而不是“ Cash ”。

SELECT "d"."Id", "d"."Amount", "d"."Category", "d"."PaymentMode", "d"."TransactionType"
FROM "DbTransactions" AS "d"
WHERE ("d"."Category" IN ('Clothing') &
        ((("d"."TransactionType" = 'income') AND "d"."TransactionType" IS NOT NULL) |
          (("d"."PaymentMode" = 'Credit') AND "d"."PaymentMode" IS NOT NULL))) &
      ("d"."Amount" = '10.0')
ORDER BY "d"."Id"

The example app also prints one of the selected entities to spot-check.

该示例应用程序还将打印所选实体之一以进行抽查。

结论 (Conclusion)

LINQ expressions are a very powerful tool to filter and transform data. I hope this example helped demystify how expression trees are built. Of course, parsing the expression tree feels a little like magic. How does Entity Framework Core walk the expression tree to produce meaningful SQL? I’m exploring that myself and with a little help from my friend, ExpressionVisitor. More posts to come on this!

LINQ表达式是过滤和转换数据的非常强大的工具。 我希望该示例有助于揭露表达式树的构建方式。 当然,解析表达式树感觉有点像魔术。 Entity Framework Core如何遍历表达式树以产生有意义SQL? 我正在自己探索这个问题,并在我的朋友ExpressionVisitor的帮助下进行了探索。 更多的帖子来了!

Regards,

问候,

Jeremy Likness

翻译自: https://www.codeproject.com/Articles/5270252/Dynamically-Build-LINQ-Expressions

linq 动态表达式

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值