简体   繁体   English

如何在C#中动态构建Func Lambda表达式

[英]How to build a Func lambda expression dynamically in C#

I have an ElasticSearch database in which I want to perform an aggregation. 我有一个要在其中执行聚合的ElasticSearch数据库。 I'm using NEST and lambda expression to create the query. 我正在使用NEST和lambda表达式来创建查询。

However, I need to perform the aggregation on multiple fields of the same document (channel1 and channel2) at the same time. 但是,我需要在同一文档(channel1和channel2)的多个字段上同时执行聚合。 Currently I have 2 channels so my query works fine over them. 目前,我有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")))
            )
        )
    );

The problem is that the no. 问题是没有。 of channels could be different, maybe three or four or whatever so I'd like to have my Func below 的频道可能有所不同,也许是三个或四个,等等,所以我想将Func放在下面

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

to be dynamically created (like you do on a SQL query for example) because the no. 动态创建(例如您对SQL查询进行的创建),因为没有。 of channels is known only at runtime. 的通道数仅在运行时已知。

Eg If I have four channels the query should be like: 例如,如果我有四个渠道,查询应该是:

  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")))
            )
        )
    );

So basically I need some kind of for loop in which I can build the lambda expression dinamically 所以基本上我需要某种for循环,可以在其中动态创建lambda表达式

Try using the System.Linq.Expressions namespace. 尝试使用System.Linq.Expressions命名空间。 It contains various expression types which you can use to construct an expression tree, which you can then compile and run dynamically. 它包含各种表达式类型,可用于构造表达式树,然后可以对其进行编译和动态运行。

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

You can use the LoopExpression if you need it. 如果需要,可以使用LoopExpression。 Alternatively, if you know the number needed at the time of expression tree creation, you could loop using normal C# code to add in the repeated functionality to basically unroll the loop. 另外,如果您知道在创建表达式树时所需的数目,则可以使用普通的C#代码进行循环,以添加重复的功能以基本上展开循环。 You can cast the compiled expression to the desired Func type if you wish (or specify this in the generic arguments of the Lambda method), to use a strongly-typed approach. 如果愿意(可以在Lambda方法的通用参数中指定),可以将编译的表达式强制转换为所需的Func类型,以使用强类型方法。 You can see this in the example in the link I posted. 您可以在我发布的链接的示例中看到这一点。

EDIT: Based on your requirements, you probably do not need to use the Expressions namespace if you do not want to, though you still could. 编辑:根据您的要求,尽管您仍然可以使用,但如果您不想使用Expressions命名空间,则可能不需要。 Perhaps you may want to write an extension method like this: 也许您可能想编写如下扩展方法:

// 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;
}

This will call .Average at least once if the count is greater than 0, and then for each additional channel. 如果计数大于0,则将调用.Average至少一次,然后再对每个附加通道进行一次。 You would use it like this: 您可以这样使用它:

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

If the channel count is retrievable from the 'ag' object, then you can exclude the channelCount parameter entirely if you want. 如果可以从“ ag”对象中检索通道计数,则可以根据需要完全排除channelCount参数。

This is written outside Visual Studio, so I can't guarantee it's 100% correct, but something like the following demonstrates how you can construct expressions, as well as compile them for direct invocation: 这是在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");
}

For situations where you just need the expression, you could just pass around the expression; 对于只需要表达式的情况,可以只传递表达式; just be aware that some consumers of expressions may not support every expression type - particularly if the expression has to be transpiled into something like SQL. 请注意,某些表达式的使用者可能不支持每种表达式类型-尤其是在必须将表达式转换为类似SQL的表达式时。

MSDN has some very interesting articles on how to use expressions at runtime to do some pretty nifty things. MSDN上有一些非常有趣的文章,介绍如何在运行时使用表达式来做一些非常漂亮的事情。

You can write a method that takes a name for the histogram and collection of field names and returns a Func<AggregationDescriptor<DataRecord>, AggregationDescriptor<DataRecord>> 您可以编写一个使用直方图名称和字段名称集合并返回Func<AggregationDescriptor<DataRecord>, AggregationDescriptor<DataRecord>>

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)))));
}

It looks a bit long in the tooth, but essentially, we build up a method that expects to receive a AggregationDescriptor<DataRecord> and call .DateHistogram() on it, passing it the histogram name and generate a collection of field names and decription labels from the field collection to pass to the DateHistogramAggregationDescriptor<DataRecord> . 长它看起来有点的牙齿,但本质上,我们建立了一个期望接收的方法AggregationDescriptor<DataRecord>和调用.DateHistogram()就可以了,它传递的直方图名称和产生字段名和能解密标签的集合从字段集合传递到DateHistogramAggregationDescriptor<DataRecord>

To use is simply 使用简单

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 { }

which outputs the following search query 输出以下搜索查询

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"
          }
        }
      }
    }
  }
}

No need to build up an Expression tree with the Expression API :) 无需使用Expression API构建Expression树:)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM