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

时间:2015-09-07 16:06:47

标签: c# elasticsearch lambda nest

我有一个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查询中所做的那样)因为没有。通道只在运行时才知道。

E.g。 如果我有四个频道,则查询应该是:

  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表达式

3 个答案:

答案 0 :(得分:0)

尝试使用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一次,然后对每个附加通道调用.Average。 你可以这样使用它:

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

如果频道数可以从&#39; ag&#39;对象,那么你可以完全排除channelCount参数。

答案 1 :(得分:0)

这是在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有一些非常有趣的文章,介绍如何在运行时使用表达式来做一些非常漂亮的事情。

答案 2 :(得分:0)

您可以编写一个方法,为直方图和字段名称集合命名并返回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)))));
}

看起来有点长,但实际上,我们构建了一个方法,希望在其上接收AggregationDescriptor<DataRecord>并调用.DateHistogram(),将直方图名称传递给它并生成一个集合字段集合中的字段名称和 decription 标签,以传递给DateHistogramAggregationDescriptor<DataRecord>

使用只是

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

无需使用Expression API构建Expression树:)