【问题标题】:How to use a variable when making a lambda expression using Expression.Lambda<Func<>>()使用 Expression.Lambda<Func<>>() 制作 lambda 表达式时如何使用变量
【发布时间】:2021-02-23 12:44:36
【问题描述】:

我有一个如下的 linq 查询:

var query = _appDbContext.Persons
                .Where(p => p.Id == 123)
                .Select(lambda)
                .ToList();

我使用 lambda 是因为我的 select 语句中有一个变量。拉姆达:

var wantedData = "Adress";

var x = Expression.Parameter(typeof(Person), "x");
var body = Expression.PropertyOrField(x, wantedData);
var lambda = Expression.Lambda<Func<Person, Adress>>(body, x);

查询将返回 id 为 123 的人的地址。假设我希望接收此人的所有订阅,而不是地址。由于 lambda 语句中的“地址”,仅设置 wantedData = "Subscription" 将不起作用。所以我正在寻找一种方法来使用变量来更改 lambda 语句中的“地址”。

我尝试了以下,显然不起作用。

var lambda = Expression.Lambda<Func<Person, wantedData>>(body, x);

有没有办法做到这一点?

【问题讨论】:

  • 你为什么不只是这样查询? var addresses= _appDbContext.Persons.Where(p =&gt; p.Id == 123).Select(p =&gt; p.Address).ToList();
  • @MarianSimonca 因为我希望我的查询基于变量获取地址或订阅。对于像你这样的查询,我必须为此构建两个查询。
  • 但是您可以在具有 Expression 参数的方法中使用此查询。调用该函数时,您基本上传递了您希望通过该查询获得的属性
  • @MarianSimonca 我是使用 linq 查询的新手,所以我可能是错的,但这不是我已经在为 var x 赋值吗?
  • 是的,有点类似,但是有点复杂,我想举个例子让你试试

标签: c# linq lambda


【解决方案1】:

好的,我设法创建了一个以表达式为参数的通用查询方法。

编辑:这将接收一个字符串值,并尝试将其与您的 Person 类上的现有属性匹配。

private IEnumerable<TResult> GetPersonData<TResult>(Expression<Func<Person, TResult>> selectExpression)
{
    return _dbContext.Persons
            // .Where(filter)
        .Select(selectExpression)
        .ToList();
}

public IEnumerable<object> GetData(string dataPropertyName)
{
    switch(dataPropertyName) {
         case nameof(Person.Address): return GetPersonData(p => p.Address);
         case nameof(Person.Subscription): return GetPersonData(p => p.Subscription);
         // other cases
         default: throw new InvalidArgumentException("Invalid property name");
    }
}

请注意,此代码只是现场编写的示例,可能无法直接使用复制粘贴

【讨论】:

  • 这行得通,但并不能真正解决问题。我仍然需要为每个调用创建一个函数。我想将我想要接收的数据作为这样的参数提供:GetPersonData("Adress") 这可能吗?
  • 您可以使用 Person 的任何导航属性调用此方法。我创建了两个方法只是为了举例说明如何在不同的场景中调用GetPersonData
  • 我明白了,但是不能这样:var wantedData = "Adress",然后像这样调用函数:GetPersonData(p =&gt; p.wantedData)
  • 如您所见,GetPersonData(p =&gt; p.Address) 正在指定您想要接收的内容,并且此版本更安全,因为您没有将任何魔术字符串作为参数传递,并且如果您更改了 Address 属性的名称也可以使用
  • 好吧,您不再需要wantedData 变量,因为您不再需要创建lambda 变量
【解决方案2】:

我制作了类似于OrderBy 的内容,并根据您的选择对其进行了一些调整。这仅在您知道返回类型时才有效。 您可以使用以下代码创建扩展方法:

public static IQueryable<TResult> Select<T,TResult>(this IQueryable<T> source, string propertyName)
{
    var prop = typeof(T).GetProperties()
        .FirstOrDefault(x => x.Name.ToUpper() == propertyName.ToUpper());
    ParameterExpression pe = Expression.Parameter(typeof(T), "x");
    MemberExpression body = Expression.Property(pe, prop.Name);

    LambdaExpression lambda = Expression.Lambda(body, new ParameterExpression[] { pe });

    MethodCallExpression selectCallExpression = Expression.Call(
        typeof(Queryable),
        "Select",
        new Type[] { source.ElementType, prop.PropertyType },
        source.Expression,
        lambda);
    return source.Provider.CreateQuery<TResult>(selectCallExpression);
}

这样,你可以像这样使用它:

var query = _appDbContext.Persons
                .Where(p => p.Id == 123)
                .Select<Person,string>(wantedData)
                .ToList();

【讨论】:

    猜你喜欢
    • 2023-04-10
    • 1970-01-01
    • 1970-01-01
    • 2013-09-10
    • 2011-09-03
    • 1970-01-01
    • 2010-10-20
    • 2012-01-21
    相关资源
    最近更新 更多