/// <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 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}."); }