2025-02-13 13:35:41 +02:00
using Radzen ;
using Radzen.Blazor ;
2021-01-19 11:02:49 +02:00
using System ;
using System.Collections ;
using System.Collections.Generic ;
2026-01-07 12:29:58 +05:00
using System.ComponentModel ;
2023-05-05 15:54:10 +03:00
using System.Globalization ;
2021-01-19 11:02:49 +02:00
using System.Linq ;
2025-02-13 13:35:41 +02:00
using System.Linq.Expressions ;
using System.Reflection ;
2025-02-14 10:28:35 +02:00
using System.Text.RegularExpressions ;
2021-01-19 11:02:49 +02:00
namespace Radzen
{
2021-10-07 11:35:29 +03:00
/// <summary>
/// Class QueryableExtension.
/// </summary>
2021-01-19 11:02:49 +02:00
public static class QueryableExtension
{
2025-02-13 13:35:41 +02:00
static Expression notNullCheck ( Expression property ) = > Nullable . GetUnderlyingType ( property . Type ) ! = null | | property . Type = = typeof ( string ) ?
2025-02-14 14:51:09 +02:00
Expression . Coalesce ( property , property . Type = = typeof ( string ) ? Expression . Constant ( string . Empty ) : Expression . Constant ( null , property . Type ) ) : property ;
2025-02-13 13:35:41 +02:00
/// <summary>
/// Projects each element of a sequence into a collection of property values.
/// </summary>
2025-09-09 08:15:34 +02:00
public static IQueryable Select ( this IQueryable source , string propertyName )
2025-02-13 13:35:41 +02:00
{
2026-01-07 12:29:58 +05:00
ArgumentNullException . ThrowIfNull ( source ) ;
ArgumentNullException . ThrowIfNull ( propertyName ) ;
2025-02-13 13:35:41 +02:00
var parameter = Expression . Parameter ( source . ElementType , "x" ) ;
var property = GetNestedPropertyExpression ( parameter , propertyName ) ;
var lambda = Expression . Lambda ( notNullCheck ( property ) , parameter ) ;
var selectExpression = Expression . Call ( typeof ( Queryable ) ,
nameof ( Queryable . Select ) , [ source . ElementType , property . Type ] , source . Expression ,
Expression . Quote ( lambda ) ) ;
return source . Provider . CreateQuery ( selectExpression ) ;
}
2026-01-30 12:09:34 +02:00
/// <summary>
/// Projects each element of a sequence into a collection of property values.
/// </summary>
public static IQueryable Select ( this IQueryable source , IEnumerable < string > propertyNames )
{
ArgumentNullException . ThrowIfNull ( source ) ;
ArgumentNullException . ThrowIfNull ( propertyNames ) ;
var parameter = Expression . Parameter ( source . ElementType , "x" ) ;
var bindings = new List < MemberBinding > ( ) ;
var allProperties = source . ElementType . GetProperties ( ) ;
foreach ( var property in allProperties . Where ( p = > propertyNames . Contains ( p . Name ) ) )
{
bindings . Add ( Expression . Bind ( property , Expression . Property ( parameter , property ) ) ) ;
}
var body = Expression . MemberInit ( Expression . New ( source . ElementType ) , bindings ) ;
var delegateType = typeof ( Func < , > ) . MakeGenericType ( source . ElementType , source . ElementType ) ;
var lambda = Expression . Lambda ( delegateType , body , parameter ) ;
var selectExpression = Expression . Call ( typeof ( Queryable ) ,
nameof ( Queryable . Select ) , [ source . ElementType , source . ElementType ] , source . Expression ,
Expression . Quote ( lambda ) ) ;
return source . Provider . CreateQuery ( selectExpression ) ;
}
/// <summary>
/// Projects each element of a sequence into a collection of property values.
/// </summary>
public static IQueryable < T > Select < T > ( this IQueryable < T > source , IEnumerable < string > propertyNames )
{
ArgumentNullException . ThrowIfNull ( source ) ;
ArgumentNullException . ThrowIfNull ( propertyNames ) ;
var parameter = Expression . Parameter ( typeof ( T ) , "x" ) ;
var bindings = new List < MemberBinding > ( ) ;
var allProperties = typeof ( T ) . GetProperties ( ) ;
foreach ( var property in allProperties . Where ( p = > propertyNames . Contains ( p . Name ) ) )
{
bindings . Add ( Expression . Bind ( property , Expression . Property ( parameter , property ) ) ) ;
}
var body = Expression . MemberInit ( Expression . New ( typeof ( T ) ) , bindings ) ;
var lambda = Expression . Lambda < Func < T , T > > ( body , parameter ) ;
var selectExpression = Expression . Call ( typeof ( Queryable ) ,
nameof ( Queryable . Select ) , [ typeof ( T ) , typeof ( T ) ] , source . Expression ,
Expression . Quote ( lambda ) ) ;
return source . Provider . CreateQuery < T > ( selectExpression ) ;
}
2025-02-13 13:35:41 +02:00
/// <summary>
/// Projects each element of a sequence to an IEnumerable and flattens the resulting sequences into one sequence.
/// </summary>
public static IQueryable SelectMany ( this IQueryable source , string propertyName )
{
2026-01-07 12:29:58 +05:00
ArgumentNullException . ThrowIfNull ( source ) ;
ArgumentNullException . ThrowIfNull ( propertyName ) ;
2025-02-13 13:35:41 +02:00
var parameter = Expression . Parameter ( source . ElementType , "x" ) ;
var property = GetNestedPropertyExpression ( parameter , propertyName ) ;
var lambda = Expression . Lambda ( notNullCheck ( property ) , parameter ) ;
var returnElementType = property . Type . GetElementType ( ) ? ?
( property . Type . IsGenericType ? property . Type . GetGenericArguments ( ) [ 0 ] : typeof ( object ) ) ;
var enumerableType = typeof ( IEnumerable < > ) . MakeGenericType ( returnElementType ) ;
var delegateType = typeof ( Func < , > ) . MakeGenericType ( source . ElementType , enumerableType ) ;
lambda = Expression . Lambda ( delegateType , lambda . Body , lambda . Parameters ) ;
var selectManyExpression = Expression . Call ( typeof ( Queryable ) ,
nameof ( Queryable . SelectMany ) , [ source . ElementType , returnElementType ] , source . Expression ,
Expression . Quote ( lambda ) ) ;
return source . Provider . CreateQuery ( selectManyExpression ) ;
}
/// <summary>
/// Projects each element of a sequence to an IEnumerable and flattens the resulting sequences into one sequence.
/// </summary>
public static IQueryable < GroupResult > GroupByMany < T > ( this IQueryable < T > source , string [ ] properties )
{
2026-01-07 12:29:58 +05:00
ArgumentNullException . ThrowIfNull ( source ) ;
ArgumentNullException . ThrowIfNull ( properties ) ;
2025-02-13 13:35:41 +02:00
var parameter = Expression . Parameter ( source . ElementType , "x" ) ;
return GroupByMany ( source ,
2025-05-07 13:11:30 +03:00
properties . Select ( p = > Expression . Lambda < Func < T , object > > ( Expression . Convert ( GetNestedPropertyExpression ( parameter , p ) , typeof ( object ) ) , parameter ) . Compile ( ) ) . ToArray ( ) ,
2025-02-13 13:35:41 +02:00
0 ) ;
}
private static IQueryable < GroupResult > GroupByMany < T > ( IEnumerable < T > source , Func < T , object > [ ] lambdas , int index )
{
return source . GroupBy ( lambdas [ index ] ) . Select (
g = > new GroupResult
{
Key = g . Key ,
Count = g . Count ( ) ,
Items = g ,
Subgroups = index < lambdas . Length - 1 ? GroupByMany ( g , lambdas , index + 1 ) : null
} ) . AsQueryable ( ) ;
}
2025-02-19 17:58:20 +02:00
internal static string RemoveVariableReference ( string expression )
{
// Regex pattern to match any variable reference in a lambda expression
string pattern = @"^\s*\b\w+\b\s*=>\s*" ; // Matches "it => " or similar
// Remove the variable reference from the start
expression = Regex . Replace ( expression , pattern , "" ) . Trim ( ) ;
// Remove remaining instances of the variable reference prefix (e.g., "it.")
pattern = @"\b\w+\." ; // Matches "it.", "x.", etc.
expression = Regex . Replace ( expression , pattern , "" ) ;
return expression . Trim ( ) ;
}
2025-02-13 13:35:41 +02:00
/// <summary>
/// Sorts the elements of a sequence in ascending or descending order according to a key.
/// </summary>
2025-02-19 18:09:44 +02:00
/// <returns>A <see cref="IQueryable{T}"/> whose elements are sorted according to the specified <paramref name="selector"/>.</returns>
2026-01-07 12:29:58 +05:00
public static IOrderedQueryable < T > OrderBy < T > ( this IQueryable < T > source , string? selector = null )
2025-02-13 13:35:41 +02:00
{
2026-01-07 12:29:58 +05:00
ArgumentNullException . ThrowIfNull ( source ) ;
2025-04-24 13:04:31 +03:00
selector = $"{selector}" ;
2026-01-07 12:29:58 +05:00
if ( selector . Contains ( "=>" , StringComparison . Ordinal ) )
2025-04-24 13:04:31 +03:00
{
var identifierName = selector . Split ( "=>" ) [ 0 ] ;
2026-01-07 12:29:58 +05:00
selector = selector . Replace ( $"{identifierName}=>" , "" , StringComparison . Ordinal ) . Trim ( ) ;
2025-04-24 13:04:31 +03:00
string methodAsc = "OrderBy" ;
string methodDesc = "OrderByDescending" ;
Expression expression = source . Expression ;
foreach ( var part in selector . Split ( "," ) )
{
var lambda = ExpressionParser . ParseLambda < T > ( $"{identifierName.Trim()} => {part}" ) ;
expression = Expression . Call (
2026-01-07 12:29:58 +05:00
typeof ( Queryable ) , part . Trim ( ) . ToLower ( CultureInfo . InvariantCulture ) . Contains ( " desc" , StringComparison . Ordinal ) ? methodDesc : methodAsc ,
2025-04-24 13:04:31 +03:00
new Type [ ] { source . ElementType , lambda . ReturnType } ,
expression , Expression . Quote ( lambda ) ) ;
methodAsc = "ThenBy" ;
methodDesc = "ThenByDescending" ;
}
return ( IOrderedQueryable < T > ) source . Provider . CreateQuery ( expression ) ;
}
2025-02-19 17:58:20 +02:00
return ( IOrderedQueryable < T > ) OrderBy ( ( IQueryable ) source , selector ) ;
2025-02-13 13:35:41 +02:00
}
/// <summary>
/// Sorts the elements of a sequence in ascending or descending order according to a key.
/// </summary>
2025-02-19 18:09:44 +02:00
/// <returns>A <see cref="IQueryable"/> whose elements are sorted according to the specified <paramref name="selector"/>.</returns>
2026-01-07 12:29:58 +05:00
public static IQueryable OrderBy ( this IQueryable source , string? selector = null )
2025-02-13 13:35:41 +02:00
{
2026-01-07 12:29:58 +05:00
ArgumentNullException . ThrowIfNull ( source ) ;
selector = ! string . IsNullOrEmpty ( selector ) & & selector . Contains ( "=>" , StringComparison . Ordinal )
? RemoveVariableReference ( selector )
: selector ;
2025-02-19 17:58:20 +02:00
2025-02-13 13:35:41 +02:00
var parameters = new ParameterExpression [ ] { Expression . Parameter ( source . ElementType , "x" ) } ;
2026-01-07 12:29:58 +05:00
var parameterExpression = parameters [ 0 ] ;
2025-02-13 13:35:41 +02:00
Expression expression = source . Expression ;
string methodAsc = "OrderBy" ;
string methodDesc = "OrderByDescending" ;
2025-03-07 11:14:15 +02:00
string [ ] sortStrings = new string [ ] { "asc" , "desc" } ;
2025-02-13 13:35:41 +02:00
2025-02-19 17:58:20 +02:00
foreach ( var o in ( selector ? ? "" ) . Split ( ',' , StringSplitOptions . RemoveEmptyEntries ) )
2025-02-13 13:35:41 +02:00
{
var nameAndOrder = o . Trim ( ) ;
2025-03-07 11:14:15 +02:00
var name = string . Join ( " " , nameAndOrder . Split ( ' ' ) . Where ( i = > ! sortStrings . Contains ( i . Trim ( ) ) ) ) . Trim ( ) ;
var order = nameAndOrder . Split ( ' ' ) . FirstOrDefault ( i = > sortStrings . Contains ( i . Trim ( ) ) ) ? ? sortStrings . First ( ) ;
2025-02-13 13:35:41 +02:00
2026-01-07 12:29:58 +05:00
Expression property = ! string . IsNullOrEmpty ( name ) & & name ! = "x" & & name ! = "it"
? GetNestedPropertyExpression ( parameterExpression , name )
: parameterExpression ;
2025-02-13 13:35:41 +02:00
expression = Expression . Call (
2025-03-07 11:14:15 +02:00
typeof ( Queryable ) , order . Equals ( sortStrings . First ( ) , StringComparison . OrdinalIgnoreCase ) ? methodAsc : methodDesc ,
2025-02-13 13:35:41 +02:00
new Type [ ] { source . ElementType , property . Type } ,
2025-03-21 07:04:17 +02:00
expression , Expression . Quote ( Expression . Lambda ( property , parameters ) ) ) ;
2025-02-13 13:35:41 +02:00
methodAsc = "ThenBy" ;
methodDesc = "ThenByDescending" ;
}
return source . Provider . CreateQuery ( expression ) ;
}
/// <summary>
/// Returns the first element of a sequence, or a default value if the sequence contains no elements.
/// </summary>
/// <param name="source">The <see cref="IQueryable"/> to return the first element of.</param>
/// <returns>default if source is empty; otherwise, the first element in source.</returns>
public static dynamic FirstOrDefault ( this IQueryable source )
{
2026-01-07 12:29:58 +05:00
ArgumentNullException . ThrowIfNull ( source ) ;
var methodInfo = typeof ( Queryable )
. GetTypeInfo ( )
. GetDeclaredMethods ( nameof ( Queryable . FirstOrDefault ) )
. First ( mi = > mi . IsGenericMethod & & mi . GetParameters ( ) . Length = = 1 )
. MakeGenericMethod ( source . ElementType ) ;
return source . Provider . Execute ( Expression . Call ( null , methodInfo , source . Expression ) ) ! ;
2025-02-13 13:35:41 +02:00
}
2025-10-03 11:07:39 +03:00
/// <summary>
/// Returns the last element of a sequence, or a default value if the sequence contains no elements.
/// </summary>
/// <param name="source">The <see cref="IQueryable"/> to return the last element of.</param>
/// <returns>default if source is empty; otherwise, the last element in source.</returns>
public static dynamic LastOrDefault ( this IQueryable source )
{
2026-01-07 12:29:58 +05:00
ArgumentNullException . ThrowIfNull ( source ) ;
var methodInfo = typeof ( Queryable )
. GetTypeInfo ( )
. GetDeclaredMethods ( nameof ( Queryable . LastOrDefault ) )
. First ( mi = > mi . IsGenericMethod & & mi . GetParameters ( ) . Length = = 1 )
. MakeGenericMethod ( source . ElementType ) ;
return source . Provider . Execute ( Expression . Call ( null , methodInfo , source . Expression ) ) ! ;
2025-10-03 11:07:39 +03:00
}
2025-02-13 13:35:41 +02:00
/// <summary>
/// Converts the elements of an <see cref="IQueryable"/> to the specified type.
/// </summary>
/// <param name="source">The <see cref="IQueryable"/> that contains the elements to be converted.</param>
/// <param name="type">The type to convert the elements of source to.</param>
/// <returns>An <see cref="IQueryable"/> that contains each element of the source sequence converted to the specified type.</returns>
public static IQueryable Cast ( this IQueryable source , Type type )
{
2026-01-07 12:29:58 +05:00
ArgumentNullException . ThrowIfNull ( source ) ;
ArgumentNullException . ThrowIfNull ( type ) ;
var methodInfo = typeof ( Queryable )
. GetTypeInfo ( )
. GetDeclaredMethods ( nameof ( Queryable . Cast ) )
. First ( mi = > mi . IsGenericMethod & & mi . GetParameters ( ) . Length = = 1 )
. MakeGenericMethod ( type ) ;
return source . Provider . CreateQuery ( Expression . Call ( null , methodInfo , source . Expression ) ) ;
2025-02-13 13:35:41 +02:00
}
2025-10-03 11:07:39 +03:00
/// <summary>
/// Sums the elements of an <see cref="IQueryable"/> using the specified type.
/// </summary>
/// <param name="source">The <see cref="IQueryable"/> that contains the elements to be summed.</param>
/// <param name="type">The type to cast the elements of source to.</param>
/// <returns>The result.</returns>
public static dynamic Sum ( this IQueryable source , Type type )
{
2026-01-07 12:29:58 +05:00
ArgumentNullException . ThrowIfNull ( source ) ;
ArgumentNullException . ThrowIfNull ( type ) ;
var methodInfo = typeof ( Queryable )
. GetTypeInfo ( )
. GetDeclaredMethods ( nameof ( Queryable . Sum ) )
. First ( mi = > mi . GetParameters ( ) . Length = = 1 & & mi . ReturnType = = type ) ;
return source . Provider . Execute ( Expression . Call ( null , methodInfo , source . Expression ) ) ! ;
2025-10-03 11:07:39 +03:00
}
/// <summary>
/// Calculates the average of the elements of an <see cref="IQueryable"/> using the specified type.
/// </summary>
/// <param name="source">The <see cref="IQueryable"/> that contains the elements to be used for average calculation.</param>
/// <param name="type">The type to cast the elements of source to.</param>
/// <returns>The result.</returns>
public static dynamic Average ( this IQueryable source , Type type )
{
2026-01-07 12:29:58 +05:00
ArgumentNullException . ThrowIfNull ( source ) ;
ArgumentNullException . ThrowIfNull ( type ) ;
var methodInfo = typeof ( Queryable )
. GetTypeInfo ( )
. GetDeclaredMethods ( nameof ( Queryable . Average ) )
. First ( mi = >
mi . GetParameters ( ) . Length = = 1 & &
mi . GetParameters ( ) [ 0 ] . ParameterType . IsGenericType & &
mi . GetParameters ( ) [ 0 ] . ParameterType . GetGenericArguments ( ) [ 0 ] = = type & &
mi . ReturnType = = type ) ;
return source . Provider . Execute ( Expression . Call ( null , methodInfo , source . Expression ) ) ! ;
2025-10-03 11:07:39 +03:00
}
/// <summary>
/// Gets the minimum value of elements of an <see cref="IQueryable"/> using the specified type.
/// </summary>
/// <param name="source">The <see cref="IQueryable"/> that contains the elements to be used to get the minimum value.</param>
/// <param name="type">The type to cast the elements of source to.</param>
/// <returns>The result.</returns>
public static dynamic Min ( this IQueryable source , Type type )
{
2026-01-07 12:29:58 +05:00
ArgumentNullException . ThrowIfNull ( source ) ;
ArgumentNullException . ThrowIfNull ( type ) ;
var methodInfo = typeof ( Queryable )
. GetTypeInfo ( )
. GetDeclaredMethods ( nameof ( Queryable . Min ) )
. First ( mi = > mi . GetParameters ( ) . Length = = 1 & & mi . ReturnType = = type ) ;
return source . Provider . Execute ( Expression . Call ( null , methodInfo , source . Expression ) ) ! ;
2025-10-03 11:07:39 +03:00
}
/// <summary>
/// Gets the maximum value of elements of an <see cref="IQueryable"/> using the specified type.
/// </summary>
/// <param name="source">The <see cref="IQueryable"/> that contains the elements to be used to get the maximum value.</param>
/// <param name="type">The type to cast the elements of source to.</param>
/// <returns>The result.</returns>
public static dynamic Max ( this IQueryable source , Type type )
{
2026-01-07 12:29:58 +05:00
ArgumentNullException . ThrowIfNull ( source ) ;
ArgumentNullException . ThrowIfNull ( type ) ;
var maxMethod = typeof ( Queryable ) . GetTypeInfo ( ) . GetDeclaredMethods ( nameof ( Queryable . Max ) ) . FirstOrDefault ( mi = > mi . GetParameters ( ) . Length = = 1 & & mi . ReturnType = = type ) ;
if ( maxMethod = = null )
{
throw new InvalidOperationException ( $"Max method not found for type {type}" ) ;
}
return source . Provider . Execute ( Expression . Call ( null , maxMethod , source . Expression ) ) ! ;
2025-10-03 11:07:39 +03:00
}
2025-02-13 13:35:41 +02:00
/// <summary>
/// Returns distinct elements from a sequence by using the default equality comparer to compare values.
/// </summary>
/// <param name="source">The sequence to remove duplicate elements from.</param>
/// <returns>An <see cref="IQueryable"/> that contains distinct elements from the source sequence.</returns>
public static IQueryable Distinct ( this IQueryable source )
{
2026-01-07 12:29:58 +05:00
ArgumentNullException . ThrowIfNull ( source ) ;
var distinctMethod = typeof ( Queryable ) . GetTypeInfo ( )
. GetDeclaredMethods ( nameof ( Queryable . Distinct ) )
. FirstOrDefault ( mi = > mi . IsGenericMethod & & mi . GetParameters ( ) . Length = = 1 )
? ? throw new InvalidOperationException ( "Unable to locate Queryable.Distinct method." ) ;
return source . Provider . CreateQuery ( Expression . Call (
null ,
distinctMethod . MakeGenericMethod ( source . ElementType ) ,
2025-02-13 13:35:41 +02:00
source . Expression ) ) ;
}
/// <summary>
/// Filters using the specified filter descriptors.
/// </summary>
public static IQueryable Where (
this IQueryable source ,
IEnumerable < FilterDescriptor > filters ,
LogicalFilterOperator logicalFilterOperator ,
FilterCaseSensitivity filterCaseSensitivity )
{
2026-01-07 12:29:58 +05:00
ArgumentNullException . ThrowIfNull ( source ) ;
ArgumentNullException . ThrowIfNull ( filters ) ;
2025-02-13 13:35:41 +02:00
var whereMethod = typeof ( QueryableExtension )
. GetMethods ( )
. First ( m = > m . Name = = "Where" & & m . IsGenericMethodDefinition & & m . GetParameters ( ) . Any ( p = > p . ParameterType = = typeof ( IEnumerable < FilterDescriptor > ) ) )
. MakeGenericMethod ( source . ElementType ) ;
2026-01-07 12:29:58 +05:00
var result = whereMethod . Invoke ( null , new object [ ] { source , filters , logicalFilterOperator , filterCaseSensitivity } ) as IQueryable ;
if ( result = = null )
{
throw new InvalidOperationException ( "Queryable.Where invocation returned null." ) ;
}
return result ;
2025-02-13 13:35:41 +02:00
}
/// <summary>
/// Filters using the specified filter descriptors.
/// </summary>
2025-05-07 13:11:30 +03:00
public static IQueryable < T > Where < T > ( this IQueryable < T > source , IEnumerable < FilterDescriptor > filters ,
2025-02-13 13:35:41 +02:00
LogicalFilterOperator logicalFilterOperator , FilterCaseSensitivity filterCaseSensitivity )
{
2026-01-07 12:29:58 +05:00
ArgumentNullException . ThrowIfNull ( source ) ;
if ( filters = = null )
{
return source ;
}
var filterList = filters as ICollection < FilterDescriptor > ? ? filters . ToList ( ) ;
if ( filterList . Count = = 0 )
2025-02-13 13:35:41 +02:00
return source ;
var parameter = Expression . Parameter ( typeof ( T ) , "x" ) ;
2026-01-07 12:29:58 +05:00
Expression ? combinedExpression = null ;
2025-02-13 13:35:41 +02:00
2026-01-07 12:29:58 +05:00
foreach ( var filter in filterList )
2025-02-13 13:35:41 +02:00
{
2026-01-07 12:29:58 +05:00
var expression = GetExpression < T > ( parameter , filter , filterCaseSensitivity , filter . Type ? ? typeof ( object ) ) ;
2025-02-13 13:35:41 +02:00
if ( expression = = null ) continue ;
combinedExpression = combinedExpression = = null
? expression
: logicalFilterOperator = = LogicalFilterOperator . And ?
Expression . AndAlso ( combinedExpression , expression ) :
Expression . OrElse ( combinedExpression , expression ) ;
}
if ( combinedExpression = = null )
return source ;
2026-01-07 12:29:58 +05:00
var lambda = Expression . Lambda < Func < T , bool > > ( combinedExpression ! , parameter ) ;
2025-02-13 13:35:41 +02:00
return source . Where ( lambda ) ;
}
2026-01-07 12:29:58 +05:00
internal static Expression GetNestedPropertyExpression ( Expression expression , string property , Type ? type = null )
2025-02-13 13:35:41 +02:00
{
2026-01-07 12:29:58 +05:00
var parts = property . Split ( separator , 2 ) ;
2025-02-13 14:28:08 +02:00
string currentPart = parts [ 0 ] ;
2025-02-13 13:35:41 +02:00
Expression member ;
2025-02-13 14:28:08 +02:00
2025-05-27 07:17:05 +03:00
if ( expression . Type . IsGenericType & & typeof ( IDictionary < , > ) . IsAssignableFrom ( expression . Type . GetGenericTypeDefinition ( ) ) | |
2025-08-13 09:14:59 +03:00
typeof ( IDictionary ) . IsAssignableFrom ( expression . Type ) | | typeof ( System . Data . DataRow ) . IsAssignableFrom ( expression . Type ) )
2025-02-13 13:35:41 +02:00
{
2025-02-13 14:28:08 +02:00
var key = currentPart . Split ( '"' ) [ 1 ] ;
var typeString = currentPart . Split ( '(' ) [ 0 ] ;
2025-02-13 13:35:41 +02:00
2026-01-07 12:29:58 +05:00
var indexer = typeof ( System . Data . DataRow ) . IsAssignableFrom ( expression . Type ) ?
Expression . Property ( expression , expression . Type . GetProperty ( "Item" , new [ ] { typeof ( string ) } ) ! , Expression . Constant ( key ) ) :
Expression . Property ( expression , expression . Type . GetProperty ( "Item" ) ! , Expression . Constant ( key ) ) ;
2025-02-13 13:35:41 +02:00
member = Expression . Convert (
2025-05-12 12:35:46 +03:00
indexer ,
2026-01-07 12:29:58 +05:00
parts . Length > 1 ? indexer . Type : type ? ? Type . GetType ( typeString . EndsWith ( '?' ) ? $"System.Nullable`1[System.{typeString.TrimEnd('?')}]" : $"System.{typeString}" ) ? ? typeof ( object ) ) ;
2025-02-13 14:28:08 +02:00
}
2026-01-07 12:29:58 +05:00
else if ( currentPart . Contains ( '[' , StringComparison . Ordinal ) ) // Handle array or list indexing
2025-02-13 14:28:08 +02:00
{
2026-01-07 12:29:58 +05:00
var indexStart = currentPart . IndexOf ( '[' , StringComparison . Ordinal ) ;
2025-02-13 14:28:08 +02:00
var propertyName = currentPart . Substring ( 0 , indexStart ) ;
var indexString = currentPart . Substring ( indexStart + 1 , currentPart . Length - indexStart - 2 ) ;
member = Expression . PropertyOrField ( expression , propertyName ) ;
if ( int . TryParse ( indexString , out int index ) )
{
if ( member . Type . IsArray )
{
2025-08-13 08:52:41 +03:00
member = Expression . ArrayIndex ( member , Expression . Constant ( index ) ) ;
2025-02-13 14:28:08 +02:00
}
else if ( member . Type . IsGenericType & &
( member . Type . GetGenericTypeDefinition ( ) = = typeof ( List < > ) | |
typeof ( IList < > ) . IsAssignableFrom ( member . Type . GetGenericTypeDefinition ( ) ) ) )
{
var itemProperty = member . Type . GetProperty ( "Item" ) ;
if ( itemProperty ! = null )
{
member = Expression . Property ( member , itemProperty , Expression . Constant ( index ) ) ;
}
}
}
else
{
throw new ArgumentException ( $"Invalid index format: {indexString}" ) ;
}
2025-02-13 13:35:41 +02:00
}
2026-01-07 12:29:58 +05:00
else if ( expression ! = null & & expression . Type ! = null & & expression . Type . IsInterface )
2025-03-06 21:03:35 +02:00
{
member = Expression . Property ( expression ,
2026-01-07 12:29:58 +05:00
new [ ] { expression . Type } . Concat ( expression . Type . GetInterfaces ( ) ) . FirstOrDefault ( t = > t . GetProperty ( currentPart ) ! = null ) ! ,
2025-03-06 21:03:35 +02:00
currentPart
) ;
}
2025-02-13 13:35:41 +02:00
else
{
2026-01-07 12:29:58 +05:00
if ( expression = = null | | string . IsNullOrEmpty ( currentPart ) )
{
return Expression . Constant ( null , typeof ( object ) ) ;
}
var p = expression . Type ? . GetProperty ( currentPart , BindingFlags . Public | BindingFlags . Instance ) ;
member = p ! = null ? Expression . Property ( expression , p ) : Expression . PropertyOrField ( expression , currentPart ) ;
2025-02-13 13:35:41 +02:00
}
2026-01-07 12:29:58 +05:00
if ( expression ! = null & & expression . Type ! = null & & expression . Type . IsValueType & & Nullable . GetUnderlyingType ( expression . Type ) = = null )
2025-02-17 12:56:57 +02:00
{
expression = Expression . Convert ( expression , typeof ( object ) ) ;
}
2025-02-13 13:35:41 +02:00
return parts . Length > 1 ? GetNestedPropertyExpression ( member , parts [ 1 ] , type ) :
( Nullable . GetUnderlyingType ( member . Type ) ! = null | | member . Type = = typeof ( string ) ) ?
2026-01-07 12:29:58 +05:00
expression ! = null ? Expression . Condition ( Expression . Equal ( expression , Expression . Constant ( null ) ) , Expression . Constant ( null , member . Type ) , member ) : member :
2025-02-13 14:28:08 +02:00
member ;
2025-02-13 13:35:41 +02:00
}
internal static Expression GetExpression < T > ( ParameterExpression parameter , FilterDescriptor filter , FilterCaseSensitivity filterCaseSensitivity , Type type )
{
2026-01-07 12:29:58 +05:00
Type ? valueType = filter . FilterValue ! = null ? filter . FilterValue . GetType ( ) : null ;
2025-02-14 17:58:04 +02:00
var isEnumerable = valueType ! = null & & IsEnumerable ( valueType ) & & valueType ! = typeof ( string ) ;
2026-01-07 12:29:58 +05:00
Type ? secondValueType = filter . SecondFilterValue ! = null ? filter . SecondFilterValue . GetType ( ) : null ;
2025-02-13 13:35:41 +02:00
2026-01-07 12:29:58 +05:00
Expression p = ! string . IsNullOrEmpty ( filter . Property ) ? GetNestedPropertyExpression ( parameter , filter . Property , type ) : Expression . Constant ( null ) ;
2025-02-18 23:10:17 +02:00
2026-01-07 12:29:58 +05:00
var propertyName = ! isEnumerable & & ! IsEnumerable ( p . Type ) ? ( ! string . IsNullOrWhiteSpace ( filter . FilterProperty ) ? filter . FilterProperty : filter . Property ) : filter . Property ;
Expression property = ! string . IsNullOrEmpty ( propertyName ) ? GetNestedPropertyExpression ( parameter , propertyName , type ) : Expression . Constant ( null ) ;
2025-02-13 13:35:41 +02:00
2026-01-07 12:29:58 +05:00
Type ? collectionItemType = IsEnumerable ( property . Type ) & & property . Type . IsGenericType ? property . Type . GetGenericArguments ( ) [ 0 ] : null ;
2025-02-13 13:35:41 +02:00
2026-01-07 12:29:58 +05:00
ParameterExpression ? collectionItemTypeParameter = collectionItemType ! = null ? Expression . Parameter ( collectionItemType , "x" ) : null ;
2025-02-13 13:35:41 +02:00
2025-07-09 16:59:15 +02:00
if ( collectionItemType ! = null & & filter . Property ! = filter . FilterProperty )
2025-02-13 13:35:41 +02:00
{
2026-01-07 12:29:58 +05:00
property = ! string . IsNullOrEmpty ( filter . FilterProperty )
? GetNestedPropertyExpression ( collectionItemTypeParameter ! , filter . FilterProperty )
: collectionItemTypeParameter ! ;
2025-02-13 13:35:41 +02:00
filter . FilterOperator = filter . FilterOperator = = FilterOperator . In ? FilterOperator . Contains :
filter . FilterOperator = = FilterOperator . NotIn ? FilterOperator . DoesNotContain : filter . FilterOperator ;
}
2025-02-17 16:35:57 +02:00
var isEnum = ! isEnumerable & & ( PropertyAccess . IsEnum ( property . Type ) | | PropertyAccess . IsNullableEnum ( property . Type ) ) ;
2025-02-14 17:58:04 +02:00
var caseInsensitive = property . Type = = typeof ( string ) & & ! isEnumerable & & filterCaseSensitivity = = FilterCaseSensitivity . CaseInsensitive ;
2025-02-13 13:35:41 +02:00
2025-03-04 08:46:10 +02:00
var isEnumerableProperty = IsEnumerable ( property . Type ) & & property . Type ! = typeof ( string ) ;
2026-01-07 12:29:58 +05:00
var constantValue = caseInsensitive
? $"{filter.FilterValue}" . ToLowerInvariant ( )
: isEnum & & ! isEnumerable & & filter . FilterValue ! = null
? Enum . ToObject ( Nullable . GetUnderlyingType ( property . Type ) ? ? property . Type , filter . FilterValue )
: filter . FilterValue ;
Type constantType ;
if ( ! isEnum & & isEnumerable )
{
constantType = valueType ? ? typeof ( object ) ;
}
else if ( isEnumerableProperty )
{
constantType = valueType ? ? property . Type ;
}
else
{
constantType = property . Type ;
}
if ( constantValue = = null & & constantType . IsValueType & & Nullable . GetUnderlyingType ( constantType ) = = null )
{
constantType = typeof ( object ) ;
}
var constant = Expression . Constant ( constantValue , constantType ) ;
2025-02-13 13:35:41 +02:00
2025-02-14 17:58:04 +02:00
if ( caseInsensitive & & ! isEnumerable )
2025-02-13 13:35:41 +02:00
{
2026-01-07 12:29:58 +05:00
property = Expression . Call ( notNullCheck ( property ) , typeof ( string ) . GetMethod ( "ToLower" , Type . EmptyTypes ) ! ) ;
2025-02-13 13:35:41 +02:00
}
2026-01-07 12:29:58 +05:00
Expression ? secondConstant = null ;
if ( filter . SecondFilterValue ! = null )
{
var secondValue = caseInsensitive
? $"{filter.SecondFilterValue}" . ToLowerInvariant ( )
: isEnum
? Enum . ToObject ( Nullable . GetUnderlyingType ( property . Type ) ? ? property . Type , filter . SecondFilterValue )
: filter . SecondFilterValue ;
Type secondConstantType ;
if ( secondValueType ! = null & & ! isEnum & & IsEnumerable ( secondValueType ) )
{
secondConstantType = secondValueType ;
}
else
{
secondConstantType = property . Type ;
}
if ( secondValue = = null & & secondConstantType . IsValueType & & Nullable . GetUnderlyingType ( secondConstantType ) = = null )
{
secondConstantType = typeof ( object ) ;
}
secondConstant = Expression . Constant ( secondValue , secondConstantType ) ;
}
2025-02-13 13:35:41 +02:00
2026-01-07 12:29:58 +05:00
Expression ? primaryExpression = filter . FilterOperator switch
2025-02-13 13:35:41 +02:00
{
FilterOperator . Equals = > Expression . Equal ( notNullCheck ( property ) , constant ) ,
FilterOperator . NotEquals = > Expression . NotEqual ( notNullCheck ( property ) , constant ) ,
FilterOperator . LessThan = > Expression . LessThan ( notNullCheck ( property ) , constant ) ,
FilterOperator . LessThanOrEquals = > Expression . LessThanOrEqual ( notNullCheck ( property ) , constant ) ,
FilterOperator . GreaterThan = > Expression . GreaterThan ( notNullCheck ( property ) , constant ) ,
FilterOperator . GreaterThanOrEquals = > Expression . GreaterThanOrEqual ( notNullCheck ( property ) , constant ) ,
2025-02-14 17:58:04 +02:00
FilterOperator . Contains = > isEnumerable ?
2025-02-20 10:59:26 +02:00
Expression . Call ( typeof ( Enumerable ) , nameof ( Enumerable . Contains ) , new Type [ ] { property . Type } , constant , notNullCheck ( property ) ) :
2025-08-13 08:52:41 +03:00
isEnumerableProperty ?
2026-01-07 12:29:58 +05:00
Expression . Call ( typeof ( Enumerable ) , nameof ( Enumerable . Contains ) , new Type [ ] { collectionItemType ! } , notNullCheck ( property ) , constant ) :
Expression . Call ( notNullCheck ( property ) , typeof ( string ) . GetMethod ( "Contains" , new [ ] { typeof ( string ) } ) ! , constant ) ,
2025-02-14 17:58:04 +02:00
FilterOperator . In = > isEnumerable & &
2025-03-04 08:46:10 +02:00
isEnumerableProperty ?
2026-01-07 12:29:58 +05:00
Expression . Call ( typeof ( Enumerable ) , nameof ( Enumerable . Any ) , new Type [ ] { collectionItemType ! } ,
Expression . Call ( typeof ( Enumerable ) , nameof ( Enumerable . Intersect ) , new Type [ ] { collectionItemType ! } , constant , notNullCheck ( property ) ) ) : Expression . Constant ( true ) ,
2025-02-14 17:58:04 +02:00
FilterOperator . DoesNotContain = > isEnumerable ?
2025-02-20 10:59:26 +02:00
Expression . Not ( Expression . Call ( typeof ( Enumerable ) , nameof ( Enumerable . Contains ) , new Type [ ] { property . Type } , constant , notNullCheck ( property ) ) ) :
2025-03-04 08:46:10 +02:00
isEnumerableProperty ?
2026-01-07 12:29:58 +05:00
Expression . Not ( Expression . Call ( typeof ( Enumerable ) , nameof ( Enumerable . Contains ) , new Type [ ] { collectionItemType ! } , notNullCheck ( property ) , constant ) ) :
Expression . Not ( Expression . Call ( notNullCheck ( property ) , typeof ( string ) . GetMethod ( "Contains" , new [ ] { typeof ( string ) } ) ! , constant ) ) ,
2025-02-14 17:58:04 +02:00
FilterOperator . NotIn = > isEnumerable & &
2025-03-04 08:46:10 +02:00
isEnumerableProperty ?
2026-01-07 12:29:58 +05:00
Expression . Call ( typeof ( Enumerable ) , nameof ( Enumerable . Any ) , new Type [ ] { collectionItemType ! } ,
Expression . Call ( typeof ( Enumerable ) , nameof ( Enumerable . Except ) , new Type [ ] { collectionItemType ! } , constant , notNullCheck ( property ) ) ) : Expression . Constant ( true ) ,
FilterOperator . StartsWith = > Expression . Call ( notNullCheck ( property ) , typeof ( string ) . GetMethod ( "StartsWith" , new [ ] { typeof ( string ) } ) ! , constant ) ,
FilterOperator . EndsWith = > Expression . Call ( notNullCheck ( property ) , typeof ( string ) . GetMethod ( "EndsWith" , new [ ] { typeof ( string ) } ) ! , constant ) ,
2025-02-13 13:35:41 +02:00
FilterOperator . IsNull = > Expression . Equal ( property , Expression . Constant ( null , property . Type ) ) ,
FilterOperator . IsNotNull = > Expression . NotEqual ( property , Expression . Constant ( null , property . Type ) ) ,
FilterOperator . IsEmpty = > Expression . Equal ( property , Expression . Constant ( String . Empty ) ) ,
FilterOperator . IsNotEmpty = > Expression . NotEqual ( property , Expression . Constant ( String . Empty ) ) ,
_ = > null
} ;
2026-01-07 12:29:58 +05:00
if ( collectionItemType ! = null & & primaryExpression ! = null & &
! ( filter . FilterOperator = = FilterOperator . In | | filter . FilterOperator = = FilterOperator . NotIn ) )
{
if ( filter . Property ! = null )
2025-02-13 13:35:41 +02:00
{
2026-01-07 12:29:58 +05:00
primaryExpression = Expression . Call ( typeof ( Enumerable ) , filter . CollectionFilterMode = = CollectionFilterMode . Any ? nameof ( Enumerable . Any ) : nameof ( Enumerable . All ) , new Type [ ] { collectionItemType ! } ,
GetNestedPropertyExpression ( parameter , filter . Property ) , Expression . Lambda ( primaryExpression , collectionItemTypeParameter ! ) ) ;
}
2025-02-13 13:35:41 +02:00
}
2026-01-07 12:29:58 +05:00
Expression ? secondExpression = null ;
2025-02-13 13:35:41 +02:00
if ( secondConstant ! = null )
{
secondExpression = filter . SecondFilterOperator switch
{
FilterOperator . Equals = > Expression . Equal ( notNullCheck ( property ) , secondConstant ) ,
FilterOperator . NotEquals = > Expression . NotEqual ( notNullCheck ( property ) , secondConstant ) ,
FilterOperator . LessThan = > Expression . LessThan ( notNullCheck ( property ) , secondConstant ) ,
FilterOperator . LessThanOrEquals = > Expression . LessThanOrEqual ( notNullCheck ( property ) , secondConstant ) ,
FilterOperator . GreaterThan = > Expression . GreaterThan ( notNullCheck ( property ) , secondConstant ) ,
FilterOperator . GreaterThanOrEquals = > Expression . GreaterThanOrEqual ( notNullCheck ( property ) , secondConstant ) ,
2026-01-07 12:29:58 +05:00
FilterOperator . Contains = > Expression . Call ( notNullCheck ( property ) , typeof ( string ) . GetMethod ( "Contains" , new [ ] { typeof ( string ) } ) ! , secondConstant ) ,
FilterOperator . DoesNotContain = > Expression . Not ( Expression . Call ( notNullCheck ( property ) , property . Type . GetMethod ( "Contains" , new [ ] { typeof ( string ) } ) ! , secondConstant ) ) ,
FilterOperator . StartsWith = > Expression . Call ( notNullCheck ( property ) , typeof ( string ) . GetMethod ( "StartsWith" , new [ ] { typeof ( string ) } ) ! , secondConstant ) ,
FilterOperator . EndsWith = > Expression . Call ( notNullCheck ( property ) , typeof ( string ) . GetMethod ( "EndsWith" , new [ ] { typeof ( string ) } ) ! , secondConstant ) ,
2025-02-13 13:35:41 +02:00
FilterOperator . IsNull = > Expression . Equal ( property , Expression . Constant ( null , property . Type ) ) ,
FilterOperator . IsNotNull = > Expression . NotEqual ( property , Expression . Constant ( null , property . Type ) ) ,
FilterOperator . IsEmpty = > Expression . Equal ( property , Expression . Constant ( String . Empty ) ) ,
FilterOperator . IsNotEmpty = > Expression . NotEqual ( property , Expression . Constant ( String . Empty ) ) ,
_ = > null
} ;
}
2026-01-07 12:29:58 +05:00
if ( collectionItemType ! = null & & secondExpression ! = null & &
! ( filter . SecondFilterOperator = = FilterOperator . In | | filter . SecondFilterOperator = = FilterOperator . NotIn ) )
{
if ( filter . Property ! = null )
2025-02-13 13:35:41 +02:00
{
2026-01-07 12:29:58 +05:00
secondExpression = Expression . Call ( typeof ( Enumerable ) , nameof ( Enumerable . Any ) , new Type [ ] { collectionItemType ! } ,
GetNestedPropertyExpression ( parameter , filter . Property ) , Expression . Lambda ( secondExpression , collectionItemTypeParameter ! ) ) ;
}
2025-02-13 13:35:41 +02:00
}
if ( primaryExpression ! = null & & secondExpression ! = null )
{
return filter . LogicalFilterOperator switch
{
LogicalFilterOperator . And = > Expression . AndAlso ( primaryExpression , secondExpression ) ,
LogicalFilterOperator . Or = > Expression . OrElse ( primaryExpression , secondExpression ) ,
_ = > primaryExpression
} ;
}
2026-01-07 12:29:58 +05:00
return primaryExpression ! ;
2025-02-13 13:35:41 +02:00
}
2021-10-07 11:35:29 +03:00
/// <summary>
/// The linq filter operators
/// </summary>
2021-05-25 09:01:11 +03:00
internal static readonly IDictionary < FilterOperator , string > LinqFilterOperators = new Dictionary < FilterOperator , string >
2021-05-17 04:23:29 -07:00
{
2025-02-13 13:35:41 +02:00
{ FilterOperator . Equals , "==" } ,
2021-05-17 04:23:29 -07:00
{ FilterOperator . NotEquals , "!=" } ,
{ FilterOperator . LessThan , "<" } ,
{ FilterOperator . LessThanOrEquals , "<=" } ,
{ FilterOperator . GreaterThan , ">" } ,
{ FilterOperator . GreaterThanOrEquals , ">=" } ,
{ FilterOperator . StartsWith , "StartsWith" } ,
{ FilterOperator . EndsWith , "EndsWith" } ,
2021-07-20 11:08:32 +03:00
{ FilterOperator . Contains , "Contains" } ,
2021-11-30 11:31:52 +02:00
{ FilterOperator . DoesNotContain , "DoesNotContain" } ,
2023-05-22 16:57:04 +03:00
{ FilterOperator . In , "In" } ,
{ FilterOperator . NotIn , "NotIn" } ,
2021-11-30 11:31:52 +02:00
{ FilterOperator . IsNull , "==" } ,
2022-07-07 14:14:30 +02:00
{ FilterOperator . IsEmpty , "==" } ,
{ FilterOperator . IsNotNull , "!=" } ,
2023-09-28 12:56:21 +07:00
{ FilterOperator . IsNotEmpty , "!=" } ,
{ FilterOperator . Custom , "" }
2021-05-17 04:23:29 -07:00
} ;
2021-10-07 11:35:29 +03:00
/// <summary>
/// The o data filter operators
/// </summary>
2021-05-25 09:01:11 +03:00
internal static readonly IDictionary < FilterOperator , string > ODataFilterOperators = new Dictionary < FilterOperator , string >
2021-05-18 11:39:53 +03:00
{
{ FilterOperator . Equals , "eq" } ,
{ FilterOperator . NotEquals , "ne" } ,
{ FilterOperator . LessThan , "lt" } ,
{ FilterOperator . LessThanOrEquals , "le" } ,
{ FilterOperator . GreaterThan , "gt" } ,
{ FilterOperator . GreaterThanOrEquals , "ge" } ,
{ FilterOperator . StartsWith , "startswith" } ,
{ FilterOperator . EndsWith , "endswith" } ,
2021-07-20 11:08:32 +03:00
{ FilterOperator . Contains , "contains" } ,
2025-05-20 11:46:24 +03:00
{ FilterOperator . DoesNotContain , "contains" } ,
2021-11-30 11:31:52 +02:00
{ FilterOperator . IsNull , "eq" } ,
2022-07-07 14:14:30 +02:00
{ FilterOperator . IsEmpty , "eq" } ,
{ FilterOperator . IsNotNull , "ne" } ,
2023-05-23 14:33:57 +03:00
{ FilterOperator . IsNotEmpty , "ne" } ,
{ FilterOperator . In , "in" } ,
2023-09-28 12:56:21 +07:00
{ FilterOperator . NotIn , "in" } ,
{ FilterOperator . Custom , "" }
2021-05-18 11:39:53 +03:00
} ;
2026-01-07 12:29:58 +05:00
private static readonly char [ ] separator = new char [ ] { '.' } ;
2021-05-18 11:39:53 +03:00
2021-10-07 11:35:29 +03:00
/// <summary>
/// Converts to list.
/// </summary>
/// <param name="query">The query.</param>
/// <returns>IList.</returns>
2021-01-19 11:02:49 +02:00
public static IList ToList ( IQueryable query )
{
2026-01-07 12:29:58 +05:00
ArgumentNullException . ThrowIfNull ( query ) ;
var toListMethod = typeof ( Enumerable ) . GetMethods ( )
. FirstOrDefault ( m = > m . Name = = nameof ( Enumerable . ToList ) & & m . GetParameters ( ) . Length = = 1 )
? ? throw new InvalidOperationException ( "Unable to locate Enumerable.ToList method." ) ;
var genericToList = toListMethod . MakeGenericMethod ( query . ElementType ) ;
var result = genericToList . Invoke ( null , new object [ ] { query } ) ;
if ( result is IList list )
{
return list ;
}
throw new InvalidOperationException ( "Failed to convert queryable to list." ) ;
2021-01-19 11:02:49 +02:00
}
2021-10-07 11:35:29 +03:00
/// <summary>
/// Converts to filterstring.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="columns">The columns.</param>
/// <returns>System.String.</returns>
2026-01-07 12:29:58 +05:00
public static string ToFilterString < T > ( this IEnumerable < RadzenDataGridColumn < T > > columns ) where T : notnull
2021-05-17 04:23:29 -07:00
{
2025-05-07 13:11:30 +03:00
Func < RadzenDataGridColumn < T > , bool > canFilter = ( c ) = > c . Filterable & & c . FilterPropertyType ! = null & &
2026-01-09 08:07:17 +02:00
( ! ( c . GetFilterValue ( ) = = null | | ( c . GetFilterValue ( ) as string ) ? . Length = = 0 )
2025-05-07 13:11:30 +03:00
| | c . GetFilterOperator ( ) = = FilterOperator . IsNotNull | | c . GetFilterOperator ( ) = = FilterOperator . IsNull
| | c . GetFilterOperator ( ) = = FilterOperator . IsEmpty | | c . GetFilterOperator ( ) = = FilterOperator . IsNotEmpty )
& & c . GetFilterProperty ( ) ! = null ;
2024-02-26 08:00:34 +01:00
2025-05-07 13:11:30 +03:00
Func < RadzenDataGridColumn < T > , bool > canFilterCustom = ( c ) = > c . Filterable & & c . FilterPropertyType ! = null & &
( c . GetFilterOperator ( ) = = FilterOperator . Custom & & c . GetCustomFilterExpression ( ) ! = null )
& & c . GetFilterProperty ( ) ! = null ;
2021-05-17 04:23:29 -07:00
2026-01-07 12:29:58 +05:00
var columnsToFilter = columns . Where ( canFilter ) . ToList ( ) ;
var columnsWithCustomFilter = columns . Where ( canFilterCustom ) . ToList ( ) ;
2025-07-10 14:27:45 +03:00
2025-05-07 13:11:30 +03:00
var grid = columns . FirstOrDefault ( ) ? . Grid ;
var gridLogicalFilterOperator = grid ! = null ? grid . LogicalFilterOperator : LogicalFilterOperator . And ;
var gridFilterCaseSensitivity = grid ! = null ? grid . FilterCaseSensitivity : FilterCaseSensitivity . Default ;
2025-02-13 13:35:41 +02:00
2025-05-07 13:11:30 +03:00
var serializer = new ExpressionSerializer ( ) ;
var filterExpression = "" ;
2026-01-07 12:29:58 +05:00
if ( columnsToFilter . Count > 0 )
2025-05-07 13:11:30 +03:00
{
var filters = columnsToFilter . Select ( c = > new FilterDescriptor ( )
2021-05-17 04:23:29 -07:00
{
2025-05-07 13:11:30 +03:00
Property = c . Property ,
FilterProperty = c . FilterProperty ,
Type = c . FilterPropertyType ,
FilterValue = c . GetFilterValue ( ) ,
FilterOperator = c . GetFilterOperator ( ) ,
SecondFilterValue = c . GetSecondFilterValue ( ) ,
SecondFilterOperator = c . GetSecondFilterOperator ( ) ,
2025-11-04 10:27:11 +02:00
LogicalFilterOperator = c . GetLogicalFilterOperator ( ) ,
2025-11-04 11:38:06 +02:00
CollectionFilterMode = c . GetCollectionFilterMode ( )
2026-01-07 12:29:58 +05:00
} ) . ToList ( ) ;
2021-06-30 16:09:43 +03:00
2026-01-07 12:29:58 +05:00
if ( filters . Count > 0 )
2025-05-07 13:11:30 +03:00
{
2026-01-07 12:29:58 +05:00
var parameter = Expression . Parameter ( typeof ( T ) , columnsWithCustomFilter . Count > 0 ? "it" : "x" ) ;
Expression ? combinedExpression = null ;
2021-11-18 16:02:26 +02:00
2025-05-07 13:11:30 +03:00
foreach ( var filter in filters )
2021-11-18 16:02:26 +02:00
{
2026-01-07 12:29:58 +05:00
var expression = GetExpression < T > ( parameter , filter , gridFilterCaseSensitivity , filter . Type ? ? typeof ( object ) ) ;
2025-05-07 13:11:30 +03:00
if ( expression = = null ) continue ;
2024-12-19 09:24:23 +02:00
2025-05-07 13:11:30 +03:00
combinedExpression = combinedExpression = = null
? expression
: gridLogicalFilterOperator = = LogicalFilterOperator . And ?
Expression . AndAlso ( combinedExpression , expression ) :
Expression . OrElse ( combinedExpression , expression ) ;
2025-02-18 10:38:52 +02:00
}
2025-05-07 13:11:30 +03:00
if ( combinedExpression ! = null )
2021-06-30 16:09:43 +03:00
{
2025-05-07 13:11:30 +03:00
filterExpression = serializer . Serialize ( Expression . Lambda < Func < T , bool > > ( combinedExpression , parameter ) ) ;
2021-06-30 16:09:43 +03:00
}
2025-05-07 13:11:30 +03:00
}
2021-05-17 04:23:29 -07:00
2025-05-07 13:11:30 +03:00
}
2021-05-17 04:23:29 -07:00
2025-05-07 13:11:30 +03:00
var customFilterExpression = "" ;
2026-01-07 12:29:58 +05:00
if ( columnsWithCustomFilter . Count > 0 )
2025-05-07 13:11:30 +03:00
{
2026-01-07 12:29:58 +05:00
var expressions = columnsWithCustomFilter . Select ( c = > ( c . GetCustomFilterExpression ( ) ? ? "" ) . Replace ( " or " , " || " , StringComparison . Ordinal ) . Replace ( " and " , " && " , StringComparison . Ordinal ) ) . Where ( e = > ! string . IsNullOrEmpty ( e ) ) . ToList ( ) ;
2025-05-07 13:11:30 +03:00
customFilterExpression = string . Join ( $"{(gridLogicalFilterOperator == LogicalFilterOperator.And ? " & & " : " | | ")}" , expressions ) ;
2021-05-17 04:23:29 -07:00
2025-05-07 13:11:30 +03:00
return ! string . IsNullOrEmpty ( filterExpression ) & & ! string . IsNullOrEmpty ( customFilterExpression ) ?
$"{filterExpression} {(gridLogicalFilterOperator == LogicalFilterOperator.And ? " & & " : " | | ")} {customFilterExpression}" :
! string . IsNullOrEmpty ( customFilterExpression ) ? "it => " + customFilterExpression : filterExpression ;
2021-05-17 04:23:29 -07:00
}
2025-05-07 13:11:30 +03:00
return filterExpression ;
2021-05-17 04:23:29 -07:00
}
2024-06-11 07:44:35 +02:00
/// <summary>
/// Converts a RadzenDataFilter to a Linq-compatibly filter string
/// </summary>
/// <typeparam name="T">The type that is being filtered</typeparam>
2024-09-17 09:51:11 +03:00
/// <param name="dataFilter">The RadzenDataFilter component</param>
2024-06-11 07:44:35 +02:00
/// <returns>A Linq-compatible filter string</returns>
2024-09-17 09:51:11 +03:00
public static string ToFilterString < T > ( this RadzenDataFilter < T > dataFilter )
2024-06-11 07:44:35 +02:00
{
2026-01-07 12:29:58 +05:00
ArgumentNullException . ThrowIfNull ( dataFilter ) ;
2024-09-17 09:51:11 +03:00
Func < CompositeFilterDescriptor , bool > canFilter = ( c ) = > dataFilter . properties . Where ( col = > col . Property = = c . Property ) . FirstOrDefault ( ) ? . FilterPropertyType ! = null & &
2026-01-16 09:33:21 +02:00
( ! ( c . FilterValue = = null | | ( c . FilterValue as string ) ? . Length = = 0 )
2025-05-07 13:11:30 +03:00
| | c . FilterOperator = = FilterOperator . IsNotNull | | c . FilterOperator = = FilterOperator . IsNull
| | c . FilterOperator = = FilterOperator . IsEmpty | | c . FilterOperator = = FilterOperator . IsNotEmpty )
& & c . Property ! = null ;
2024-09-17 09:51:11 +03:00
2026-01-07 12:29:58 +05:00
var applicableFilters = dataFilter . Filters . Concat ( dataFilter . Filters . SelectManyRecursive ( i = > i . Filters ? ? Enumerable . Empty < CompositeFilterDescriptor > ( ) ) ) . Where ( canFilter ) . ToList ( ) ;
if ( applicableFilters . Count > 0 )
2024-09-17 09:51:11 +03:00
{
2025-05-07 13:11:30 +03:00
var serializer = new ExpressionSerializer ( ) ;
2024-06-11 07:44:35 +02:00
2025-05-07 13:11:30 +03:00
var filterExpressions = new List < Expression > ( ) ;
2024-06-11 07:44:35 +02:00
2025-05-07 13:11:30 +03:00
var parameter = Expression . Parameter ( typeof ( T ) , "x" ) ;
2024-06-11 07:44:35 +02:00
2025-05-07 13:11:30 +03:00
foreach ( var filter in dataFilter . Filters )
2024-06-11 07:44:35 +02:00
{
2025-05-07 13:11:30 +03:00
AddWhereExpression < T > ( parameter , filter , ref filterExpressions , dataFilter . FilterCaseSensitivity ) ;
2024-06-11 07:44:35 +02:00
}
2024-06-10 10:40:57 -04:00
2026-01-07 12:29:58 +05:00
Expression ? combinedExpression = null ;
2024-05-20 10:55:21 +03:00
2025-05-07 13:11:30 +03:00
foreach ( var expression in filterExpressions )
2022-07-07 14:14:30 +02:00
{
2025-05-07 13:11:30 +03:00
combinedExpression = combinedExpression = = null
? expression
: dataFilter . LogicalFilterOperator = = LogicalFilterOperator . And ?
Expression . AndAlso ( combinedExpression , expression ) :
Expression . OrElse ( combinedExpression , expression ) ;
2022-07-07 14:14:30 +02:00
}
2021-05-17 04:23:29 -07:00
2025-05-07 13:11:30 +03:00
if ( combinedExpression ! = null )
2021-11-30 11:31:52 +02:00
{
2025-05-07 13:11:30 +03:00
return serializer . Serialize ( Expression . Lambda < Func < T , bool > > ( combinedExpression , parameter ) ) ;
2021-11-30 11:31:52 +02:00
}
2021-05-17 04:23:29 -07:00
}
return "" ;
}
2025-05-20 11:46:24 +03:00
/// <summary>
/// Converts a enumerable of CompositeFilterDescriptor to a Linq-compatibly filter string
/// </summary>
/// <typeparam name="T">The type that is being filtered</typeparam>
/// <param name="filters">The enumerable of CompositeFilterDescriptor </param>
/// <param name="logicalFilterOperator">The LogicalFilterOperator</param>
/// <param name="filterCaseSensitivity">The FilterCaseSensitivity</param>
/// <returns>A Linq-compatible filter string</returns>
public static string ToFilterString < T > ( this IEnumerable < CompositeFilterDescriptor > filters ,
LogicalFilterOperator logicalFilterOperator = LogicalFilterOperator . And ,
FilterCaseSensitivity filterCaseSensitivity = FilterCaseSensitivity . Default )
{
2026-01-07 12:29:58 +05:00
ArgumentNullException . ThrowIfNull ( filters ) ;
2026-01-16 09:33:21 +02:00
Func < CompositeFilterDescriptor , bool > canFilter = ( c ) = >
( ! ( c . FilterValue = = null | | ( c . FilterValue as string ) ? . Length = = 0 )
2025-05-20 11:46:24 +03:00
| | c . FilterOperator = = FilterOperator . IsNotNull | | c . FilterOperator = = FilterOperator . IsNull
| | c . FilterOperator = = FilterOperator . IsEmpty | | c . FilterOperator = = FilterOperator . IsNotEmpty )
& & c . Property ! = null ;
2026-01-07 12:29:58 +05:00
var applicableFilters = filters . Concat ( filters . SelectManyRecursive ( i = > i . Filters ? ? Enumerable . Empty < CompositeFilterDescriptor > ( ) ) ) . Where ( canFilter ) . ToList ( ) ;
if ( applicableFilters . Count > 0 )
2025-05-20 11:46:24 +03:00
{
var serializer = new ExpressionSerializer ( ) ;
var filterExpressions = new List < Expression > ( ) ;
var parameter = Expression . Parameter ( typeof ( T ) , "x" ) ;
foreach ( var filter in filters )
{
AddWhereExpression < T > ( parameter , filter , ref filterExpressions , filterCaseSensitivity ) ;
}
2026-01-07 12:29:58 +05:00
Expression ? combinedExpression = null ;
2025-05-20 11:46:24 +03:00
foreach ( var expression in filterExpressions )
{
combinedExpression = combinedExpression = = null
? expression
: logicalFilterOperator = = LogicalFilterOperator . And ?
Expression . AndAlso ( combinedExpression , expression ) :
Expression . OrElse ( combinedExpression , expression ) ;
}
if ( combinedExpression ! = null )
{
return serializer . Serialize ( Expression . Lambda < Func < T , bool > > ( combinedExpression , parameter ) ) ;
}
}
return "" ;
}
2021-10-07 11:35:29 +03:00
/// <summary>
/// Gets the column o data filter.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="column">The column.</param>
2023-04-11 00:50:19 -05:00
/// <param name="filterValue">The specific value to filter by</param>
/// <param name="columnFilterOperator">The operator used to compare to <paramref name="filterValue"/></param>
2021-10-07 11:35:29 +03:00
/// <returns>System.String.</returns>
2026-01-07 12:29:58 +05:00
internal static string GetColumnODataFilter < T > ( RadzenDataGridColumn < T > column , object? filterValue , FilterOperator columnFilterOperator ) where T : notnull
{
var filterProperty = column . GetFilterProperty ( ) ;
if ( string . IsNullOrEmpty ( filterProperty ) )
2021-05-17 04:23:29 -07:00
{
2026-01-07 12:29:58 +05:00
return string . Empty ;
}
var property = filterProperty . Replace ( '.' , '/' ) ;
2021-05-17 04:23:29 -07:00
2021-11-30 11:31:52 +02:00
var odataFilterOperator = ODataFilterOperators [ columnFilterOperator ] ;
2021-05-17 04:23:29 -07:00
2026-01-07 12:29:58 +05:00
var value = column . FilterPropertyType ! = null & & IsEnumerable ( column . FilterPropertyType ) & & column . FilterPropertyType ! = typeof ( string )
2023-04-11 00:50:19 -05:00
? null
2024-09-04 09:02:39 +03:00
: ( string ) Convert . ChangeType ( filterValue is DateTimeOffset ?
2024-11-05 10:52:32 +02:00
( ( DateTimeOffset ) filterValue ) . UtcDateTime : filterValue is DateOnly ?
2025-05-07 13:11:30 +03:00
( ( DateOnly ) filterValue ) . ToString ( "yyy-MM-dd" , CultureInfo . InvariantCulture ) :
2025-11-24 09:18:55 +02:00
filterValue is Guid ? ( ( Guid ) filterValue ) . ToString ( ) :
2026-01-07 12:29:58 +05:00
filterValue ! , typeof ( string ) , CultureInfo . InvariantCulture ) ;
2021-05-17 04:23:29 -07:00
2021-05-18 10:04:34 +03:00
if ( column . Grid . FilterCaseSensitivity = = FilterCaseSensitivity . CaseInsensitive & & column . FilterPropertyType = = typeof ( string ) )
2021-05-17 04:23:29 -07:00
{
property = $"tolower({property})" ;
}
2026-01-07 12:29:58 +05:00
if ( column . FilterPropertyType ! = null & & ( PropertyAccess . IsEnum ( column . FilterPropertyType ) | | PropertyAccess . IsNullableEnum ( column . FilterPropertyType ) ) )
2023-03-14 09:33:47 +02:00
{
return $"{property} {odataFilterOperator} '{value}'" ;
}
else if ( column . FilterPropertyType = = typeof ( string ) )
2021-05-17 04:23:29 -07:00
{
2021-05-18 10:04:34 +03:00
if ( ! string . IsNullOrEmpty ( value ) & & columnFilterOperator = = FilterOperator . Contains )
2021-05-17 04:23:29 -07:00
{
return column . Grid . FilterCaseSensitivity = = FilterCaseSensitivity . CaseInsensitive ?
$"contains({property}, tolower('{value}'))" :
$"contains({property}, '{value}')" ;
}
2021-07-20 11:08:32 +03:00
else if ( ! string . IsNullOrEmpty ( value ) & & columnFilterOperator = = FilterOperator . DoesNotContain )
{
return column . Grid . FilterCaseSensitivity = = FilterCaseSensitivity . CaseInsensitive ?
$"not(contains({property}, tolower('{value}')))" :
$"not(contains({property}, '{value}'))" ;
}
2021-05-17 04:23:29 -07:00
else if ( ! string . IsNullOrEmpty ( value ) & & columnFilterOperator = = FilterOperator . StartsWith )
{
return column . Grid . FilterCaseSensitivity = = FilterCaseSensitivity . CaseInsensitive ?
$"startswith({property}, tolower('{value}'))" :
$"startswith({property}, '{value}')" ;
}
else if ( ! string . IsNullOrEmpty ( value ) & & columnFilterOperator = = FilterOperator . EndsWith )
{
return column . Grid . FilterCaseSensitivity = = FilterCaseSensitivity . CaseInsensitive ?
$"endswith({property}, tolower('{value}'))" :
$"endswith({property}, '{value}')" ;
}
else if ( ! string . IsNullOrEmpty ( value ) & & columnFilterOperator = = FilterOperator . Equals )
{
2021-05-31 09:58:46 +03:00
return column . Grid . FilterCaseSensitivity = = FilterCaseSensitivity . CaseInsensitive ?
$"{property} eq tolower('{value}')" :
$"{property} eq '{value}'" ;
}
else if ( ! string . IsNullOrEmpty ( value ) & & columnFilterOperator = = FilterOperator . NotEquals )
{
return column . Grid . FilterCaseSensitivity = = FilterCaseSensitivity . CaseInsensitive ?
$"{property} ne tolower('{value}')" :
$"{property} ne '{value}'" ;
2021-05-17 04:23:29 -07:00
}
2021-11-30 11:31:52 +02:00
else if ( columnFilterOperator = = FilterOperator . IsNull | | columnFilterOperator = = FilterOperator . IsNotNull )
{
return $"{property} {odataFilterOperator} null" ;
}
2022-07-07 14:14:30 +02:00
else if ( columnFilterOperator = = FilterOperator . IsEmpty | | columnFilterOperator = = FilterOperator . IsNotEmpty )
{
return $"{property} {odataFilterOperator} ''" ;
}
2021-11-18 16:02:26 +02:00
}
2022-07-06 14:09:26 +03:00
else if ( IsEnumerable ( column . FilterPropertyType ) & & column . FilterPropertyType ! = typeof ( string ) )
2021-11-18 16:02:26 +02:00
{
2023-04-11 00:50:19 -05:00
var enumerableValue = ( ( IEnumerable ) ( filterValue ! = null ? filterValue : Enumerable . Empty < object > ( ) ) ) . AsQueryable ( ) ;
2021-11-19 12:36:34 +02:00
var enumerableValueAsString = "(" + String . Join ( "," ,
( enumerableValue . ElementType = = typeof ( string ) ? enumerableValue . Cast < string > ( ) . Select ( i = > $@"'{i}'" ) . Cast < object > ( ) : enumerableValue . Cast < object > ( ) ) ) + ")" ;
2023-05-23 14:33:57 +03:00
var enumerableValueAsStringOrForAny = String . Join ( " or " ,
( enumerableValue . ElementType = = typeof ( string ) ? enumerableValue . Cast < string > ( )
. Select ( i = > $@"i/{property} eq '{i}'" ) . Cast < object > ( ) : enumerableValue . Cast < object > ( ) . Select ( i = > $@"i/{property} eq {i}" ) . Cast < object > ( ) ) ) ;
2025-02-13 13:35:41 +02:00
if ( enumerableValue . Cast < object > ( ) . Any ( ) & & columnFilterOperator = = FilterOperator . Contains )
2021-11-19 12:36:34 +02:00
{
return $"{property} in {enumerableValueAsString}" ;
}
2025-02-13 13:35:41 +02:00
else if ( enumerableValue . Cast < object > ( ) . Any ( ) & & columnFilterOperator = = FilterOperator . DoesNotContain )
2021-11-19 12:36:34 +02:00
{
return $"not({property} in {enumerableValueAsString})" ;
}
2025-02-13 13:35:41 +02:00
else if ( enumerableValue . Cast < object > ( ) . Any ( ) & & columnFilterOperator = = FilterOperator . In )
2023-05-23 14:33:57 +03:00
{
return $"{column.Property}/any(i:{enumerableValueAsStringOrForAny})" ;
}
2025-02-13 13:35:41 +02:00
else if ( enumerableValue . Cast < object > ( ) . Any ( ) & & columnFilterOperator = = FilterOperator . NotIn )
2023-05-23 14:33:57 +03:00
{
return $"not({column.Property}/any(i: {enumerableValueAsStringOrForAny}))" ;
}
2021-05-17 04:23:29 -07:00
}
2026-01-07 12:29:58 +05:00
else if ( column ! = null & & column . FilterPropertyType ! = null & & PropertyAccess . IsNumeric ( column . FilterPropertyType ) )
2021-05-17 04:23:29 -07:00
{
2024-06-11 01:45:34 -04:00
if ( columnFilterOperator = = FilterOperator . IsNull | | columnFilterOperator = = FilterOperator . IsNotNull )
{
return $"{property} {odataFilterOperator} null" ;
}
else
{
return $"{property} {odataFilterOperator} {value}" ;
}
2021-05-17 04:23:29 -07:00
}
2026-01-07 12:29:58 +05:00
else if ( column ! = null & & ( column . FilterPropertyType = = typeof ( bool ) | | column . FilterPropertyType = = typeof ( bool? ) ) )
2021-05-17 04:23:29 -07:00
{
2021-11-30 11:31:52 +02:00
if ( columnFilterOperator = = FilterOperator . IsNull | | columnFilterOperator = = FilterOperator . IsNotNull )
{
return $"{property} {odataFilterOperator} null" ;
}
2022-07-07 14:14:30 +02:00
else if ( columnFilterOperator = = FilterOperator . IsEmpty | | columnFilterOperator = = FilterOperator . IsNotEmpty )
{
return $"{property} {odataFilterOperator} ''" ;
}
2021-11-30 11:31:52 +02:00
else
{
2026-01-07 12:29:58 +05:00
if ( string . IsNullOrEmpty ( value ) )
{
return $"{property} {odataFilterOperator} null" ;
}
return $"{property} eq {value.ToLowerInvariant()}" ;
2021-11-30 11:31:52 +02:00
}
2021-05-17 04:23:29 -07:00
}
2026-01-07 12:29:58 +05:00
else if ( column ! = null & & ( column . FilterPropertyType = = typeof ( DateTime ) | |
2021-05-17 04:23:29 -07:00
column . FilterPropertyType = = typeof ( DateTime ? ) | |
column . FilterPropertyType = = typeof ( DateTimeOffset ) | |
2024-11-21 07:52:24 +01:00
column . FilterPropertyType = = typeof ( DateTimeOffset ? ) | |
2025-05-07 13:11:30 +03:00
column . FilterPropertyType = = typeof ( DateOnly ) | |
2026-01-07 12:29:58 +05:00
column . FilterPropertyType = = typeof ( DateOnly ? ) ) )
2021-05-17 04:23:29 -07:00
{
2021-11-30 11:31:52 +02:00
if ( columnFilterOperator = = FilterOperator . IsNull | | columnFilterOperator = = FilterOperator . IsNotNull )
{
return $"{property} {odataFilterOperator} null" ;
}
2022-07-07 14:14:30 +02:00
else if ( columnFilterOperator = = FilterOperator . IsEmpty | | columnFilterOperator = = FilterOperator . IsNotEmpty )
{
return $"{property} {odataFilterOperator} ''" ;
}
2021-11-30 11:31:52 +02:00
else
{
2026-01-07 12:29:58 +05:00
return $"{property} {odataFilterOperator} {(column.FilterPropertyType == typeof(DateOnly) || column.FilterPropertyType == typeof(DateOnly?) ? value : (value != null ? DateTime.Parse(value, CultureInfo.InvariantCulture, System.Globalization.DateTimeStyles.RoundtripKind).ToString(" yyyy - MM - ddTHH : mm : ss . fffZ ", CultureInfo.InvariantCulture) : " "))}" ;
2021-11-30 11:31:52 +02:00
}
2021-05-17 04:23:29 -07:00
}
2026-01-07 12:29:58 +05:00
else if ( column ? . FilterPropertyType = = typeof ( Guid ) | | column ? . FilterPropertyType = = typeof ( Guid ? ) )
2021-05-17 04:23:29 -07:00
{
2021-11-30 11:31:52 +02:00
return $"{property} {odataFilterOperator} {value}" ;
2021-05-17 04:23:29 -07:00
}
return "" ;
}
2021-10-07 11:35:29 +03:00
/// <summary>
/// Converts to odatafilterstring.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="columns">The columns.</param>
/// <returns>System.String.</returns>
2026-01-07 12:29:58 +05:00
public static string ToODataFilterString < T > ( this IEnumerable < RadzenDataGridColumn < T > > columns ) where T : notnull
2021-05-17 04:23:29 -07:00
{
2024-02-29 09:53:57 +01:00
var columnsWithFilter = GetFilterableColumns ( columns ) ;
2021-05-17 04:23:29 -07:00
2026-01-07 12:29:58 +05:00
if ( columnsWithFilter . Count > 0 )
2021-05-17 04:23:29 -07:00
{
var gridLogicalFilterOperator = columns . FirstOrDefault ( ) ? . Grid ? . LogicalFilterOperator ;
var gridBooleanOperator = gridLogicalFilterOperator = = LogicalFilterOperator . And ? "and" : "or" ;
var whereList = new List < string > ( ) ;
2024-02-29 09:53:57 +01:00
foreach ( var column in columnsWithFilter )
2021-05-17 04:23:29 -07:00
{
var property = column . GetFilterProperty ( ) . Replace ( '.' , '/' ) ;
2021-11-19 12:36:34 +02:00
var value = column . GetFilterValue ( ) ;
var secondValue = column . GetSecondFilterValue ( ) ;
2021-05-17 04:23:29 -07:00
2024-02-06 13:20:46 +00:00
if ( column . GetFilterOperator ( ) = = FilterOperator . Custom )
{
var customFilterExpression = column . GetCustomFilterExpression ( ) ;
if ( ! string . IsNullOrEmpty ( customFilterExpression ) )
{
whereList . Add ( customFilterExpression ) ;
}
}
else if ( value ! = null | | column . GetFilterOperator ( ) = = FilterOperator . IsNotNull | | column . GetFilterOperator ( ) = = FilterOperator . IsNull
2022-07-07 14:14:30 +02:00
| | column . GetFilterOperator ( ) = = FilterOperator . IsEmpty | | column . GetFilterOperator ( ) = = FilterOperator . IsNotEmpty )
2021-05-17 04:23:29 -07:00
{
2021-05-18 11:39:53 +03:00
var linqOperator = ODataFilterOperators [ column . GetFilterOperator ( ) ] ;
2021-05-17 04:23:29 -07:00
if ( linqOperator = = null )
{
linqOperator = "==" ;
}
var booleanOperator = column . LogicalFilterOperator = = LogicalFilterOperator . And ? "and" : "or" ;
2021-11-19 12:36:34 +02:00
if ( secondValue = = null )
2021-05-17 04:23:29 -07:00
{
2023-04-11 00:50:19 -05:00
whereList . Add ( column . GetColumnODataFilter ( ) ) ;
2021-05-17 04:23:29 -07:00
}
else
2023-04-11 00:50:19 -05:00
{
whereList . Add ( $"({column.GetColumnODataFilter()} {booleanOperator} {column.GetColumnODataFilter(second: true)})" ) ;
2021-05-17 04:23:29 -07:00
}
}
}
return string . Join ( $" {gridBooleanOperator} " , whereList . Where ( i = > ! string . IsNullOrEmpty ( i ) ) ) ;
}
return "" ;
}
2023-06-13 11:32:54 +03:00
/// <summary>
/// Gets if type is IEnumerable.
/// </summary>
2026-01-07 12:29:58 +05:00
public static bool IsEnumerable ( Type ? type )
2022-07-06 14:09:26 +03:00
{
2026-01-07 12:29:58 +05:00
return type ! = null & & ( typeof ( IEnumerable ) . IsAssignableFrom ( type ) | | typeof ( IEnumerable < > ) . IsAssignableFrom ( type ) ) & & type ! = typeof ( string ) ;
2022-07-06 14:09:26 +03:00
}
2021-10-07 11:35:29 +03:00
/// <summary>
/// Wheres the specified columns.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="source">The source.</param>
/// <param name="columns">The columns.</param>
/// <returns>IQueryable<T>.</returns>
2026-01-07 12:29:58 +05:00
public static IQueryable < T > Where < T > ( this IQueryable < T > source , IEnumerable < RadzenDataGridColumn < T > > columns ) where T : notnull
2021-05-17 04:23:29 -07:00
{
2026-01-07 12:29:58 +05:00
ArgumentNullException . ThrowIfNull ( source ) ;
ArgumentNullException . ThrowIfNull ( columns ) ;
2021-05-17 04:23:29 -07:00
Func < RadzenDataGridColumn < T > , bool > canFilter = ( c ) = > c . Filterable & & c . FilterPropertyType ! = null & &
2026-01-07 12:29:58 +05:00
( ! ( c . GetFilterValue ( ) = = null | | ( c . GetFilterValue ( ) as string ) ? . Length = = 0 )
2022-07-07 14:14:30 +02:00
| | c . GetFilterOperator ( ) = = FilterOperator . IsNotNull | | c . GetFilterOperator ( ) = = FilterOperator . IsNull
2025-02-14 14:38:12 +02:00
| | c . GetFilterOperator ( ) = = FilterOperator . IsEmpty | | c . GetFilterOperator ( ) = = FilterOperator . IsNotEmpty )
& & c . GetFilterProperty ( ) ! = null ;
Func < RadzenDataGridColumn < T > , bool > canFilterCustom = ( c ) = > c . Filterable & & c . FilterPropertyType ! = null & &
( c . GetFilterOperator ( ) = = FilterOperator . Custom & & c . GetCustomFilterExpression ( ) ! = null )
2022-07-07 14:14:30 +02:00
& & c . GetFilterProperty ( ) ! = null ;
2021-05-17 04:23:29 -07:00
2026-01-07 12:29:58 +05:00
var columnsToFilter = columns . Where ( canFilter ) . ToList ( ) ;
2025-02-13 13:35:41 +02:00
var grid = columns . FirstOrDefault ( ) ? . Grid ;
2025-02-13 14:53:45 +02:00
var gridLogicalFilterOperator = grid ! = null ? grid . LogicalFilterOperator : LogicalFilterOperator . And ;
var gridFilterCaseSensitivity = grid ! = null ? grid . FilterCaseSensitivity : FilterCaseSensitivity . Default ;
2023-04-11 00:50:19 -05:00
2026-01-07 12:29:58 +05:00
if ( columnsToFilter . Count > 0 )
2025-02-13 13:35:41 +02:00
{
2025-02-14 14:38:12 +02:00
source = source . Where ( columnsToFilter . Select ( c = > new FilterDescriptor ( )
2025-05-07 13:11:30 +03:00
{
Property = c . Property ,
FilterProperty = c . FilterProperty ,
Type = c . FilterPropertyType ,
FilterValue = c . GetFilterValue ( ) ,
FilterOperator = c . GetFilterOperator ( ) ,
SecondFilterValue = c . GetSecondFilterValue ( ) ,
SecondFilterOperator = c . GetSecondFilterOperator ( ) ,
2025-11-04 10:27:11 +02:00
LogicalFilterOperator = c . GetLogicalFilterOperator ( ) ,
2025-11-04 11:38:06 +02:00
CollectionFilterMode = c . GetCollectionFilterMode ( )
2025-05-07 13:11:30 +03:00
} ) , gridLogicalFilterOperator , gridFilterCaseSensitivity ) ;
2021-05-17 04:23:29 -07:00
}
2026-01-07 12:29:58 +05:00
var columnsWithCustomFilter = columns . Where ( canFilterCustom ) . ToList ( ) ;
2025-02-14 14:38:12 +02:00
2026-01-07 12:29:58 +05:00
if ( columnsToFilter . Count > 0 )
2025-02-14 14:38:12 +02:00
{
2026-01-07 12:29:58 +05:00
var expressions = columnsWithCustomFilter . Select ( c = > ( c . GetCustomFilterExpression ( ) ? ? "" ) . Replace ( " or " , " || " , StringComparison . Ordinal ) . Replace ( " and " , " && " , StringComparison . Ordinal ) ) . Where ( e = > ! string . IsNullOrEmpty ( e ) ) . ToList ( ) ;
source = expressions . Count > 0 ?
2025-02-14 14:38:12 +02:00
System . Linq . Dynamic . Core . DynamicExtensions . Where ( source , "it => " + string . Join ( $"{(gridLogicalFilterOperator == LogicalFilterOperator.And ? " & & " : " | | ")}" , expressions ) ) : source ;
}
2021-05-17 04:23:29 -07:00
return source ;
}
2022-09-20 14:59:15 +03:00
/// <summary>
/// Wheres the specified filters.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="source">The source.</param>
/// <param name="dataFilter">The DataFilter.</param>
/// <returns>IQueryable<T>.</returns>
public static IQueryable < T > Where < T > ( this IQueryable < T > source , RadzenDataFilter < T > dataFilter )
{
2026-01-07 12:29:58 +05:00
ArgumentNullException . ThrowIfNull ( source ) ;
ArgumentNullException . ThrowIfNull ( dataFilter ) ;
2022-09-20 14:59:15 +03:00
Func < CompositeFilterDescriptor , bool > canFilter = ( c ) = > dataFilter . properties . Where ( col = > col . Property = = c . Property ) . FirstOrDefault ( ) ? . FilterPropertyType ! = null & &
2026-01-07 12:29:58 +05:00
( ! ( c . FilterValue = = null | | ( c . FilterValue as string ) ? . Length = = 0 )
2022-09-20 14:59:15 +03:00
| | c . FilterOperator = = FilterOperator . IsNotNull | | c . FilterOperator = = FilterOperator . IsNull
| | c . FilterOperator = = FilterOperator . IsEmpty | | c . FilterOperator = = FilterOperator . IsNotEmpty )
& & c . Property ! = null ;
2026-01-07 12:29:58 +05:00
var applicableFilters = dataFilter . Filters . Concat ( dataFilter . Filters . SelectManyRecursive ( i = > i . Filters ? ? Enumerable . Empty < CompositeFilterDescriptor > ( ) ) ) . Where ( canFilter ) . ToList ( ) ;
if ( applicableFilters . Count > 0 )
2022-09-20 14:59:15 +03:00
{
2025-02-13 13:35:41 +02:00
var filterExpressions = new List < Expression > ( ) ;
var parameter = Expression . Parameter ( typeof ( T ) , "x" ) ;
2022-09-20 18:23:15 +03:00
foreach ( var filter in dataFilter . Filters )
2022-09-20 14:59:15 +03:00
{
2025-02-13 13:35:41 +02:00
AddWhereExpression < T > ( parameter , filter , ref filterExpressions , dataFilter . FilterCaseSensitivity ) ;
2022-09-20 14:59:15 +03:00
}
2026-01-07 12:29:58 +05:00
Expression ? combinedExpression = null ;
2025-02-13 13:35:41 +02:00
foreach ( var expression in filterExpressions )
{
combinedExpression = combinedExpression = = null
? expression
: dataFilter . LogicalFilterOperator = = LogicalFilterOperator . And ?
Expression . AndAlso ( combinedExpression , expression ) :
Expression . OrElse ( combinedExpression , expression ) ;
}
if ( combinedExpression ! = null )
{
var lambda = Expression . Lambda < Func < T , bool > > ( combinedExpression , parameter ) ;
return source . Where ( lambda ) ;
}
2022-09-20 14:59:15 +03:00
}
return source ;
}
2025-02-13 13:35:41 +02:00
/// <summary>
/// Wheres the specified filters.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns>IQueryable<T>.</returns>
public static IQueryable < T > Where < T > ( this IQueryable < T > source , IEnumerable < CompositeFilterDescriptor > filters , LogicalFilterOperator logicalFilterOperator , FilterCaseSensitivity filterCaseSensitivity )
2022-09-20 14:59:15 +03:00
{
2026-01-07 12:29:58 +05:00
ArgumentNullException . ThrowIfNull ( source ) ;
ArgumentNullException . ThrowIfNull ( filters ) ;
2025-05-07 13:11:30 +03:00
Func < CompositeFilterDescriptor , bool > canFilter = ( c ) = >
2026-01-07 12:29:58 +05:00
( ! ( c . FilterValue = = null | | ( c . FilterValue as string ) ? . Length = = 0 )
2025-02-13 13:35:41 +02:00
| | c . FilterOperator = = FilterOperator . IsNotNull | | c . FilterOperator = = FilterOperator . IsNull
2025-10-03 11:07:39 +03:00
| | c . FilterOperator = = FilterOperator . IsEmpty | | c . FilterOperator = = FilterOperator . IsNotEmpty
2026-01-07 12:29:58 +05:00
| | ( c . Filters ! = null & & c . Filters . Any ( ) ) )
2025-02-13 13:35:41 +02:00
& & c . Property ! = null ;
2026-01-07 12:29:58 +05:00
var applicableFilters = filters . Where ( canFilter ) . ToList ( ) ;
if ( applicableFilters . Count > 0 )
2022-09-20 14:59:15 +03:00
{
2025-02-13 13:35:41 +02:00
var filterExpressions = new List < Expression > ( ) ;
2022-09-20 18:23:15 +03:00
2025-02-13 13:35:41 +02:00
var parameter = Expression . Parameter ( typeof ( T ) , "x" ) ;
2022-09-20 18:23:15 +03:00
2025-02-13 13:35:41 +02:00
foreach ( var filter in filters )
2022-11-24 15:26:14 +02:00
{
2025-02-13 13:35:41 +02:00
AddWhereExpression < T > ( parameter , filter , ref filterExpressions , filterCaseSensitivity ) ;
2022-11-24 15:26:14 +02:00
}
2026-01-07 12:29:58 +05:00
Expression ? combinedExpression = null ;
2022-09-20 14:59:15 +03:00
2025-02-13 13:35:41 +02:00
foreach ( var expression in filterExpressions )
2022-09-20 14:59:15 +03:00
{
2025-02-13 13:35:41 +02:00
combinedExpression = combinedExpression = = null
? expression
: logicalFilterOperator = = LogicalFilterOperator . And ?
Expression . AndAlso ( combinedExpression , expression ) :
Expression . OrElse ( combinedExpression , expression ) ;
2022-09-20 14:59:15 +03:00
}
2025-02-13 13:35:41 +02:00
if ( combinedExpression ! = null )
2022-09-20 14:59:15 +03:00
{
2025-02-13 13:35:41 +02:00
var lambda = Expression . Lambda < Func < T , bool > > ( combinedExpression , parameter ) ;
return source . Where ( lambda ) ;
2022-09-20 14:59:15 +03:00
}
2025-02-13 13:35:41 +02:00
}
2022-09-20 14:59:15 +03:00
2025-02-13 13:35:41 +02:00
return source ;
}
2022-09-20 14:59:15 +03:00
2025-02-13 13:35:41 +02:00
private static void AddWhereExpression < T > ( ParameterExpression parameter , CompositeFilterDescriptor filter , ref List < Expression > filterExpressions , FilterCaseSensitivity filterCaseSensitivity )
{
if ( filter . Filters ! = null )
{
var innerFilterExpressions = new List < Expression > ( ) ;
2022-09-20 14:59:15 +03:00
2025-02-13 13:35:41 +02:00
foreach ( var f in filter . Filters )
2022-09-20 14:59:15 +03:00
{
2025-02-13 13:35:41 +02:00
AddWhereExpression < T > ( parameter , f , ref innerFilterExpressions , filterCaseSensitivity ) ;
2022-09-20 14:59:15 +03:00
}
2025-02-13 13:35:41 +02:00
2026-01-07 12:29:58 +05:00
if ( innerFilterExpressions . Count > 0 )
2022-09-20 14:59:15 +03:00
{
2026-01-07 12:29:58 +05:00
Expression ? combinedExpression = null ;
2022-09-26 21:47:24 +03:00
2025-02-13 13:35:41 +02:00
foreach ( var expression in innerFilterExpressions )
2022-09-20 14:59:15 +03:00
{
2025-02-13 13:35:41 +02:00
combinedExpression = combinedExpression = = null
? expression
: filter . LogicalFilterOperator = = LogicalFilterOperator . And ?
Expression . AndAlso ( combinedExpression , expression ) :
Expression . OrElse ( combinedExpression , expression ) ;
2022-09-20 14:59:15 +03:00
}
2025-02-13 13:35:41 +02:00
if ( combinedExpression ! = null )
2024-11-18 10:28:53 +02:00
{
2025-02-13 13:35:41 +02:00
filterExpressions . Add ( combinedExpression ) ;
2024-11-18 10:28:53 +02:00
}
}
2025-02-13 13:35:41 +02:00
}
else
{
if ( filter . Property = = null | | filter . FilterOperator = = null | | ( filter . FilterValue = = null & &
filter . FilterOperator ! = FilterOperator . IsNull & & filter . FilterOperator ! = FilterOperator . IsNotNull & &
filter . FilterOperator ! = FilterOperator . IsEmpty & & filter . FilterOperator ! = FilterOperator . IsNotEmpty ) )
2024-11-18 10:28:53 +02:00
{
2025-02-13 13:35:41 +02:00
return ;
2024-01-05 15:18:22 +02:00
}
2023-02-22 15:53:11 +02:00
2025-02-13 13:35:41 +02:00
var f = new FilterDescriptor ( )
{
Property = filter . Property ,
FilterProperty = filter . FilterProperty ,
FilterValue = filter . FilterValue ,
2026-01-07 12:29:58 +05:00
FilterOperator = filter . FilterOperator . HasValue ? filter . FilterOperator . Value : default ( FilterOperator ) ,
2025-02-13 13:35:41 +02:00
LogicalFilterOperator = filter . LogicalFilterOperator ,
Type = filter . Type
} ;
2023-02-22 15:53:11 +02:00
2026-01-07 12:29:58 +05:00
var expression = GetExpression < T > ( parameter , f , filterCaseSensitivity , f . Type ? ? typeof ( object ) ) ;
2025-02-13 13:35:41 +02:00
if ( expression ! = null )
{
filterExpressions . Add ( expression ) ;
2022-09-20 14:59:15 +03:00
}
}
}
2024-07-18 13:32:08 +03:00
/// <summary>
/// Wheres the specified filters.
/// </summary>
/// <param name="source">The source.</param>
/// <param name="property">The property.</param>
/// <param name="value">The value.</param>
/// <param name="op">The StringFilterOperator.</param>
/// <param name="cs">The FilterCaseSensitivity.</param>
/// <returns>IQueryable<T>.</returns>
2026-01-07 12:29:58 +05:00
public static IQueryable Where ( this IQueryable source , string property , string? value , StringFilterOperator op , FilterCaseSensitivity cs )
2024-07-18 13:32:08 +03:00
{
2026-01-07 12:29:58 +05:00
ArgumentNullException . ThrowIfNull ( source ) ;
property ? ? = string . Empty ;
value ? ? = string . Empty ;
2025-03-01 12:18:35 +02:00
IQueryable result ;
if ( ! string . IsNullOrEmpty ( value ) )
2024-07-18 13:32:08 +03:00
{
2025-03-01 12:18:35 +02:00
var ignoreCase = cs = = FilterCaseSensitivity . CaseInsensitive ;
var parameter = Expression . Parameter ( source . ElementType , "it" ) ;
var inMemory = typeof ( EnumerableQuery ) . IsAssignableFrom ( source . GetType ( ) ) ;
Expression propertyExpression = parameter ;
if ( ! string . IsNullOrEmpty ( property ) )
{
propertyExpression = GetNestedPropertyExpression ( parameter , property ) ;
}
if ( string . IsNullOrEmpty ( property ) & & inMemory | |
propertyExpression ! = null & & propertyExpression . Type ! = typeof ( string ) )
{
2025-05-22 09:46:06 +03:00
propertyExpression = Expression . Call ( string . IsNullOrEmpty ( property ) & & inMemory ? notNullCheck ( parameter ) : notNullCheck ( propertyExpression ) , "ToString" , Type . EmptyTypes ) ;
2025-03-01 12:18:35 +02:00
}
2026-01-07 12:29:58 +05:00
if ( ignoreCase & & propertyExpression ! = null )
2025-03-01 12:18:35 +02:00
{
2026-01-07 12:29:58 +05:00
propertyExpression = Expression . Call ( notNullCheck ( propertyExpression ! ) , "ToLower" , Type . EmptyTypes ) ;
2025-03-01 12:18:35 +02:00
}
2026-01-07 12:29:58 +05:00
var constantExpression = Expression . Constant ( ignoreCase ? value . ToLower ( CultureInfo . InvariantCulture ) : value , typeof ( string ) ) ;
Expression ? comparisonExpression = null ;
2025-03-01 12:18:35 +02:00
2026-01-07 12:29:58 +05:00
if ( propertyExpression ! = null )
2025-03-01 12:18:35 +02:00
{
2026-01-07 12:29:58 +05:00
switch ( op )
{
case StringFilterOperator . Contains :
comparisonExpression = Expression . Call ( notNullCheck ( propertyExpression ) , "Contains" , null , constantExpression ) ;
break ;
case StringFilterOperator . StartsWith :
comparisonExpression = Expression . Call ( notNullCheck ( propertyExpression ) , "StartsWith" , null , constantExpression ) ;
break ;
case StringFilterOperator . EndsWith :
comparisonExpression = Expression . Call ( notNullCheck ( propertyExpression ) , "EndsWith" , null , constantExpression ) ;
break ;
default :
comparisonExpression = Expression . Equal ( propertyExpression , constantExpression ) ;
break ;
}
2025-03-01 12:18:35 +02:00
}
2026-01-07 12:29:58 +05:00
var lambda = Expression . Lambda ( comparisonExpression ! , parameter ) ;
2025-03-01 12:18:35 +02:00
result = source . Provider . CreateQuery ( Expression . Call (
typeof ( Queryable ) ,
"Where" ,
new Type [ ] { source . ElementType } ,
source . Expression ,
lambda
) ) ;
}
else
{
result = source ;
2024-07-18 13:32:08 +03:00
}
2025-03-01 12:18:35 +02:00
return result ;
2024-07-18 13:32:08 +03:00
}
2023-02-01 14:23:06 +02:00
/// <summary>
/// Converts to OData filter expression.
/// </summary>
/// <param name="dataFilter">The DataFilter.</param>
/// <returns>System.String.</returns>
public static string ToODataFilterString < T > ( this RadzenDataFilter < T > dataFilter )
{
2026-01-07 12:29:58 +05:00
ArgumentNullException . ThrowIfNull ( dataFilter ) ;
2023-02-01 14:23:06 +02:00
Func < CompositeFilterDescriptor , bool > canFilter = ( c ) = > dataFilter . properties . Where ( col = > col . Property = = c . Property ) . FirstOrDefault ( ) ? . FilterPropertyType ! = null & &
2026-01-16 09:33:21 +02:00
( ! ( c . FilterValue = = null | | ( c . FilterValue as string ) ? . Length = = 0 )
2023-02-01 14:23:06 +02:00
| | c . FilterOperator = = FilterOperator . IsNotNull | | c . FilterOperator = = FilterOperator . IsNull
| | c . FilterOperator = = FilterOperator . IsEmpty | | c . FilterOperator = = FilterOperator . IsNotEmpty )
& & c . Property ! = null ;
2026-01-07 12:29:58 +05:00
var applicableFilters = dataFilter . Filters . Concat ( dataFilter . Filters . SelectManyRecursive ( i = > i . Filters ? ? Enumerable . Empty < CompositeFilterDescriptor > ( ) ) ) . Where ( canFilter ) . ToList ( ) ;
if ( applicableFilters . Count > 0 )
2023-02-01 14:23:06 +02:00
{
var filterExpressions = new List < string > ( ) ;
foreach ( var filter in dataFilter . Filters )
{
2025-05-20 11:46:24 +03:00
var ft = dataFilter . properties . Where ( col = > col . Property = = filter . Property ) . FirstOrDefault ( ) ? . FilterPropertyType ;
if ( ft ! = null & & ft ! = filter . Type )
{
filter . Type = ft ;
}
AddODataExpression < T > ( canFilter , filter , ref filterExpressions , dataFilter . LogicalFilterOperator , dataFilter . FilterCaseSensitivity ) ;
2023-02-01 14:23:06 +02:00
}
2026-01-07 12:29:58 +05:00
return filterExpressions . Count > 0 ?
string . Join ( $" {dataFilter.LogicalFilterOperator.ToString().ToLower(CultureInfo.InvariantCulture)} " , filterExpressions )
2023-02-01 14:23:06 +02:00
: "" ;
}
return "" ;
}
2025-05-20 11:46:24 +03:00
/// <summary>
/// Converts a enumerable of CompositeFilterDescriptor to a OData-compatibly filter string
/// </summary>
/// <param name="filters">The enumerable of CompositeFilterDescriptor </param>
/// <param name="logicalFilterOperator">The LogicalFilterOperator</param>
/// <param name="filterCaseSensitivity">The FilterCaseSensitivity</param>
/// <returns>A OData-compatible filter string</returns>
public static string ToODataFilterString < T > ( this IEnumerable < CompositeFilterDescriptor > filters ,
LogicalFilterOperator logicalFilterOperator = LogicalFilterOperator . And ,
FilterCaseSensitivity filterCaseSensitivity = FilterCaseSensitivity . Default )
{
2026-01-07 12:29:58 +05:00
ArgumentNullException . ThrowIfNull ( filters ) ;
2026-01-16 09:33:21 +02:00
Func < CompositeFilterDescriptor , bool > canFilter = ( c ) = >
( ! ( c . FilterValue = = null | | ( c . FilterValue as string ) ? . Length = = 0 )
2025-05-20 11:46:24 +03:00
| | c . FilterOperator = = FilterOperator . IsNotNull | | c . FilterOperator = = FilterOperator . IsNull
| | c . FilterOperator = = FilterOperator . IsEmpty | | c . FilterOperator = = FilterOperator . IsNotEmpty )
& & c . Property ! = null ;
2026-01-07 12:29:58 +05:00
var applicableFilters = filters . Concat ( filters . SelectManyRecursive ( i = > i . Filters ? ? Enumerable . Empty < CompositeFilterDescriptor > ( ) ) ) . Where ( canFilter ) . ToList ( ) ;
if ( applicableFilters . Count > 0 )
2025-05-20 11:46:24 +03:00
{
var filterExpressions = new List < string > ( ) ;
foreach ( var filter in filters )
{
AddODataExpression < T > ( canFilter , filter , ref filterExpressions , logicalFilterOperator , filterCaseSensitivity ) ;
}
2026-01-07 12:29:58 +05:00
return filterExpressions . Count > 0 ?
string . Join ( $" {logicalFilterOperator.ToString().ToLower(CultureInfo.InvariantCulture)} " , filterExpressions )
2025-05-20 11:46:24 +03:00
: "" ;
}
return "" ;
}
private static void AddODataExpression < T > ( Func < CompositeFilterDescriptor , bool > canFilter ,
CompositeFilterDescriptor filter , ref List < string > filterExpressions ,
LogicalFilterOperator logicalFilterOperator = LogicalFilterOperator . And ,
FilterCaseSensitivity filterCaseSensitivity = FilterCaseSensitivity . Default )
2023-02-01 14:23:06 +02:00
{
if ( filter . Filters ! = null )
{
var innerFilterExpressions = new List < string > ( ) ;
foreach ( var f in filter . Filters )
{
2025-05-20 11:46:24 +03:00
AddODataExpression < T > ( canFilter , f , ref innerFilterExpressions , logicalFilterOperator , filterCaseSensitivity ) ;
2023-02-01 14:23:06 +02:00
}
2026-01-07 12:29:58 +05:00
if ( innerFilterExpressions . Count > 0 )
2023-02-01 14:23:06 +02:00
{
2026-01-07 12:29:58 +05:00
filterExpressions . Add ( "(" + string . Join ( $" {filter.LogicalFilterOperator.ToString().ToLower(CultureInfo.InvariantCulture)} " , innerFilterExpressions ) + ")" ) ;
2023-02-01 14:23:06 +02:00
}
}
else
{
2024-02-07 02:32:04 -05:00
if ( filter . Property = = null | | filter . FilterOperator = = null | | ( filter . FilterValue = = null & &
2023-02-01 14:23:06 +02:00
filter . FilterOperator ! = FilterOperator . IsNull & & filter . FilterOperator ! = FilterOperator . IsNotNull ) )
{
return ;
}
var property = filter . Property . Replace ( '.' , '/' ) ;
2023-04-11 00:50:19 -05:00
2025-05-20 11:46:24 +03:00
var parameter = Expression . Parameter ( typeof ( T ) , "x" ) ;
var propertyExpression = GetNestedPropertyExpression ( parameter , filter . Property ) ;
if ( propertyExpression = = null ) return ;
var filterPropertyType = filter . Type ? ? propertyExpression . Type ;
2023-02-01 14:23:06 +02:00
2025-05-20 11:46:24 +03:00
if ( filterCaseSensitivity = = FilterCaseSensitivity . CaseInsensitive & & filterPropertyType = = typeof ( string ) )
2023-02-01 14:23:06 +02:00
{
property = $"tolower({property})" ;
}
if ( filter . FilterOperator = = FilterOperator . StartsWith | | filter . FilterOperator = = FilterOperator . EndsWith
| | filter . FilterOperator = = FilterOperator . Contains | | filter . FilterOperator = = FilterOperator . DoesNotContain )
{
2025-05-20 11:46:24 +03:00
if ( IsEnumerable ( filterPropertyType ) & & filterPropertyType ! = typeof ( string ) & &
2023-02-01 14:23:06 +02:00
( filter . FilterOperator = = FilterOperator . Contains | | filter . FilterOperator = = FilterOperator . DoesNotContain ) )
{
var enumerableValue = ( ( IEnumerable ) ( filter . FilterValue ! = null ? filter . FilterValue : Enumerable . Empty < object > ( ) ) ) . AsQueryable ( ) ;
2025-02-13 13:35:41 +02:00
var firstItemType = enumerableValue . Cast < object > ( ) . Any ( ) ? enumerableValue . FirstOrDefault ( ) . GetType ( ) : typeof ( object ) ;
2023-02-01 14:23:06 +02:00
var enumerableValueAsString = "(" + String . Join ( "," ,
2023-09-18 09:11:46 +03:00
( enumerableValue . ElementType = = typeof ( string ) | | firstItemType = = typeof ( string ) ? enumerableValue . Cast < string > ( ) . Select ( i = > $@"'{i}'" ) . Cast < object > ( ) : enumerableValue . Cast < object > ( ) ) ) + ")" ;
2023-02-01 14:23:06 +02:00
2025-02-13 13:35:41 +02:00
if ( enumerableValue . Cast < object > ( ) . Any ( ) & & filter . FilterOperator = = FilterOperator . Contains )
2023-02-01 14:23:06 +02:00
{
filterExpressions . Add ( $"{property} in {enumerableValueAsString}" ) ;
}
2025-02-13 13:35:41 +02:00
else if ( enumerableValue . Cast < object > ( ) . Any ( ) & & filter . FilterOperator = = FilterOperator . DoesNotContain )
2023-02-01 14:23:06 +02:00
{
filterExpressions . Add ( $"not({property} in {enumerableValueAsString})" ) ;
}
}
else
{
2025-05-20 11:46:24 +03:00
var expression = filterCaseSensitivity = = FilterCaseSensitivity . CaseInsensitive ?
2024-02-07 02:32:04 -05:00
$"{ODataFilterOperators[filter.FilterOperator.Value]}({property}, tolower('{filter.FilterValue}'))" :
$"{ODataFilterOperators[filter.FilterOperator.Value]}({property}, '{filter.FilterValue}')" ;
2023-02-01 14:23:06 +02:00
if ( filter . FilterOperator = = FilterOperator . DoesNotContain )
{
expression = $"not({expression})" ;
}
filterExpressions . Add ( expression ) ;
}
}
2023-04-11 00:50:19 -05:00
else
2023-02-01 14:23:06 +02:00
{
2025-05-20 11:46:24 +03:00
if ( IsEnumerable ( filterPropertyType ) & & filterPropertyType ! = typeof ( string ) )
2023-02-01 14:23:06 +02:00
return ;
var value = $"{filter.FilterValue}" ;
if ( filter . FilterOperator = = FilterOperator . IsNull | | filter . FilterOperator = = FilterOperator . IsNotNull )
{
value = $"null" ;
}
else if ( filter . FilterOperator = = FilterOperator . IsEmpty | | filter . FilterOperator = = FilterOperator . IsNotEmpty )
{
value = $"''" ;
}
2025-05-20 11:46:24 +03:00
else if ( filterPropertyType = = typeof ( string ) | | PropertyAccess . IsEnum ( filterPropertyType ) | | PropertyAccess . IsNullableEnum ( filterPropertyType ) )
2023-02-01 14:23:06 +02:00
{
value = $"'{value}'" ;
}
2025-05-20 11:46:24 +03:00
else if ( filterPropertyType = = typeof ( DateTime ) | | filterPropertyType = = typeof ( DateTime ? ) )
2023-02-01 14:23:06 +02:00
{
2023-07-25 08:49:42 +03:00
try
{
2026-01-07 12:29:58 +05:00
value = Convert . ToDateTime ( filter . FilterValue , CultureInfo . InvariantCulture ) . ToString ( CultureInfo . InvariantCulture ) ;
2023-07-25 08:49:42 +03:00
}
catch
{
//
}
2023-09-12 08:42:47 +03:00
value = $"{DateTime.Parse(value, CultureInfo.InvariantCulture, System.Globalization.DateTimeStyles.RoundtripKind).ToString(" yyyy - MM - ddTHH : mm : ss . fffZ ", CultureInfo.InvariantCulture)}" ;
2023-02-01 14:23:06 +02:00
}
2025-05-20 11:46:24 +03:00
else if ( filterPropertyType = = typeof ( bool ) | | filterPropertyType = = typeof ( bool? ) )
2023-02-01 14:23:06 +02:00
{
2026-01-07 12:29:58 +05:00
value = value . ToLower ( CultureInfo . InvariantCulture ) ;
2023-02-01 14:23:06 +02:00
}
2024-02-07 02:32:04 -05:00
filterExpressions . Add ( $@"{property} {ODataFilterOperators[filter.FilterOperator.Value]} {value}" ) ;
2023-02-01 14:23:06 +02:00
}
}
}
2021-10-07 11:35:29 +03:00
/// <summary>
/// Ases the o data enumerable.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="source">The source.</param>
/// <returns>ODataEnumerable<T>.</returns>
2021-01-19 11:02:49 +02:00
public static ODataEnumerable < T > AsODataEnumerable < T > ( this IEnumerable < T > source )
{
return new ODataEnumerable < T > ( source ) ;
}
2021-06-21 15:49:08 +03:00
2021-10-07 11:35:29 +03:00
/// <summary>
/// Selects the many recursive.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="source">The source.</param>
/// <param name="selector">The selector.</param>
/// <returns>IEnumerable<T>.</returns>
2021-06-21 15:49:08 +03:00
public static IEnumerable < T > SelectManyRecursive < T > ( this IEnumerable < T > source , Func < T , IEnumerable < T > > selector )
{
var result = source . SelectMany ( selector ) ;
if ( ! result . Any ( ) )
{
return result ;
}
return result . Concat ( result . SelectManyRecursive ( selector ) ) ;
}
2024-02-29 09:53:57 +01:00
2026-01-07 12:29:58 +05:00
private static List < RadzenDataGridColumn < T > > GetFilterableColumns < T > ( IEnumerable < RadzenDataGridColumn < T > > columns ) where T : notnull
2024-02-29 09:53:57 +01:00
{
return columns
. Where ( c = > c . Filterable
& & c . FilterPropertyType ! = null
2026-01-21 17:31:33 +02:00
& & ( ! ( c . GetFilterValue ( ) = = null | | ( c . GetFilterValue ( ) as string ) ? . Length = = 0 )
2024-04-08 10:08:49 +03:00
| | ! c . CanSetFilterValue ( )
2024-02-29 09:53:57 +01:00
| | c . HasCustomFilter ( ) )
& & c . GetFilterProperty ( ) ! = null )
. ToList ( ) ;
}
2021-01-19 11:02:49 +02:00
}
2025-05-07 13:11:30 +03:00
}