mirror of https://github.com/sean-m/McRule.git
Initial implementation for nested property selector. Not null safe.
parent
54ba5be48a
commit
8c548fcd38
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
@ -235,9 +237,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 +274,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 +283,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 +393,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)
|
||||
|
|
Loading…
Reference in New Issue