snippetcsharpMinor
Generate a LINQ Expression Tree from a filter string
Viewed 0 times
expressionlinqgeneratefilterfromstringtree
Problem
I have a C# Service that takes a filter string and a
Filters have the form
The
Example
In the example above
The code for the service can be found below:
```
public class FilterService : IFilterService where TModel : class
{
private readonly FilterTypeFactory _filterTypeFactory;
private readonly Lazy> _modelPropertiesLazy;
public FilterService()
{
_filterTypeFactory = new FilterTypeFactory();
_modelPropertiesLazy = new Lazy>(() => typeof(TModel).GetTypeInfo().GetProperties(BindingFlags.Instance | BindingFlags.Public));
}
public Expression> GenerateFilterFromFilterMapSync(string filterString) where TFilterMap : class, new()
{
IEnumerable filters = ParseFilterString(filterString);
if (!filters.Any())
{
return null;
}
IEnumerable filterMapProperties = typeof(TFilterMap).GetTypeInfo().GetProperties(BindingFlags.Instance | BindingFlags.Public);
TFilterMap filterMa
FilterMap class and generates an Expression> for use within Linq to * Scenarios (currently it is in use to create dynamic filtering for documentdb queries).Filters have the form
parameter_operation_value, for example: firstName_eq_Joe. Multiple filters can be contained within the same string if they are space delimited, for example: firstName_eq_Joe lastName_eq_Bloggs.The
FilterMap class is a simple POCO that states what filters are available and where they exist on the target object. Each property on the FilterMap is a string, where the property name is the property part of a filter and its value is where that property lives on T.Example
FilterMap class:public class FilterMap
{
public string Reputation => "Reputation";
public string FirstName => "User.FirstName";
public string LastName => "User.LastName";
}In the example above
Reputation is mapped to the property Reputation. FirstName is mapped to an object property on T called User and then a property on that User object called FirstName.The code for the service can be found below:
```
public class FilterService : IFilterService where TModel : class
{
private readonly FilterTypeFactory _filterTypeFactory;
private readonly Lazy> _modelPropertiesLazy;
public FilterService()
{
_filterTypeFactory = new FilterTypeFactory();
_modelPropertiesLazy = new Lazy>(() => typeof(TModel).GetTypeInfo().GetProperties(BindingFlags.Instance | BindingFlags.Public));
}
public Expression> GenerateFilterFromFilterMapSync(string filterString) where TFilterMap : class, new()
{
IEnumerable filters = ParseFilterString(filterString);
if (!filters.Any())
{
return null;
}
IEnumerable filterMapProperties = typeof(TFilterMap).GetTypeInfo().GetProperties(BindingFlags.Instance | BindingFlags.Public);
TFilterMap filterMa
Solution
internal abstract class BaseFilterType The
GetPropertyExpression() just got into my eyes asking myself what all of the case evaluations means. The same will likely happen to Sam the maintainer while hunting down a bug or adding functionality to that class. Instead of having values in the case statements like "eq", "ne", "ct" etc you should either extract this to meaningful constants like e.g
private const string equal = eq";
private const string notEqual = "ne";or use an enum like so
public enum Operation
{
Equal, NotEqual, GreaterThan, and so on
}Using an enum approach would need to change your
FilterInfo class to use this Operation enum instead of string but would come with the benefit that you wouldn't need to call ToLowerInvariant(). You could replace the
internal readonly IEnumerable _allowedOperations; with Operation as well by using the [FlagsAttribute] on the enum so you can combine the enums by AND, OR, EXCLUSIVE OR. Using the FalgsAttribute should go hand in hand with naming the enum using a plural form, hence Operations should then be used. internal class FilterTypeFactory I would extract the
case TypeCode.Object: in the GetFilterType() method to a separate method because that method/case is too big. In addition instead of accessing the
PropertyType of the PropertyInfo over and over again, I would access it once, store it in a variable and check against this variable.Code Snippets
private const string equal = eq";
private const string notEqual = "ne";public enum Operation
{
Equal, NotEqual, GreaterThan, and so on
}Context
StackExchange Code Review Q#156208, answer score: 4
Revisions (0)
No revisions yet.