具有多个可选参数的LINQ Filter查询

时间:2016-12-20 21:12:08

标签: c# asp.net linq filtering

我需要针对返回的列表编写LINQ查询,该列表包含多个可选参数。将有以下变量:

计划, ID, 名字, 姓, DateFrom, DateTo, MemDateOfBirth

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

因此,例如,如果他们提供了一个名字以及往返日期,我希望返回一个人的所有实例的过滤列表,该名称由起始日期之间的第一个名称等等...

使用LINQ实现此目的的最简单方法是什么?这些变量是可选参数,因此可以提供任何或所有变量。我知道我可以返回主列表,然后多次过滤它以获得结果,但我想知道是否有更快,更简单的方法通过LINQ来做...

提前感谢您的帮助!

4 个答案:

答案 0 :(得分:2)

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

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
        };

答案 1 :(得分:1)

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

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

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


因此,我们要使用的是:

  • 反思(又名慢家伙,所以我想如果要争取毫秒,最好选择多个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,则可以使用GET,以[FromQuery]方法自动获得此对象。

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();

仅此而已!


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

简而言之:

我们有扩展方法,该方法接受Filtering对象,使用反射将其分解,因为非null属性使用表达式创建lambda,并将这些lambda添加到源IEnumerable<T>

对于类型安全过滤对象必须实现通用接口IFilterParams<T>

代码:

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;
    }
}

答案 2 :(得分:0)

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

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

这个过滤器创建有点乏味,但这符合您的目的。

答案 3 :(得分:0)

假设您不想对许多表进行概括,我会做类似的事情:

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

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