Compare commits

...

2 Commits

2 changed files with 55 additions and 25 deletions

View File

@ -7,7 +7,7 @@ using System.Reflection.Metadata;
namespace McRule.Tests {
public class Filtering {
People[] peoples = new[] {
People[] peoples = new[] {
new People("Sean", "Confused", 35, true, new[] {"muggle"}),
new People("Sean", "Actor", 90, false, new[] {"muggle", "metallurgist"}),
new People("Bean", "Runt", 20, false, new[] {"muggle", "giant"}),
@ -26,6 +26,7 @@ namespace McRule.Tests {
public int? number { get; set; }
public bool stillWithUs { get; set; }
public string[] tags { get; set; }
public People? relatedTo { get; set; }
public People(string name, string kind, int? number, bool stillWithUs, string[] tags = null)
{
@ -38,11 +39,11 @@ namespace McRule.Tests {
public override bool Equals(object obj)
{
return obj is People other &&
name == other.name &&
kind == other.kind &&
number == other.number &&
stillWithUs == other.stillWithUs &&
return obj is People other &&
name == other.name &&
kind == other.kind &&
number == other.number &&
stillWithUs == other.stillWithUs &&
System.Linq.Enumerable.SequenceEqual(tags, other.tags);
}
}
@ -256,12 +257,12 @@ namespace McRule.Tests {
var generator = new PolicyToExpressionGenerator();
var generatedFilter = muggles.GeneratePredicateExpression<People>(generator);
var filteredFolks = peoples.Where(generatedFilter.Compile());
var efGenerator = new PolicyToEFExpressionGenerator();
var efGeneratedFilter = muggles.GeneratePredicateExpression<People>(efGenerator);
var efFilteredFolks = peoples.Where(efGeneratedFilter.Compile());
var filter = muggles.GetPredicateExpression<People>()?.Compile();
var folks = peoples.Where(filter);
@ -294,7 +295,7 @@ namespace McRule.Tests {
Assert.NotNull(folks);
Assert.NotNull(folks.Where(x => x.kind == "Viking" && x.stillWithUs == false));
Assert.NotNull(folks.Where(x => x.kind == "Viking" && x.stillWithUs == true));
// Should be either a viking or dead, not neither.
Assert.Null(folks.FirstOrDefault(x => x.kind != "Viking" && x.stillWithUs == true));
}
@ -326,6 +327,23 @@ namespace McRule.Tests {
var failedGenerator = new FailedExpressionGeneratorBase();
Assert.Throws<NotImplementedException>(() => { _ = eans.GeneratePredicateExpression<People>(failedGenerator); });
}
[Test]
public void SelectPropertyOfMemberObject() {
var filter = vikings.GetPredicateExpression<People>()?.Compile();
// This should be the two Vikings: Lars then Ragnar, in that order
var folks = peoples.Where(filter).OrderBy(x => x.name);
// We specify that Lars is related to Ragnar
folks.First().relatedTo = folks.Skip(1).First();
var testMembersProperty = ("People", "relatedTo.name", "Ragnar").ToFilterRule().GetPredicateExpression<People>().Compile();
Assert.NotNull(folks);
Assert.True(testMembersProperty(folks.First()));
}
}
internal class FailedExpressionGeneratorBase : ExpressionGeneratorBase

View File

@ -35,7 +35,7 @@ public static partial class PredicateExpressionPolicyExtensions
}
/// <summary>
/// Test for null value. This is used to test for null literals.
/// Test for null value. This is used to test for null literals.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="expression"></param>
@ -91,7 +91,9 @@ public static partial class PredicateExpressionPolicyExtensions
// Bind to the property by name and make the constant value
// we'll be passing into the Contains() call
var parameter = Expression.Parameter(typeof(T), "x");
var opLeft = Expression.Property(parameter, property);
Expression opLeft = parameter;
foreach (string p in property.Split(".")) opLeft = Expression.PropertyOrField(opLeft, p);
var opRight = Expression.Constant(value);
// Create generic method which is bound with the Call Expression below
@ -110,7 +112,7 @@ public static partial class PredicateExpressionPolicyExtensions
}
/// <summary>
/// Combine a list of expressions exclusively with AndAlso predicate from
/// Combine a list of expressions exclusively with AndAlso predicate from
/// PredicateBuilder. This operator short circuits.
/// </summary>
public static Expression<Func<T, bool>>? CombineAnd<T>(IEnumerable<Expression<Func<T, bool>>> predicates)
@ -155,9 +157,9 @@ public static partial class PredicateExpressionPolicyExtensions
return CombineOr(predicates);
}
/// <summary>
/// Builds expressions using string member functions StartsWith, EndsWith or Contains as the comparator.
/// </summary>
/// <summary>
/// Builds expressions using string member functions StartsWith, EndsWith or Contains as the comparator.
/// </summary>
public static Expression<Func<T, bool>> AddStringPropertyExpression<T>(
Expression<Func<T, string>> lambda, string filter, string filterType, bool ignoreCase = false)
{
@ -211,11 +213,19 @@ public static partial class PredicateExpressionPolicyExtensions
break;
}
}
// No literals found.
return (false, null);
}
public static Expression<Func<T, dynamic>> SelectPropertyOrField<T>(string propertyName) {
var parameter = Expression.Parameter(typeof(T), "x");
Expression opLeft = parameter;
foreach (var p in propertyName.Split(".")) opLeft = Expression.PropertyOrField(opLeft, p);
return Expression.Lambda<Func<T, dynamic>>(opLeft, parameter);
}
public static ExpressionGenerator GetCoreExpressionGenerator() => new PolicyToExpressionGenerator();
public static ExpressionGenerator GetEfExpressionGenerator() => new PolicyToEFExpressionGenerator();
@ -223,9 +233,9 @@ public static partial class PredicateExpressionPolicyExtensions
public class PolicyToExpressionGenerator : ExpressionGeneratorBase
{
/// <summary>
/// Builds expressions using string member functions StartsWith, EndsWith or Contains as the comparator.
/// </summary>
/// <summary>
/// Builds expressions using string member functions StartsWith, EndsWith or Contains as the comparator.
/// </summary>
public override Expression<Func<T, bool>> AddStringPropertyExpression<T>(
Expression<Func<T, string>> lambda, string filter, string filterType, bool ignoreCase = false)
{
@ -235,9 +245,9 @@ public class PolicyToExpressionGenerator : ExpressionGeneratorBase
public class PolicyToEFExpressionGenerator : ExpressionGeneratorBase
{
/// <summary>
/// Builds expressions using string member functions StartsWith, EndsWith or Contains as the comparator.
/// </summary>
/// <summary>
/// Builds expressions using string member functions StartsWith, EndsWith or Contains as the comparator.
/// </summary>
public override Expression<Func<T, bool>> AddStringPropertyExpression<T>(
Expression<Func<T, string>> lambda, string filter, string filterType, bool ignoreCase = false)
{
@ -272,7 +282,7 @@ public abstract class ExpressionGeneratorBase : ExpressionGenerator
public virtual Expression<Func<T, bool>> AddStringPropertyExpression<T>(
Expression<Func<T, string>> lambda, string filter, string filterType, bool ignoreCase = false)
{
throw new NotImplementedException("Must override the AddStringPropertyExpression<T> method in a child class. This one is virtual and shouldn't ever be called.");
throw new NotImplementedException("Must override the AddStringPropertyExpression<T> method in a child class. This one is virtual and shouldn't ever be called.");
}
/// <summary>
@ -281,7 +291,9 @@ public abstract class ExpressionGeneratorBase : ExpressionGenerator
public Expression<Func<T, bool>> GetPredicateExpressionForType<T>(string property, string value)
{
var parameter = Expression.Parameter(typeof(T), "x");
var opLeft = Expression.Property(parameter, property);
Expression opLeft = parameter;
foreach (string p in property.Split(".")) opLeft = Expression.PropertyOrField(opLeft, p);
(bool literalFound, LiteralValue? processedValue) = GetStringValueLiteral(value);
var opRight = Expression.Constant(value);
Expression? comparison = null;
@ -389,7 +401,7 @@ public abstract class ExpressionGeneratorBase : ExpressionGenerator
}
// If comparison is null that means we haven't been able to infer a good comparison
// expression for it so just defer to a false literal.
// expression for it so just defer to a false literal.
Expression<Func<T, bool>> falsePredicate = x => false;
comparison = comparison == null ? falsePredicate : comparison;
if (isNullableValueType)