如何在c#中动态构建一个Func lambda表达式
本文关键字:一个 Func 表达式 lambda 构建 动态 | 更新日期: 2023-09-27 18:11:30
我有一个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")))
)
)
);
问题是没有。通道数可以是不同的,可能是3个或4个之类的所以我想把Func放在
下面ag => ag.Average("avg1", b => b.Field("channel1")).Average("avg2", b => b.Field("channel2"))
必须动态创建(就像在SQL查询中所做的那样),因为no。只有在运行时才知道。
。如果我有四个通道,查询应该是这样的:
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表达式
您可以编写一个方法,该方法接受直方图和字段名称集合的名称,并返回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()
,将直方图名称传递给它,并从字段集合中生成字段名称和描述标签的集合,以传递给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"
}
}
}
}
}
}
不需要用表达式API构建表达式树:)
这是在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有一些非常有趣的文章,关于如何在运行时使用表达式来做一些非常漂亮的事情。
尝试使用System.Linq.Expressions命名空间。它包含各种表达式类型,您可以使用它们来构造表达式树,然后可以编译和动态运行。
https://msdn.microsoft.com/en-us/library/system.linq.expressions%28v=vs.110%29.aspx如果需要的话,可以使用LoopExpression。另外,如果在创建表达式树时知道所需的数目,则可以使用普通的c#代码添加重复功能来展开循环。如果您愿意,可以将编译后的表达式强制转换为所需的Func类型(或在Lambda方法的泛型参数中指定这一点),以使用强类型方法。你可以在我发布的链接中看到这个例子。
编辑:根据您的需求,如果您不希望使用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参数。