Skip to content

Instantly share code, notes, and snippets.

@SeanKilleen
Last active August 29, 2015 14:01
Show Gist options
  • Save SeanKilleen/1fa043025fed436eecc3 to your computer and use it in GitHub Desktop.
Save SeanKilleen/1fa043025fed436eecc3 to your computer and use it in GitHub Desktop.
// An example of one specific filter
[FilterName("IsMDL")] // this attribute is used to find the
public class ClaimsReportIsMDLFilter : NullableBoolFilter, ISearchFilter<vSEARCH_ClaimsReport>
{
public Expression<Func<vSEARCH_ClaimsReport, bool>> GetExpression(string valuesToProcess)
{
Expression<Func<vSEARCH_ClaimsReport, bool?>> selector = item => item.IsMDL; // this sets the field
// this uses the expression extensions to get the standard expression and then apply it to IsMDL
var composed = selector.Compose(base.GetFilterFunc(valuesToProcess));
var predicate = PredicateBuilder.False<vSEARCH_ClaimsReport>();
predicate = predicate.Or(composed);
return predicate;
}
}
// This test passes.
public class ClaimsReportIsMDLFilterTests
{
// ReSharper disable InconsistentNaming
private readonly vSEARCH_ClaimsReport ItemWithMDL = new vSEARCH_ClaimsReport { IsMDL = true };
private readonly vSEARCH_ClaimsReport ItemWithoutMDL = new vSEARCH_ClaimsReport { IsMDL = false };
private readonly vSEARCH_ClaimsReport ItemWithNullMDL = new vSEARCH_ClaimsReport { IsMDL = null };
// ReSharper restore InconsistentNaming
[Fact]
public void WithSearchValueOf1_HidesNonMDLAndNull()
{
var sut = this.GetCompiledExpressionForValues("1");
sut.Invoke(ItemWithMDL).Should().BeTrue();
sut.Invoke(ItemWithoutMDL).Should().BeFalse();
sut.Invoke(ItemWithNullMDL).Should().BeFalse();
}
private Func<vSEARCH_ClaimsReport, bool> GetCompiledExpressionForValues(string searchValue)
{
return new ClaimsReportIsMDLFilter().GetExpression(searchValue).Compile();
}
}
// This code happens in a method called "ClaimsReport" which takes in a NameValueCollection.
// ...
var filter = PredicateBuilder.True<vSEARCH_ClaimsReport>(); // predicate that evaluates to true.
var extractor = new SearchGridAttributeExtractor(nvc); // extracts items from the NameValueCollection
// a dictionary of attribute names and their respective search strings
var filterColumnsAndValues = extractor.AllPopulatedNonStandardAttributeNamesAndTheirSearchValues();
//Gets all of the associated search classes, loops through them to get their expressions based on the
//search values.
var filters = new SearchFilterLocator<vSEARCH_ClaimsReport>().GetPredicateExpressionsFromListOfFilters(filterColumnsAndValues);
// Adds each inner filter as an "And" to the PredicateBuilder, so it becomes an "And" join
// of several potential inner "or" joins.
foreach (var filterItem in filters)
{
filter = filter.And(filterItem);
}
//if we were to do the following instead, it would work
filter = filter.And(x=> x.IsMDL == true);
// ...
// Then, the code calls into a method which uses a SqlExpressionVisitor and passes this list of expressions into
// a where clause, where it bombs.
// these are 100% thanks to Servy on StackOverflow. See http://stackoverflow.com/a/23499052/316847 for more.
//According to servy:
// What this is doing is replacing all instances of the second expression's parameter with the body of the
// first expression, effectively inlining that expression into the second. The rest is simply replacing all of
// the parameters with a new single parameter and wrapping it back up into a lambda.
public static class ExpressionExtensions
{
public static Expression<Func<TFirstParam, TResult>> Compose<TFirstParam, TIntermediate, TResult>
(this Expression<Func<TFirstParam, TIntermediate>> first,
Expression<Func<TIntermediate, TResult>> second)
{
var param = Expression.Parameter(typeof(TFirstParam), "param");
var newFirst = first.Body.Replace(first.Parameters[0], param);
var newSecond = second.Body.Replace(second.Parameters[0], newFirst);
return Expression.Lambda<Func<TFirstParam, TResult>>(newSecond, param);
}
public static Expression Replace(this Expression expression,
Expression searchEx, Expression replaceEx)
{
return new ReplaceVisitor(searchEx, replaceEx).Visit(expression);
}
internal class ReplaceVisitor : ExpressionVisitor
{
private readonly Expression from, to;
public ReplaceVisitor(Expression from, Expression to)
{
this.from = from;
this.to = to;
}
public override Expression Visit(Expression node)
{
return node == from ? to : base.Visit(node);
}
}
}
// This is where we have to get the data.
//...
columns = columns.Where(x => !string.IsNullOrEmpty(x)).ToArray();
//Column Count
int columnCount = columns.Count();
string countSql = String.Format("SELECT DISTINCT {0} FROM {1}", string.Join(", ", columns),
typeof(DALType).GetTableName());
SqlExpressionVisitor<DALType> evCount = OrmLiteConfig.DialectProvider.ExpressionVisitor<DALType>();
evCount.Select(countSql);
if (filter != null)
{
// this is the code that throws the error.
evCount.Where(filter);
}
// The generic filter that we pull from
public class NullableBoolFilter : IGenericSearchFilter<bool?>
{
public Expression<Func<bool?, bool>> GetFilterFunc(string valuesToProcess)
{
var acceptableValues = new List<bool?>();
if (string.IsNullOrWhiteSpace(valuesToProcess))
{
// all values acceptable
acceptableValues = new List<bool?>{true, false, null};
}
else
{
if (!valuesToProcess.Contains("0") && !valuesToProcess.Contains("1"))
{
throw new ArgumentException("Invalid Nullable boolean filter attribute specified");
}
if (valuesToProcess.Contains("0"))
{
acceptableValues.Add(false);
}
if (valuesToProcess.Contains("1"))
{
acceptableValues.Add(true);
}
}
// returns the expression based on whatever the item is contained in the acceptable values
return item => acceptableValues.Any(x=> x == item);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment