gpt4 book ai didi

c# - LINQ 表达式的运行时创建

转载 作者:太空狗 更新时间:2023-10-29 21:50:43 24 4
gpt4 key购买 nike

假设我有这个表达式:

int setsize = 20;
Expression<Func<Foo, bool>> predicate = x => x.Seed % setsize == 1
|| x.Seed % setsize == 4;

这基本上将一组元素“划分”为 20 个分区,并从每个集合中检索第一个和第四个元素。

此表达式被传递给 MongoDB,它的 driver 完全能够转换为 MongoDB“查询”。但是,谓词也可以用于对象列表 (LINQ2Objects) 等。我希望这个表达式可以重用 (DRY)。但是,我希望能够传入 IEnumerable<int> 以指定要检索的项目(因此 1 和 4 不会“硬编码”到其中):

public Expression<Func<Foo, bool>> GetPredicate(IEnumerable<int> items) {
//Build expression here and return it
}

对于 LINQPad 使用此代码:

int setsize = 20;
Expression<Func<Foo, bool>> predicate = x => x.Seed % setsize == 1 || x.Seed % setsize == 4;
predicate.Dump();

}

class Foo
{
public int Seed { get; set; }

我可以检查表达式:

Expression

现在,我希望能够构建此表达式的精确复制,但要传递的整数数量可变(因此我可以传递 [1, 5, 9, 11][8][1, 2, 3, 4, 5, 6, ..., 16] 而不是 1 和 4)。

我曾尝试使用 BinaryExpressions 等,但无法正确构造此消息。主要问题是我的大部分 attempt 在将谓词传递给 MongoDB 时都会失败。 “硬编码”版本工作正常但不知何故,我所有传递动态表达式的尝试都无法通过 C# 驱动程序转换为 MongoDB 查询:

{
"$or" : [{
"Seed" : { "$mod" : [20, 1] }
}, {
"Seed" : { "$mod" : [20, 4] }
}]
}

基本上,我想在运行时动态构建表达式,使其完全复制编译器为“硬编码”版本生成的内容。

我们将不胜感激。

编辑

As requested in the comments(和 posted on pastebin),我在下面的尝试之一。我将其张贴在问题中以供将来引用,如果 pastebin 将其删除或停止其服务或...

using MongoRepository;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;

class Program
{
static void Main(string[] args)
{
MongoRepository<Foo> repo = new MongoRepository<Foo>();
var reporesult = repo.All().Where(IsInSet(new[] { 1, 4 }, 20)).ToArray();
}

private static Expression<Func<Foo, bool>> IsInSet(IEnumerable<int> seeds, int setsize)
{
if (seeds == null)
throw new ArgumentNullException("s");

if (!seeds.Any())
throw new ArgumentException("No sets specified");

return seeds.Select<int, Expression<Func<Foo, bool>>>(seed => x => x.Seed % setsize == seed).JoinByOr();
}
}

public class Foo : Entity
{
public int Seed { get; set; }
}

public static class Extensions
{
public static Expression<Func<T, bool>> JoinByOr<T>(this IEnumerable<Expression<Func<T, bool>>> filters)
{
var firstFilter = filters.First();
var body = firstFilter.Body;
var param = firstFilter.Parameters.ToArray();
foreach (var nextFilter in filters.Skip(1))
{
var nextBody = Expression.Invoke(nextFilter, param);
body = Expression.Or(body, nextBody);
}
return Expression.Lambda<Func<T, bool>>(body, param);
}
}

结果为:Unsupported where clause: <InvocationExpression>

最佳答案

试试这个:

public Expression<Func<Foo, bool>> GetExpression<T>(
int setSize, int[] elements,
Expression<Func<Foo, T>> property)
{
var seedProperty = GetPropertyInfo(property);
var parameter = Expression.Parameter(typeof(Foo));
Expression body = null;

foreach(var element in elements)
{
var condition = GetCondition(parameter, seedProperty, setSize, element);
if(body == null)
body = condition;
else
body = Expression.OrElse(body, condition);
}

if(body == null)
body = Expression.Constant(false);

return Expression.Lambda<Func<Foo, bool>>(body, parameter);
}

public Expression GetCondition(
ParameterExpression parameter, PropertyInfo seedProperty,
int setSize, int element)
{
return Expression.Equal(
Expression.Modulo(Expression.Property(parameter, seedProperty),
Expression.Constant(setSize)),
Expression.Constant(element));
}

public static PropertyInfo GetPropertyInfo(LambdaExpression propertyExpression)
{
if (propertyExpression == null)
throw new ArgumentNullException("propertyExpression");

var body = propertyExpression.Body as MemberExpression;
if (body == null)
{
throw new ArgumentException(
string.Format(
"'propertyExpression' should be a member expression, "
+ "but it is a {0}", propertyExpression.Body.GetType()));
}

var propertyInfo = body.Member as PropertyInfo;
if (propertyInfo == null)
{
throw new ArgumentException(
string.Format(
"The member used in the expression should be a property, "
+ "but it is a {0}", body.Member.GetType()));
}

return propertyInfo;
}

你可以这样调用它:

GetExpression(setSize, elements, x => x.Seed);

如果你想让它在 Foo 中也通用,你需要像这样改变它:

public static Expression<Func<TEntity, bool>> GetExpression<TEntity, TProperty>(
int setSize, int[] elements,
Expression<Func<TEntity, TProperty>> property)
{
var propertyInfo = GetPropertyInfo(property);
var parameter = Expression.Parameter(typeof(TEntity));
Expression body = null;

foreach(var element in elements)
{
var condition = GetCondition(parameter, propertyInfo , setSize, element);
if(body == null)
body = condition;
else
body = Expression.OrElse(body, condition);
}

if(body == null)
body = Expression.Constant(false);

return Expression.Lambda<Func<TEntity, bool>>(body, parameter);
}

现在,调用看起来像这样:

GetExpression(setSize, elements, (Foo x) => x.Seed);

在这种情况下,明确指定 x 的类型很重要,否则类型推断将不起作用,您必须同时指定 Foo 和类型属性作为 GetExpression 的通用参数。

关于c# - LINQ 表达式的运行时创建,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16583565/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com