Пример #1
0
        /// <summary>
        /// A more flexible filtering method than Filter(). Filter() will always return materialized items.
        /// </summary>
        public IEnumerable <TEntityInterface> FilterOrQuery(IEnumerable <TEntityInterface> items, object parameter, Type parameterType)
        {
            bool preferQuery = items is IQueryable;

            // If exists use Filter(IQueryable, TParameter) or Filter(IEnumerable, TParameter)
            {
                ReadingOption enumerableFilter = () =>
                {
                    var reader = Reflection.RepositoryEnumerableFilterMethod(parameterType);
                    if (reader == null)
                    {
                        return(null);
                    }
                    return(() =>
                    {
                        _logger.Trace(() => "Filtering using enumerable Filter(items, " + reader.GetParameters()[1].ParameterType.FullName + ")");
                        Reflection.MaterializeEntityList(ref items);
                        return (IEnumerable <TEntityInterface>)reader.InvokeEx(_repository.Value, items, parameter);
                    });
                };

                ReadingOption queryableFilter = () =>
                {
                    var reader = Reflection.RepositoryQueryableFilterMethod(parameterType);
                    if (reader == null)
                    {
                        return(null);
                    }
                    return(() =>
                    {
                        _logger.Trace(() => "Filtering using queryable Filter(items, " + reader.GetParameters()[1].ParameterType.FullName + ")");
                        var query = Reflection.AsQueryable(items);
                        return (IEnumerable <TEntityInterface>)reader.InvokeEx(_repository.Value, query, parameter);
                    });
                };

                ReadingOptions options;
                if (!preferQuery)
                {
                    options = new ReadingOptions {
                        enumerableFilter, queryableFilter
                    }
                }
                ;
                else
                {
                    options = new ReadingOptions {
                        queryableFilter, enumerableFilter
                    }
                };

                var readingMethod = options.FirstOptionOrNull();
                if (readingMethod != null)
                {
                    return(readingMethod());
                }
            }

            // If the parameter is FilterAll, unless explicitly implemented above, return all
            if (typeof(FilterAll).IsAssignableFrom(parameterType))
            {
                _logger.Trace(() => "Filtering all items returned.");
                return(items);
            }

            // If the parameter is a generic filter, unless explicitly implemented above, execute it
            if (parameterType == typeof(FilterCriteria))
            {
                _logger.Trace(() => "Filtering using generic filter");
                return(ExecuteGenericFilter(new[] { (FilterCriteria)parameter }, preferQuery, items));
            }
            if (typeof(IEnumerable <FilterCriteria>).IsAssignableFrom(parameterType))
            {
                _logger.Trace(() => "Filtering using generic filters");
                return(ExecuteGenericFilter((IEnumerable <FilterCriteria>)parameter, preferQuery, items));
            }

            // If the parameter is a generic property filter, unless explicitly implemented above, use queryable items.Where(property filter)
            if (typeof(IEnumerable <PropertyFilter>).IsAssignableFrom(parameterType))
            {
                _logger.Trace(() => "Reading using items.AsQueryable().Where(property filter");

                // The filterExpression must use EntityType or EntityNavigationType, depending on the provided query.
                var itemType = items.GetType().GetInterface("IEnumerable`1").GetGenericArguments()[0];

                var filterExpression = _genericFilterHelper.ToExpression((IEnumerable <PropertyFilter>)parameter, itemType);
                if (Reflection.IsQueryable(items))
                {
                    var query = Reflection.AsQueryable(items);
                    return(Reflection.Where(query, filterExpression));
                }
                else
                {
                    return(Reflection.Where(items, filterExpression.Compile()));
                }
            }

            // If the parameter is a filter expression, unless explicitly implemented above, use queryable items.Where(parameter)
            if (Reflection.IsPredicateExpression(parameterType))
            {
                _logger.Trace(() => "Filtering using items.AsQueryable().Where(" + parameterType.Name + ")");
                var query = Reflection.AsQueryable(items);
                return(Reflection.Where(query, (Expression)parameter));
            }

            // If the parameter is a filter function, unless explicitly implemented above, use materialized items.Where(parameter)
            if (typeof(Func <TEntityInterface, bool>).IsAssignableFrom(parameterType))
            {
                _logger.Trace(() => "Filtering using items.Where(" + parameterType.Name + ")");
                var filterFunction = parameter as Func <TEntityInterface, bool>;
                Reflection.MaterializeEntityList(ref items);
                if (filterFunction != null)
                {
                    return(items.Where(filterFunction));
                }
            }

            // If the parameter is a IEnumarable<Guid>, it will be interpreted as filter by IDs.
            if (typeof(IEnumerable <Guid>).IsAssignableFrom(parameterType))
            {
                _logger.Trace(() => "Filtering using items.Where(item => guids.Contains(item.ID))");
                if (!(parameter is List <Guid>))
                {
                    parameter = ((IEnumerable <Guid>)parameter).ToList();
                }

                if (items is IQueryable <TEntityInterface> ) // Use queryable Where function with bool expression instead of bool function.
                {
                    // The query is built by reflection to avoid an obscure problem with complex query in NHibernate:
                    // using generic parameter TEntityInterface or IEntity for a query parameter fails with exception on some complex scenarios.
                    var filterPredicateParameter = Expression.Parameter(Reflection.EntityType, "item");
                    var filterPredicate          = Expression.Lambda(
                        Expression.Call(
                            Expression.Constant(parameter),
                            typeof(List <Guid>).GetMethod("Contains"),
                            new[] { Expression.Property(filterPredicateParameter, "ID") }),
                        filterPredicateParameter);

                    return(Reflection.Where((IQueryable <TEntityInterface>)items, EFExpression.OptimizeContains(filterPredicate)));
                }

                return(items.Where(item => ((List <Guid>)parameter).Contains(item.ID)));
            }

            string errorMessage = $"{EntityName} does not implement a filter with parameter {parameterType.FullName}.";

            if (Reflection.RepositoryLoadWithParameterMethod(parameterType) != null)
            {
                errorMessage += " There is a loader method with this parameter implemented: Try reordering filters to use the " + parameterType.Name + " first.";
                throw new ClientException(errorMessage);
            }
            else
            {
                throw new FrameworkException(errorMessage);
            }
        }
        public LambdaExpression ToExpression(IEnumerable <PropertyFilter> propertyFilters, Type parameterType)
        {
            ParameterExpression parameter = Expression.Parameter(parameterType, "p");

            if (propertyFilters == null || !propertyFilters.Any())
            {
                return(Expression.Lambda(Expression.Constant(true), parameter));
            }

            Expression resultCondition = null;

            foreach (var filter in propertyFilters)
            {
                if (string.IsNullOrEmpty(filter.Property))
                {
                    continue;
                }

                Expression memberAccess = null;
                foreach (var property in filter.Property.Split('.'))
                {
                    var parentExpression = memberAccess ?? (Expression)parameter;
                    if (parentExpression.Type.GetProperty(property) == null)
                    {
                        throw new ClientException("Invalid generic filter parameter: Type '" + parentExpression.Type.FullName + "' does not have property '" + property + "'.");
                    }
                    memberAccess = Expression.Property(parentExpression, property);
                }

                // Change the type of the parameter 'value'. It is necessary for comparisons.

                bool propertyIsNullableValueType = memberAccess.Type.IsGenericType && memberAccess.Type.GetGenericTypeDefinition() == typeof(Nullable <>);
                Type propertyBasicType           = propertyIsNullableValueType ? memberAccess.Type.GetGenericArguments().Single() : memberAccess.Type;

                ConstantExpression constant;
                // Operations 'equal' and 'notequal' are supported for backward compatibility.
                if (new[] { "equals", "equal", "notequals", "notequal", "greater", "greaterequal", "less", "lessequal" }.Contains(filter.Operation, StringComparer.OrdinalIgnoreCase))
                {
                    // Constant value should be of same type as the member it is compared to.
                    object convertedValue;
                    if (filter.Value == null || propertyBasicType.IsInstanceOfType(filter.Value))
                    {
                        convertedValue = filter.Value;
                    }

                    // Guid object's type was not automatically recognized when deserializing from JSON:
                    else if (propertyBasicType == typeof(Guid) && filter.Value is string)
                    {
                        convertedValue = Guid.Parse(filter.Value.ToString());
                    }

                    // DateTime object's type was not automatically recognized when deserializing from JSON:
                    else if (propertyBasicType == typeof(DateTime) && filter.Value is string)
                    {
                        convertedValue = ParseJsonDateTime((string)filter.Value, filter.Property, propertyBasicType);
                    }

                    else if ((propertyBasicType == typeof(decimal) || propertyBasicType == typeof(int)) && filter.Value is string)
                    {
                        throw new FrameworkException($"Invalid JSON format of {propertyBasicType.Name} property '{filter.Property}'. Numeric value should not be passed as a string in JSON serialized object.");
                    }
                    else
                    {
                        convertedValue = Convert.ChangeType(filter.Value, propertyBasicType);
                    }

                    if (convertedValue == null && memberAccess.Type.IsValueType && !propertyIsNullableValueType)
                    {
                        Type nullableMemberType = typeof(Nullable <>).MakeGenericType(memberAccess.Type);
                        memberAccess = Expression.Convert(memberAccess, nullableMemberType);
                    }

                    constant = Expression.Constant(convertedValue, memberAccess.Type);
                }
                else if (new[] { "startswith", "endswith", "contains", "notcontains" }.Contains(filter.Operation, StringComparer.OrdinalIgnoreCase))
                {
                    // Constant value should be string.
                    constant = Expression.Constant(filter.Value.ToString(), typeof(string));
                }
                else if (new[] { "datein", "datenotin" }.Contains(filter.Operation, StringComparer.OrdinalIgnoreCase))
                {
                    constant = null;
                }
                else if (new[] { "in", "notin" }.Contains(filter.Operation, StringComparer.OrdinalIgnoreCase))
                {
                    if (filter.Value == null)
                    {
                        throw new ClientException($"Invalid generic filter parameter for operation '{filter.Operation}' on {propertyBasicType.Name} property '{filter.Property}'."
                                                  + $" The provided value is null, instead of an Array.");
                    }

                    // The list element should be of same type as the member it is compared to.
                    var parameterMismatchInfo = new Lazy <string>(() =>
                                                                  $"Invalid generic filter parameter for operation '{filter.Operation}' on {propertyBasicType.Name} property '{filter.Property}'."
                                                                  + $" The provided value type is '{filter.Value.GetType()}', instead of an Array of {propertyBasicType.Name}.");

                    if (!(filter.Value is IEnumerable))
                    {
                        throw new ClientException(parameterMismatchInfo.Value);
                    }

                    var list = (IEnumerable)filter.Value;

                    // Guid object's type was not automatically recognized when deserializing from JSON:
                    if (propertyBasicType == typeof(Guid) && list is IEnumerable <string> )
                    {
                        list = ((IEnumerable <string>)list).Select(s => !string.IsNullOrEmpty(s) ? (Guid?)Guid.Parse(s) : null).ToList();
                    }

                    // DateTime object's type was not automatically recognized when deserializing from JSON:
                    if (propertyBasicType == typeof(DateTime) && list is IEnumerable <string> )
                    {
                        list = ((IEnumerable <string>)list).Select(s => ParseJsonDateTime(s, filter.Property, propertyBasicType)).ToList();
                    }

                    // Adjust the list element type to exactly match the property type:
                    if (GetElementType(list) != memberAccess.Type)
                    {
                        if (list is IList && ((IList)list).Count == 0)
                        {
                            list = EmptyList(memberAccess.Type);
                        }
                        else if (propertyBasicType == typeof(Guid))
                        {
                            AdjustListTypeNullable <Guid>(ref list, propertyIsNullableValueType);
                        }
                        else if (propertyBasicType == typeof(DateTime))
                        {
                            AdjustListTypeNullable <DateTime>(ref list, propertyIsNullableValueType);
                        }
                        else if (propertyBasicType == typeof(int))
                        {
                            AdjustListTypeNullable <int>(ref list, propertyIsNullableValueType);
                        }
                        else if (propertyBasicType == typeof(decimal))
                        {
                            AdjustListTypeNullable <decimal>(ref list, propertyIsNullableValueType);
                        }

                        if (!(GetElementType(list).IsAssignableFrom(memberAccess.Type)))
                        {
                            throw new ClientException(parameterMismatchInfo.Value);
                        }
                    }

                    constant = Expression.Constant(list, list.GetType());
                }
                else
                {
                    throw new ClientException($"Unsupported generic filter operation '{filter.Operation}' on a property.");
                }

                Expression expression;
                switch (filter.Operation.ToLower())
                {
                case "equals":
                case "equal":
                    if (propertyBasicType == typeof(Guid) && constant.Value is Guid constantIdEquals)
                    {
                        // Using a different expression instead of the constant, to force Entity Framework to
                        // use query parameter instead of hardcoding the constant value (literal) into the generated query.
                        // Query with parameter will allow cache reuse for both EF LINQ compiler and database SQL compiler.
                        if (memberAccess.Type == typeof(Guid?))
                        {
                            Expression <Func <Guid?> > idLambda = () => constantIdEquals;
                            expression = Expression.Equal(memberAccess, idLambda.Body);
                        }
                        else
                        {
                            Expression <Func <Guid> > idLambda = () => constantIdEquals;
                            expression = Expression.Equal(memberAccess, idLambda.Body);
                        }
                    }
                    else if (propertyBasicType == typeof(string) && constant.Value != null)
                    {
                        expression = Expression.Call(typeof(DatabaseExtensionFunctions).GetMethod("EqualsCaseInsensitive"), memberAccess, constant);
                    }
                    else
                    {
                        expression = Expression.Equal(memberAccess, constant);
                    }
                    break;

                case "notequals":
                case "notequal":
                    if (propertyBasicType == typeof(Guid) && constant.Value is Guid constantIdNotEquals)
                    {
                        // Using a different expression instead of the constant, to force Entity Framework to
                        // use query parameter instead of hardcoding the constant value (literal) into the generated query.
                        // Query with parameter will allow cache reuse for both EF LINQ compiler and database SQL compiler.
                        Expression <Func <object> > idLambda = () => constantIdNotEquals;
                        expression = Expression.NotEqual(memberAccess, Expression.Convert(idLambda.Body, memberAccess.Type));
                    }
                    else if (propertyBasicType == typeof(string) && constant.Value != null)
                    {
                        expression = Expression.Call(typeof(DatabaseExtensionFunctions).GetMethod("NotEqualsCaseInsensitive"), memberAccess, constant);
                    }
                    else
                    {
                        expression = Expression.NotEqual(memberAccess, constant);
                    }
                    break;

                case "greater":
                    if (propertyBasicType == typeof(string))
                    {
                        expression = Expression.Call(typeof(DatabaseExtensionFunctions).GetMethod("IsGreaterThen"), memberAccess, constant);
                    }
                    else if (propertyBasicType == typeof(Guid))
                    {
                        expression = Expression.Call(typeof(DatabaseExtensionFunctions).GetMethod("GuidIsGreaterThan"), memberAccess, constant);
                    }
                    else
                    {
                        expression = Expression.GreaterThan(memberAccess, constant);
                    }
                    break;

                case "greaterequal":
                    if (propertyBasicType == typeof(string))
                    {
                        expression = Expression.Call(typeof(DatabaseExtensionFunctions).GetMethod("IsGreaterThenOrEqual"), memberAccess, constant);
                    }
                    else if (propertyBasicType == typeof(Guid))
                    {
                        expression = Expression.Call(typeof(DatabaseExtensionFunctions).GetMethod("GuidIsGreaterThanOrEqual"), memberAccess, constant);
                    }
                    else
                    {
                        expression = Expression.GreaterThanOrEqual(memberAccess, constant);
                    }
                    break;

                case "less":
                    if (propertyBasicType == typeof(string))
                    {
                        expression = Expression.Call(typeof(DatabaseExtensionFunctions).GetMethod("IsLessThen"), memberAccess, constant);
                    }
                    else if (propertyBasicType == typeof(Guid))
                    {
                        expression = Expression.Call(typeof(DatabaseExtensionFunctions).GetMethod("GuidIsLessThan"), memberAccess, constant);
                    }
                    else
                    {
                        expression = Expression.LessThan(memberAccess, constant);
                    }
                    break;

                case "lessequal":
                    if (propertyBasicType == typeof(string))
                    {
                        expression = Expression.Call(typeof(DatabaseExtensionFunctions).GetMethod("IsLessThenOrEqual"), memberAccess, constant);
                    }
                    else if (propertyBasicType == typeof(Guid))
                    {
                        expression = Expression.Call(typeof(DatabaseExtensionFunctions).GetMethod("GuidIsLessThanOrEqual"), memberAccess, constant);
                    }
                    else
                    {
                        expression = Expression.LessThanOrEqual(memberAccess, constant);
                    }
                    break;

                case "startswith":
                case "endswith":
                {
                    Expression stringMember;
                    if (propertyBasicType == typeof(string))
                    {
                        stringMember = memberAccess;
                    }
                    else
                    {
                        var castMethod = typeof(DatabaseExtensionFunctions).GetMethod("CastToString", new[] { memberAccess.Type });
                        if (castMethod == null)
                        {
                            throw new FrameworkException("Generic filter operation '" + filter.Operation + "' is not supported on property type '" + propertyBasicType.Name + "'. There is no overload of 'DatabaseExtensionFunctions.CastToString' function for the type.");
                        }
                        stringMember = Expression.Call(castMethod, memberAccess);
                    }
                    string dbMethodName = filter.Operation.Equals("startswith", StringComparison.OrdinalIgnoreCase) ? "StartsWithCaseInsensitive" : "EndsWithCaseInsensitive";
                    expression = Expression.Call(typeof(DatabaseExtensionFunctions).GetMethod(dbMethodName), stringMember, constant);
                    break;
                }

                case "contains":
                case "notcontains":
                {
                    Expression stringMember;
                    if (propertyBasicType == typeof(string))
                    {
                        stringMember = memberAccess;
                    }
                    else
                    {
                        var castMethod = typeof(DatabaseExtensionFunctions).GetMethod("CastToString", new[] { memberAccess.Type });
                        if (castMethod == null)
                        {
                            throw new FrameworkException("Generic filter operation '" + filter.Operation + "' is not supported on property type '" + propertyBasicType.Name + "'. There is no overload of 'DatabaseExtensionFunctions.CastToString' function for the type.");
                        }
                        stringMember = Expression.Call(castMethod, memberAccess);
                    }
                    expression = Expression.Call(typeof(DatabaseExtensionFunctions).GetMethod("ContainsCaseInsensitive"), stringMember, constant);

                    if (filter.Operation.Equals("notcontains", StringComparison.OrdinalIgnoreCase))
                    {
                        expression = Expression.Not(expression);
                    }
                    break;
                }

                case "datein":
                case "datenotin":
                {
                    if (propertyBasicType != typeof(DateTime))
                    {
                        throw new FrameworkException("Generic filter operation '" + filter.Operation
                                                     + "' is not supported for property type '" + propertyBasicType.Name + "'. Expected property type 'DateTime'.");
                    }

                    var match = DateRangeRegex.Match(filter.Value.ToString());
                    if (!match.Success)
                    {
                        throw new FrameworkException("Generic filter operation '" + filter.Operation + "' expects format 'yyyy-mm-dd', 'yyyy-mm' or 'yyyy'. Value '" + filter.Value + "' has invalid format.");
                    }

                    DateTime?date1, date2;
                    int      year = int.Parse(match.Groups["y"].Value);
                    if (string.IsNullOrEmpty(match.Groups["m"].Value))
                    {
                        date1 = new DateTime(year, 1, 1);
                        date2 = date1.Value.AddYears(1);
                    }
                    else
                    {
                        int month = int.Parse(match.Groups["m"].Value);
                        if (string.IsNullOrEmpty(match.Groups["d"].Value))
                        {
                            date1 = new DateTime(year, month, 1);
                            date2 = date1.Value.AddMonths(1);
                        }
                        else
                        {
                            int day = int.Parse(match.Groups["d"].Value);
                            date1 = new DateTime(year, month, day);
                            date2 = date1.Value.AddDays(1);
                        }
                    }

                    expression = Expression.AndAlso(
                        Expression.GreaterThanOrEqual(memberAccess, Expression.Constant(date1, typeof(DateTime?))),
                        Expression.LessThan(memberAccess, Expression.Constant(date2, typeof(DateTime?))));

                    if (filter.Operation.Equals("datenotin", StringComparison.OrdinalIgnoreCase))
                    {
                        expression = Expression.Not(expression);
                    }
                    break;
                }

                case "in":
                case "notin":
                {
                    Type       collectionElement     = GetElementType(constant.Type);
                    Expression convertedMemberAccess = memberAccess.Type != collectionElement
                                ? Expression.Convert(memberAccess, collectionElement)
                                : memberAccess;

                    Type collectionBasicType = typeof(IQueryable).IsAssignableFrom(constant.Type)
                                ? typeof(Queryable) : typeof(Enumerable);
                    var containsMethod = collectionBasicType.GetMethods()
                                         .Single(m => m.Name == "Contains" && m.GetParameters().Length == 2)
                                         .MakeGenericMethod(collectionElement);

                    expression = EFExpression.OptimizeContains(Expression.Call(containsMethod, constant, convertedMemberAccess));

                    if (filter.Operation.Equals("notin", StringComparison.OrdinalIgnoreCase))
                    {
                        expression = Expression.Not(expression);
                    }
                    break;
                }

                default:
                    throw new FrameworkException("Unsupported generic filter operation '" + filter.Operation + "' on property (while generating expression).");
                }

                resultCondition = resultCondition != null?Expression.AndAlso(resultCondition, expression) : expression;
            }

            return(Expression.Lambda(resultCondition, parameter));
        }
Пример #3
0
        public IEnumerable <TEntityInterface> Read(object parameter, Type parameterType, bool preferQuery)
        {
            // Use Load(parameter), Query(parameter) or Filter(Query(), parameter), if any of the options exist.
            ReadingOption loaderWithParameter = () =>
            {
                var reader = Reflection.RepositoryLoadWithParameterMethod(parameterType);
                if (reader == null)
                {
                    return(null);
                }
                return(() =>
                {
                    _logger.Trace(() => "Reading using Load(" + reader.GetParameters()[0].ParameterType.FullName + ")");
                    return (IEnumerable <TEntityInterface>)reader.InvokeEx(_repository.Value, parameter);
                });
            };

            ReadingOption queryWithParameter = () =>
            {
                var reader = Reflection.RepositoryQueryWithParameterMethod(parameterType);
                if (reader == null)
                {
                    return(null);
                }
                return(() =>
                {
                    _logger.Trace(() => "Reading using Query(" + reader.GetParameters()[0].ParameterType.FullName + ")");
                    return (IEnumerable <TEntityInterface>)reader.InvokeEx(_repository.Value, parameter);
                });
            };

            ReadingOption queryThenQueryableFilter = () =>
            {
                if (Reflection.RepositoryQueryMethod == null)
                {
                    return(null);
                }
                var reader = Reflection.RepositoryQueryableFilterMethod(parameterType);
                if (reader == null)
                {
                    return(null);
                }
                return(() =>
                {
                    _logger.Trace(() => "Reading using queryable Filter(Query(), " + reader.GetParameters()[1].ParameterType.FullName + ")");
                    var query = Reflection.RepositoryQueryMethod.InvokeEx(_repository.Value);
                    return (IEnumerable <TEntityInterface>)reader.InvokeEx(_repository.Value, query, parameter);
                });
            };

            ReadingOption queryAll = () =>
            {
                if (!typeof(FilterAll).IsAssignableFrom(parameterType))
                {
                    return(null);
                }
                var reader = Reflection.RepositoryQueryMethod;
                if (reader == null)
                {
                    return(null);
                }
                return(() =>
                {
                    _logger.Trace(() => "Reading using Query()");
                    return (IEnumerable <TEntityInterface>)reader.InvokeEx(_repository.Value);
                });
            };

            {
                ReadingOptions options;
                if (!preferQuery)
                {
                    options = new ReadingOptions {
                        loaderWithParameter, queryWithParameter, queryThenQueryableFilter
                    }
                }
                ;
                else
                {
                    options = new ReadingOptions {
                        queryWithParameter, queryThenQueryableFilter, queryAll, loaderWithParameter
                    }
                };

                var readingMethod = options.FirstOptionOrNull();
                if (readingMethod != null)
                {
                    return(readingMethod());
                }
            }

            // If the parameter is FilterAll, unless explicitly implemented above, use Load() or Query() if any option exists
            if (typeof(FilterAll).IsAssignableFrom(parameterType))
            {
                var options = new ReadingOptions {
                    () => {
                        var reader = Reflection.RepositoryLoadMethod;
                        if (reader == null)
                        {
                            return(null);
                        }
                        return(() => {
                            _logger.Trace(() => "Reading using Load()");
                            return (IEnumerable <TEntityInterface>)reader.InvokeEx(_repository.Value);
                        });
                    },
                    () => {
                        var reader = Reflection.RepositoryQueryMethod;
                        if (reader == null)
                        {
                            return(null);
                        }
                        return(() => {
                            _logger.Trace(() => "Reading using Query()");
                            return (IEnumerable <TEntityInterface>)reader.InvokeEx(_repository.Value);
                        });
                    }
                };

                if (preferQuery)
                {
                    options.Reverse();
                }

                var readingMethod = options.FirstOptionOrNull();
                if (readingMethod != null)
                {
                    return(readingMethod());
                }
            }

            // If the parameter is a generic filter, unless explicitly implemented above, execute it
            if (parameterType == typeof(FilterCriteria))
            {
                _logger.Trace(() => "Reading using generic filter");
                return(ExecuteGenericFilter(new[] { (FilterCriteria)parameter }, preferQuery));
            }
            if (typeof(IEnumerable <FilterCriteria>).IsAssignableFrom(parameterType))
            {
                _logger.Trace(() => "Reading using generic filters");
                return(ExecuteGenericFilter((IEnumerable <FilterCriteria>)parameter, preferQuery));
            }

            // If the parameter is a generic property filter, unless explicitly implemented above, use Query().Where(property filter)
            if (Reflection.RepositoryQueryMethod != null && typeof(IEnumerable <PropertyFilter>).IsAssignableFrom(parameterType))
            {
                _logger.Trace(() => "Reading using Query().Where(property filter");
                var query            = (IQueryable <TEntityInterface>)Reflection.RepositoryQueryMethod.InvokeEx(_repository.Value);
                var filterExpression = _genericFilterHelper.ToExpression((IEnumerable <PropertyFilter>)parameter, Reflection.EntityNavigationType);
                return(Reflection.Where(query, filterExpression));
            }

            // If the parameter is a filter expression, unless explicitly implemented above, use Query().Where(parameter)
            if (Reflection.RepositoryQueryMethod != null && Reflection.IsPredicateExpression(parameterType))
            {
                _logger.Trace(() => "Reading using Query().Where(" + parameterType.Name + ")");
                var query = (IQueryable <TEntityInterface>)Reflection.RepositoryQueryMethod.InvokeEx(_repository.Value);
                return(Reflection.Where(query, (Expression)parameter));
            }

            // If the parameter is a IEnumarable<Guid>, it will be interpreted as filter by IDs.
            if (Reflection.RepositoryQueryMethod != null && typeof(IEnumerable <Guid>).IsAssignableFrom(parameterType))
            {
                _logger.Trace(() => "Reading using Query().Where(item => guids.Contains(item.ID))");
                if (!(parameter is List <Guid>))
                {
                    parameter = ((IEnumerable <Guid>)parameter).ToList();
                }
                var query = (IQueryable <TEntityInterface>)Reflection.RepositoryQueryMethod.InvokeEx(_repository.Value);

                // The query is built by reflection to avoid an obscure problem with complex query in NHibernate:
                // using generic parameter TEntityInterface or IEntity for a query parameter fails with exception on some complex scenarios.
                var filterPredicateParameter = Expression.Parameter(Reflection.EntityType, "item");
                var filterPredicate          = Expression.Lambda(
                    Expression.Call(
                        Expression.Constant(parameter),
                        typeof(List <Guid>).GetMethod("Contains"),
                        new[] { Expression.Property(filterPredicateParameter, "ID") }),
                    filterPredicateParameter);

                return(Reflection.Where(query, EFExpression.OptimizeContains(filterPredicate)));
            }

            // It there is only enumerable filter available, use inefficient loader with in-memory filtering: Filter(Load(), parameter)
            {
                var reader = Reflection.RepositoryEnumerableFilterMethod(parameterType);
                if (reader != null)
                {
                    IEnumerable <TEntityInterface> items;
                    try
                    {
                        items = Read(null, typeof(FilterAll), preferQuery: false);
                    }
                    catch (FrameworkException)
                    {
                        items = null;
                    }

                    if (items != null)
                    {
                        _logger.Trace(() => "Reading using enumerable Filter(all, " + reader.GetParameters()[1].ParameterType.FullName + ")");
                        Reflection.MaterializeEntityList(ref items);
                        return((IEnumerable <TEntityInterface>)reader.InvokeEx(_repository.Value, items, parameter));
                    }
                }
            }

            throw new FrameworkException($"{EntityName} does not implement a loader, a query or a filter with parameter {parameterType.FullName}.");
        }