【问题标题】:Entity Framework where clause filtering from specific column实体框架 where 子句从特定列过滤
【发布时间】:2022-01-23 21:03:11
【问题描述】:

我遇到了 Entity Framework 6 的问题,我不知道如何解决它。 我想用 EntityFramework 从表中读取数据。 目标是使用 where 子句读取该数据,该子句从特定列中过滤数据。我要搜索的列在方法参数中指定。

一个例子: 我有一张人桌

Name FirstName Adress Email
Conner Brian New York abc@abc.com
Schwarzenegger Arnold Los Angeles abc@abc.com

通常我会选择这样的数据:

public List<Person> getData(string searchTerm)
{
    using (var db = new myDB())
    {
      return db.Person.Where(x=> x.Name == searchTerm).ToList();
    } 
}

但我也想灵活处理我要过滤的列。像这样的:

public getData(string columnToSearch, string searchTerm)
{
     using (var db = new myDB())
    {
      return db.Person.Where(columnToSearch == searchTerm).ToList();
    } 
}

我该怎么做?

我不想使用普通 SQL,也无法编辑数据库。

感谢您的帮助。

【问题讨论】:

标签: c# entity-framework linq dynamic where-clause


【解决方案1】:

鉴于您只有 4 列,我真的认为我会:

public List<Person> GetData(string columnToSearch, string searchTerm)
{
    using var db = new myDB();
  
    if(columnToSearch == "Name")
      return db.Person.Where(p => p.Name == searchTerm).ToList();
    else if(columnToSearch == "FirstName")
      return db.Person.Where(p => p.FirstName == searchTerm).ToList();
    else if(columnToSearch == "Adress")
      return db.Person.Where(p => p.Adress == searchTerm).ToList();
    else if(columnToSearch == "Email")
      return db.Person.Where(p => p.Email == searchTerm).ToList();
    else
       throw ..
}

..或一些类似的选择结构..

public List<Person> GetData(string columnToSearch, string searchTerm)
{
    using var db = new myDB();
  
    return (columnToSearch switch {
        "Name" => db.Person.Where(p => p.Name == searchTerm),
        "FirstName" => db.Person.Where(p => p.FirstName == searchTerm)
        "Adress" => db.Person.Where(p => p.Adress == searchTerm)
        "Email" => db.Person.Where(p => p.Email == searchTerm)
        _ => throw new ArgumentException(nameof(columnToSearch) + " should be one of: Name, FirstName, Adress, Email")
    }).ToList();
}

..也许甚至可以切换进行挑选的委托:

public List<Person> GetData(string columnToSearch, string searchTerm)
{
    Func<Person, bool> what = columnToSearch switch {
        "Name" => p => p.Name == searchTerm,
        "FirstName" => p => p.FirstName == searchTerm
        "Adress" => p => p.Adress == searchTerm
        "Email" => p => p.Email == searchTerm
        _ => throw new ArgumentException(nameof(columnToSearch) + " should be one of: Name, FirstName, Adress, Email")
    };

    using var db = new myDB();
    return db..Person.Where(what).ToList();
}

即使您在此表中有 20 列,使用多行编辑器编写类似这样的重复代码也相当容易:

那是一个名为 Sublime 的编辑器,但 VS 也可以,可以使用 ctrl+alt+单击放置多个光标,或者选择一些常用的东西并按 shift+alt+.。如果你使用 VS,你会想要安装“Multiple Carets Booster”扩展,否则粘贴会很疯狂;在大多数做多个插入符号的编辑器中,如果你有例如20 个插入符号,然后从剪贴板粘贴 20 行(例如 20 个列名),每个插入符号得到一个剪辑行。在 VS 中,通常每个插入符号都有 20 行(粘贴 400 行),这使得将所有 20 列都放在剪贴板上并像上面的动画一样将它们粘贴到代码流中非常困难

【讨论】:

    【解决方案2】:

    您可以使用以下扩展方法:

    public static class QueryableExtensions
    {
        public static IQueryable<T> FilterByColumn<T>(this IQueryable<T> query, string columnToSearch, string searchTerm)
        {
            var param = Expression.Parameter(typeof(T), "e");
    
            var body = Expression.Equal(
                Expression.PropertyOrField(param, columnToSearch),
                Expression.Constatnt(searchTerm));
    
            var filter = Expression.Lambda<Func<T, bool>>(body, param);
    
            return query.Where(filter);
        }
    }
    

    及用法:

    public getData(string columnToSearch, string searchTerm)
    {
        using (var db = new myDB())
        {
            return db.Person.FilterByColumn(columnToSearch, earchTerm).ToList();
        } 
    }
    

    【讨论】:

      【解决方案3】:

      您正在使用实体框架,它可以访问您的Persons 表。您可能会有一个 DbContext 具有访问此表的属性:

      public DbSet<Person> Persons {get; set;}
      

      你还有两个字符串。一个字符串表示要过滤的Person 属性的名称,另一个字符串表示该属性应具有的值。

      为了让您的代码更易于阅读、更易于重用、更易于维护和单元测试,我的建议是将您的问题拆分为两个子问题:

      • 将字符串columnToSearch(实际上是您要搜索的列的名称)翻译成propertySelector,类似于GroupBy中的keySelector。
      • 创建使用此 KeySelector 选择属性的扩展方法,并使用 searchTermWhere 仅保留具有相同值的 Persons

      这种分离的好处是,您可以在访问数据库之前检测 columnToSearch 的错误值。如果您不想支持某些属性,例如外键,可以将它们排除在外。该方法易于理解、易于重用、易于单元测试并且易于更改,例如,如果您想添加一个新列。

      Expression<Func<Person, string>> ToPropertySelector(string columnName)
      {
          switch (columName)
          {
              case nameof(Person.Name):
                 return person => person.Name;
              case nameof(Person.FirstName):
                 return person => person.FirstName;
              ...
      
              default:  // unsupported columnName
                  throw new InvalidArgumentException(...);
          }
      }
      

      如果您的列有其他名称而不是您想与用户交流的名称,例如,如果外部使用想到 LastName 而不是名称,您可以轻松更改过程:

      case "Name":
      case "LastName":
          return person => person.Name;
      

      此外,如果以后列名更改,外部用户也不必更改。

      用法:

      string columName = "Email";
      string value = "MyName.Gmail.Com";
      
      using (var dbContext = new MyDbContext(...))
      {
          var propertySelector = ToPropertySelector(columnName);
          return dbContext.Persons
              .Where(person => propertySelector(person) == value)
              .ToList();
      }
      

      当然,这只有在你的属性有一个字符串作为值时才有效。如果您还想支持其他属性类型,则需要创建一个具有正确类型的扩展方法:

      Expression<Func<Person, TProperty>> ToPropertySelector<TProperty>(string columnName)
      {
          ... see above
      }
      
      public static IQueryable<Person> Where<TProperty>(IQueryable<Person> persons,
          string columName,
          TProperty value)
      {
          var propertySelector = ToPropertySelector<TProperty>(columnName);
          return persons.Where(person => propertySelector(person) == value);
      }
      
      public static IQueryable<Person> Where<TProperty>(IQueryable<Person> persons,
          string columName,
          string valueTxt)
      {
          Type propertyType = typeof(TProperty);
          TypeConverter converter = propertyType.GetConverter();
      
          TProperty value = converter.ConvertFromString(valueTxt)
          return persons.Where(columnName, value);
      }
      

      用法:

      DateTime birthDay = new DateTime(1993, 11, 23);
      IQueryable<Person> persons = ...
      
      var personsBornOnDate = persons.Where("BirthDay", birthDay);
      

      重用示例:不必将BirthDay用作字符串,可以使用属性选择器:

      var personsBornOnDate = persons.Where(person => person.BirthDay, birthDay);
      

      后者的好处是,如果您使用不存在的属性,编译器会警告您。字符串版本只会在运行时发出警告。

      【讨论】:

      • propertySelector(person) == value 不适用于 EF Core,更糟糕的是它无法编译。
      猜你喜欢
      • 1970-01-01
      • 2011-12-31
      • 2011-10-21
      • 2010-10-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多