【问题标题】:C# Linq where clause according to property nameC# Linq where 子句根据属性名
【发布时间】:2011-12-05 07:39:42
【问题描述】:

假设我有以下课程:

public class Person { 

    public string FirstName { get; set; }
    public string SurName { get; set; }
    public int Age { get; set; }
    public string Gender { get; set; }

}

另外,我有以下方法,我正在通过存储库获取个人数据。

public IEnumerable<Person> getPeople(string searchField, string searchTerm) { 

    //_repo.GetAll() returns IEnumerable<Person>
    var model = _repo.GetAll(); 

    //Need the logic here for filtering

    return model;
}

如您所见,我得到了该方法的两个参数:searchFieldsearchTerm

searchField 用于字段名称,其值将用于过滤。 searchTerm 是用于与检索到的值进行比较的值(对不起,如果我在这里不清楚,但这是我能想到的最多的值)

我通常会做如下:

public IEnumerable<Person> getPeople(string searchField, string searchTerm) { 

    //_repo.GetAll() returns IEnumerable<Person>
    var model = _repo.GetAll(); 

    switch(searchField) { 

        case "FirstName":
            model = model.Where(x => x.FirstName == searchTerm);
            break;

        case "SurName":
            model = model.Where(x => x.SurName == searchTerm);
            break;

        //Keeps going
    }

    return model;

}

这会很好用。但是,如果我对我的类进行更改,那么如果我在这个类中添加新属性,这段代码就会发生改变或缺少某些功能。

我正在寻找如下内容:

注意:

下面这段代码完全属于我的想象,没有这样的 东西是存在的。

model = model.Where(x => x.GetPropertyByName(searchField) == searchTerm);

如果这是不可能的,我是不是飞得太高了,如果已经有内置的方法,我是完全白痴吗?

【问题讨论】:

    标签: c# asp.net asp.net-mvc linq entity-framework


    【解决方案1】:
    【解决方案2】:

    我使用这种扩展方法来实现你想要的。

    public static IQueryable<TEntity> Where<TEntity>(this IQueryable<TEntity> source, string propertyName, string value) 
    {
    
        Expression<Func<TEntity, bool>> whereExpression = x => x.GetType().InvokeMember(propertyName, BindingFlags.GetProperty, null, x, null).ObjectToString().IndexOf(value, StringComparison.InvariantCultureIgnoreCase) >= 0;
    
        return source.Where(whereExpression);       
    }
    

    注意:ObjectToString只是另一种扩展方法,如果传入的Object是NULL,则返回string.Empty

    【讨论】:

    • 这不会被转换为 SQL,而是会在从数据库加载的对象上执行,因此您可能会带回整个表。
    • @jakubkonecki 好点,但这并不意味着它没有用处。我使用这种东西来过滤从 Web 服务调用接收到的对象。有时这些网络方法不提供过滤功能,或者它们只是需要很长时间才能返回。如果您已经从数据库中获得了对象,这仍然很有用。在许多有效场景中,不返回数据库过滤数据更有意义。 Web 服务器和数据库并不总是在同一个盒子甚至是同一个地理位置
    • 我同意 Linq2Objects 很有用,但是由于存储库模式,这个问题似乎与查询数据库有关。
    • @JakubKonecki 1. 它与存储库模式无关,OP 在调用 _repo.GetAll(); 时已经获取 IEnumerable; (见对他问题的评论)所以他已经有了具体的对象。 Linq2Objects 非常适合这里。 2. 我不会因为变量名称为repository 而假设使用存储库模式。 3. 使用我的方法并获取整个表格内容没有风险。它会立即抛出运行时异常,因为它不能被翻译成 SQL。
    • AD 1. IQueryable : IEnumerable - 所以GetAll() 可能仍会返回 Linq 查询,而不是具体对象。 AD 2。但结合方法名称、参数和功能,它构成了一个引人注目的案例 AD 3。这正是我投反对票的原因。
    【解决方案3】:

    使用反射

    model = model.Where(x =>
    ((string)x.GetType().GetProperty("searchField").GetValue(0, null)) == searchTerm);
    

    【讨论】:

      【解决方案4】:

      对于 linq2Object 你可以像下面这样使用反射(不是很快):

      model.Where(x => x.GetType().GetProperty(propName).GetValue(x, null) == propVal);
      

      但对于 linq2Entity,我认为这不起作用,它适用于 linq2objects。

      【讨论】:

        【解决方案5】:

        这应该是类型安全的:

        public IEnumerable<T> Where<T,U>(Func<T,U> propertySelector, U value)
        {
          return  model.Where(x => propertySelector(x) == value);
        }
        

        用法:

        Where((MyClass x) => x.PropertyName, propertyValue);
        

        或者:

        public IEnumerable<T> Where<T>(Func<T,bool> entitySelector)
        {
          return  model.Where(entitySelector);
        }
        

        用法:

        Where<MyClass>(x => x.PropertyName == propertyValue && x.OtherProperty == otherValue);
        

        【讨论】:

          【解决方案6】:

          我认为以下实现看起来非常像您最初的意图,尽管将其更改为通用方法可能更有意义。

          public IEnumerable<Person> getPeople(string searchField, string searchTerm) {
                  PropertyInfo getter=typeof(Person).GetProperty(searchField);
                  if(getter==null) {
                      throw new ArgumentOutOfRangeException("searchField");
                  }
                  return _repo.GetAll().Where(x => getter.GetValue(x, null).ToString()==searchTerm);
          }
          

          【讨论】:

            【解决方案7】:

            在使用 Entity Framework 时,不要乱用反射、自定义表达式树等,而是考虑使用 Builder Method 对标准 LINQ 运算符的扩展,该运算符采用字符串而不是 lambda。这些更容易为动态查询需求构建:

             string filter = String.Format("it.{0} = @value", fieldName);
             var model = context.People.Where(filter, new ObjectParameter("value", searchValue));
            

            当然,这意味着您需要修改存储库以返回 IObjectSet 而不是 IEnumerable。它也会表现得更好。通过返回 IEnumerable,您将数据库中的每一行混合到存储库中,然后通过 LINQ to Objects 进行过滤,而不是将过滤器应用回您的数据库中。

            有关 EF 中的构建器方法的更多信息,请参阅http://archive.msdn.microsoft.com/EFQuerySamples/Release/ProjectReleases.aspx?ReleaseId=4422 中的 BuilderMethodSamples.cs。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2023-03-29
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多