【问题标题】:How to build a Func lambda expression dynamically in C#如何在 C# 中动态构建 Func lambda 表达式
【发布时间】:2015-12-03 05:45:00
【问题描述】:

我有一个 ElasticSearch 数据库,我想在其中执行聚合。 我正在使用 NEST 和 lambda 表达式来创建查询。

但是,我需要同时对同一个文档(channel1 和 channel2)的多个字段进行聚合。 目前我有 2 个频道,所以我的查询可以正常工作。

    var res = elasticClient.Search<DataRecord>(s => s
        .Index(ElasticIndexName)
        .Aggregations(a => a
            .DateHistogram("mydoc", h => h
                .Aggregations(ag => ag.Average("avg1", b => b.Field("channel1")).Average("avg2", b => b.Field("channel2")))
            )
        )
    );

问题是没有。频道的数量可能不同,可能是三个或四个或其他什么,所以我想在下面放上我的 Func

ag => ag.Average("avg1", b => b.Field("channel1")).Average("avg2", b => b.Field("channel2"))

要动态创建(例如您在 SQL 查询中所做的),因为没有。只有在运行时才知道通道数。

例如 如果我有四个频道,查询应该是这样的:

  var res = elasticClient.Search<DataRecord>(s => s
        .Index(ElasticIndexName)
        .Aggregations(a => a
            .DateHistogram("mydoc", h => h
                .Aggregations(ag => ag.Average("avg1", b => b.Field("channel1")).Average("avg2", b => b.Field("channel2")).Average("avg3", b => b.Field("channel3")).Average("avg4", b => b.Field("channel4")))
            )
        )
    );

所以基本上我需要某种 for 循环,我可以在其中动态地构建 lambda 表达式

【问题讨论】:

    标签: c# elasticsearch lambda nest


    【解决方案1】:

    您可以编写一个方法,该方法采用直方图名称和字段名称集合并返回 Func&lt;AggregationDescriptor&lt;DataRecord&gt;, AggregationDescriptor&lt;DataRecord&gt;&gt;

    public static Func<AggregationDescriptor<DataRecord>, AggregationDescriptor<DataRecord>> DateHistogramOfAveragesForFields(
        string histogramName, 
        IEnumerable<string> fields)
    {
        return aggs => aggs
            .DateHistogram(histogramName, h => h
                .Aggregations(d => 
                    fields.Select((field, index) => new { Field = field, Name = "avg" + (index + 1) })
                          .Aggregate(d, (descriptor, field) => descriptor.Average(field.Name, b => b.Field(field.Field)))));
    }
    

    它看起来有点长,但本质上,我们构建了一个期望接收AggregationDescriptor&lt;DataRecord&gt; 并在其上调用.DateHistogram() 的方法,将直方图名称传递给它并生成字段名称的集合,然后 description 字段集合中的标签传递给DateHistogramAggregationDescriptor&lt;DataRecord&gt;

    使用很简单

    void Main()
    {
        var settings = new ConnectionSettings(new Uri("http://localhost:9200"));
    
        var client = new ElasticClient(settings);
    
        var results = client.Search<DataRecord>(s => s
           .Index("index")
           .Aggregations(DateHistogramOfAveragesForFields("mydoc", new[] { "channel1", "channel2", "channel3", "channel4" })
           )
       );
    
        Console.WriteLine(string.Format("{0} {1}", results.RequestInformation.RequestMethod, results.RequestInformation.RequestUrl));
        Console.WriteLine(Encoding.UTF8.GetString(results.RequestInformation.Request));
    }
    
    public class DataRecord { }
    

    输出以下搜索查询

    POST http://localhost:9200/index/datarecord/_search
    {
      "aggs": {
        "mydoc": {
          "date_histogram": {
            "format": "date_optional_time"
          },
          "aggs": {
            "avg1": {
              "avg": {
                "field": "channel1"
              }
            },
            "avg2": {
              "avg": {
                "field": "channel2"
              }
            },
            "avg3": {
              "avg": {
                "field": "channel3"
              }
            },
            "avg4": {
              "avg": {
                "field": "channel4"
              }
            }
          }
        }
      }
    }
    

    无需使用表达式 API 构建表达式树 :)

    【讨论】:

      【解决方案2】:

      这是在 Visual Studio 之外编写的,所以我不能保证它是 100% 正确的,但类似以下的内容演示了如何构造表达式,以及编译它们以供直接调用:

      // Test whether a string is a certain length
      
      public Func<string,bool> IsOfCorrectLength(int lengthToTest)
      {
          var param = Expression.Parameter(typeof(string));
          var test = Expression.Equal(Expression.Property(param, "Length"), Expression.Constant(lengthToTest));
          return Expression.Lambda<string,bool>(test,param).Compile();
      }
      
      public void DoSomething()
      {
          var is5CharsLong = IsOfCorrectLength(5);
      
          var result = is5CharsLong("Here's a string that would fail");
      }
      

      对于只需要表达式的情况,您可以直接传递表达式;请注意,某些表达式使用者可能不支持每种表达式类型 - 特别是如果必须将表达式转换为 SQL 之类的东西。

      MSDN 有一些非常有趣的文章,介绍了如何在运行时使用表达式来做一些漂亮的事情。

      【讨论】:

        【解决方案3】:

        尝试使用 System.Linq.Expressions 命名空间。它包含各种表达式类型,您可以使用它们来构建表达式树,然后您可以动态编译和运行。

        https://msdn.microsoft.com/en-us/library/system.linq.expressions%28v=vs.110%29.aspx

        如果需要,您可以使用 LoopExpression。或者,如果您知道创建表达式树时所需的数量,您可以使用普通的 C# 代码循环添加重复的功能,从而基本上展开循环。如果您愿意(或在 Lambda 方法的通用参数中指定),可以将编译后的表达式转换为所需的 Func 类型,以使用强类型方法。您可以在我发布的链接中的示例中看到这一点。

        编辑:根据您的要求,如果您不想使用 Expressions 命名空间,则可能不需要使用,尽管您仍然可以。也许你可能想写一个这样的扩展方法:

        // Replace the return type "object" with the type you expect returned from the Average call
        // Replace the "object" in 'this object @this' with the type of 'ag' in the lambda 'h.Aggregations(ag => ag'
        public static object AverageChannels(this object @this, int channelCount) // alternatively, obtain the channel count from the input variable if this can be done
        {
            if (channelCount < 1)
            {
              // do something
            }
        
            var result = @this.Average("avg1", b => b.Field("channel1");
            for (int i = 2; i < channelCount + 1; i++)
            {
                var avgText = "avg" + i.ToString();
                var channelText = "channel" + i.ToString();
                result = result .Average(avgText, b => b.Field(channelText))
            }
        
            return result;
        }
        

        如果计数大于 0,这将调用 .Average 至少一次,然后对于每个附加通道。 你可以这样使用它:

        .DateHistogram("mydoc", h => h.Aggregations(ag => ag.AverageChannels(4))
        

        如果可以从“ag”对象中检索通道计数,则可以根据需要完全排除 channelCount 参数。

        【讨论】:

        • 非常感谢您的评论,您能否提供一些与我上面的案例类似的示例代码?
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多