昨天面试时被问到这个问题,所以这是我的想法:)
所有以前的答案(动态 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<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;
}
}