【问题标题】:LINQ Filter query with multiple optional parameters具有多个可选参数的 LINQ 筛选器查询
【发布时间】:2017-05-06 04:49:56
【问题描述】:

我需要针对接受多个可选参数的返回列表编写一个 LINQ 查询。会有以下变量:

计划, ID, 名, 姓, 日期从, 日期至, MemDateOfBirth

我想使用这些参数通过 LINQ 返回一个过滤列表,但它们都是可选的。当用户点击搜索按钮时,至少会提供一个,但这将由用户决定他们想要搜索的内容。如果他们提供超过 1 个,我需要按他们提供的所有方法进行过滤...

因此,例如,如果他们提供名字和去往日期,我想返回一个人的所有实例的过滤列表,该列表在去往日期和起始日期之间等...

使用 LINQ 完成此任务的最简单方法是什么?这些变量是可选参数,因此可以提供它们中的任何一个或全部。我知道我可以返回主列表,然后对其进行多次过滤以获得结果,但我想知道是否有更快、更简单的方法通过 LINQ 来实现...

提前感谢您的帮助!

【问题讨论】:

  • 我只需要在if 语句中调用多个Where

标签: c# asp.net linq filtering


【解决方案1】:

我发现这是解决此类问题的最简单方法

var q = from mt in myTable
        where (mt.FIrstname == FirstNameparam || FirstNameparam == null)
        && (mt.lastname == lastnameParam || lastnameParam == null)
        && (mt.DateField == DateParam || DateParam == null)
        select new
        {
            mt.FIrstname,
            mt.lastname,
            mt.DateField
        };

【讨论】:

    【解决方案2】:

    假设您不想对许多表进行概括,我会这样做:

    var query = 
        from m in db.table
        select m;
    
    if (plan.HasValue)
    {
        query = query.Where( x => x.plan == plan.Value);
    }
    
    ... other filters ....
    
    // Then use data (for ex. make a list).
    var list = query.ToList();
    

    根据提供者的不同,您也可以直接在 LINQ 中编写条件,例如:

    from m id db.table
    where plan == null || m.plan == plan
    where...
    select m;
    

    或使用!plan.HasValue || m.plan == plan

    但是,这更加脆弱,因为并非所有提供商都以相同的方式处理或支持这些情况。如果相应的列允许为空,则需要特别小心。

    【讨论】:

      【解决方案3】:

      昨天面试时被问到这个问题,所以这是我的想法:)

      所有以前的答案(动态 LINQ,多个 Where 子句)不会让您摆脱检查是否指定了可选参数的代码 - 例如,要使用 dLINQ,您仍然必须创建一个“连接”细绳。这些连接将基于您的可选参数的存在,那么如果您可以以相同的方式直接创建Where-sequence,为什么要创建字符串(或为什么要配置过滤器列表)?

      好吧,老实说,我的方法也包含这个逻辑,但你不会看到它(嗯..也许只是一点点:))。


      所以,我们要使用的是:

      • 反射(又名慢人,所以如果你争取毫秒,我想,你最好去多个if-Where
      • 属性
      • 表达式
      • 当然还有 LINQ。

      首先,让我向您展示它是如何工作的。假设我们有模型:

      public class Person
      {
          public string Name { get; }
          public int Age { get; }
      
          public Person( string name, int age )
          {
              Name = name;
              Age = age;
          }
      }
      

      你也将拥有.. 我们称之为Filtering object

      整洁的奖金!例如,如果您处理 ASP.Net Core,您可以使用 [FromQuery] 自动获取 GET 方法的此对象。

      public class PersonFilterParams : IFilterParams<Person>
      {
          [Filter( FilterType.Equals )]
          public string Name { get; set; }
      
          [Filter( nameof( Person.Age ), FilterType.GreaterOrEquals )]
          public int? MinAge { get; set; }
      
          [Filter( nameof( Person.Age ), FilterType.LessOrEquals )]
          public int? MaxAge { get; set; }
      
          [Filter( nameof( NonExistingProp ), FilterType.LessOrEquals )]
          public int? NonExistingProp { get; set; }
      }
      

      下面是如何使用它:

      // you can skip properies here
      var filter = new PersonFilterParams
      {
          //Name = "Name 4",
          //MinAge = 2,
          MaxAge = 20,
          NonExistingProp = 20,
      };
      
      var filteredPersons = persons
          .Filter( filter )
          .ToList();
      

      就是这样!


      现在让我们看看它是如何实现的。

      简而言之:

      我们有扩展方法,它接受过滤对象,使用反射分解它,对于非空属性使用表达式创建 lambda,将这些 lambda 添加到源 IEnumerable&lt;T&gt;

      对于类型安全过滤对象必须实现通用接口IFilterParams&lt;T&gt;

      代码:

      public enum FilterType
      {
          None,
      
          Less,
          LessOrEquals,
          Equals,
          Greater,
          GreaterOrEquals,
      }
      
      [AttributeUsage( AttributeTargets.Property, Inherited = false, AllowMultiple = false )]
      sealed class FilterAttribute : Attribute
      {
          public string PropName { get; }
          public FilterType FilterType { get; }
      
          public FilterAttribute() : this( null, FilterType.Equals )
          {
          }
      
          public FilterAttribute( FilterType filterType ) : this( null, filterType )
          {
          }
      
          public FilterAttribute( string propName, FilterType filterType )
          {
              PropName = propName;
              FilterType = filterType;
          }
      }
      
      public interface IFilterParams<T>
      {
      
      }
      
      public static class Extensions
      {
          public static IEnumerable<T> Filter<T>( this IEnumerable<T> source, IFilterParams<T> filterParams )
          {
              var sourceProps = typeof( T ).GetProperties();
              var filterProps = filterParams.GetType().GetProperties();
      
              foreach ( var prop in filterProps )
              {
                  var filterAttr = prop.GetCustomAttribute<FilterAttribute>();
      
                  if ( filterAttr == null )
                      continue;
      
                  object val = prop.GetValue( filterParams );
      
                  if ( val == null )
                      continue;
      
                  // oops.. little hole..
                  if ( prop.PropertyType == typeof( string ) && (string)val == string.Empty )
                      continue;
      
                  string propName = string.IsNullOrEmpty( filterAttr.PropName )
                      ? prop.Name
                      : filterAttr.PropName;
      
                  if ( !sourceProps.Any( x => x.Name == propName ) )
                      continue;
      
                  Func<T, bool> filter = CreateFilter<T>( propName, filterAttr.FilterType, val );
      
                  source = source.Where( filter );
              }
      
              return source;
          }
      
          private static Func<T, bool> CreateFilter<T>( string propName, FilterType filterType, object val )
          {
              var item = Expression.Parameter( typeof( T ), "x" );
              var propEx = Expression.Property( item, propName );
              var valEx = Expression.Constant( val );
      
              Expression compareEx = null;
      
              switch ( filterType )
              {
                  case FilterType.LessOrEquals:
                      compareEx = Expression.LessThanOrEqual( propEx, valEx );
                      break;
      
                  case FilterType.Less:
                      compareEx = Expression.LessThan( propEx, valEx );
                      break;
      
                  case FilterType.Equals:
                      compareEx = Expression.Equal( propEx, valEx );
                      break;
      
                  case FilterType.Greater:
                      compareEx = Expression.GreaterThan( propEx, valEx );
                      break;
      
                  case FilterType.GreaterOrEquals:
                      compareEx = Expression.GreaterThanOrEqual( propEx, valEx );
                      break;
      
                  default:
                      throw new Exception( $"Unknown FilterType '{filterType}' on property '{propName}'!" );
              }
      
              var lambda = Expression.Lambda<Func<T, bool>>( compareEx, item );
      
              Func<T, bool> filter = lambda.Compile();
      
              return filter;
          }
      }
      

      【讨论】:

        【解决方案4】:

        您可以使用 EntityFramework.DynamicFilters 来创建动态过滤器,或者您可以在 linq 中动态创建 where 子句。这是如何动态创建 where 子句的很棒的教程。

        https://www.codeproject.com/Tips/582450/Build-Where-Clause-Dynamically-in-Linq

        此过滤器创建有点乏味,但这将满足您的目的。

        【讨论】:

        • 对于单次使用(以及每个过滤列的预定义条件),该代码太复杂了...... 您仍然需要确认它可以与您的提供商一起使用并进行过滤在服务器上
        • 但是这个解决方案可以用于各种过滤,即使第二部分显示了更通用的方法来构建这个过滤器。检查他有 7 个项目来过滤他的标准,这将是 7 左右! = 5040 组合,我认为他不会对过滤项目进行这么多查询。
        • 如果您查看 cmets,其中之一是 Expression Code is not executed on sql server 所以这是一个危险信号。在任何情况下,您都没有 5040 个组合,因为每个过滤器都可以独立激活,因此您只有 7 个条件......另请参阅我的答案。
        猜你喜欢
        • 2018-03-10
        • 1970-01-01
        • 1970-01-01
        • 2014-09-22
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-01-06
        相关资源
        最近更新 更多