mirror of https://github.com/sean-m/McRule.git
NO MERGE. Tests broken.
parent
a3f5324776
commit
a4a2b12db4
|
@ -0,0 +1,77 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using static McRule.Tests.TestPolicies;
|
||||
|
||||
namespace McRule.Tests {
|
||||
public class IDictionarySelector {
|
||||
|
||||
public class SomeContext {
|
||||
public string Name { get; set; }
|
||||
public bool Authorized { get; set; } = false;
|
||||
public ContextStringDictionary Context { get; set; }
|
||||
}
|
||||
|
||||
public class ContextStringDictionary : Dictionary<string, string> { }
|
||||
|
||||
public List<SomeContext> SomeContexts = new List<SomeContext>();
|
||||
|
||||
[SetUp] public void SetUp() {
|
||||
SomeContexts.Add(new SomeContext() {
|
||||
Name = "Me",
|
||||
Context = new ContextStringDictionary() {
|
||||
{ "GivenName", "Sean"},
|
||||
{ "Surname", "McArdle" },
|
||||
{ "Department", "IT" },
|
||||
{ "Team", "Cloud" },
|
||||
}
|
||||
});
|
||||
|
||||
SomeContexts.Add(new SomeContext() {
|
||||
Name = "Dog",
|
||||
Context = new ContextStringDictionary() {
|
||||
{ "GivenName", "Navi"},
|
||||
{ "Surname", "McArdle" },
|
||||
{ "Department", "Security" },
|
||||
{ "Team", "Pets" },
|
||||
}
|
||||
});
|
||||
|
||||
SomeContexts.Add(new SomeContext() {
|
||||
Name = "Son",
|
||||
Context = new ContextStringDictionary() {
|
||||
{ "GivenName", "Thing-1"},
|
||||
{ "Surname", "McArdle" },
|
||||
{ "Department", "IT" },
|
||||
{ "Team", "Children" },
|
||||
}
|
||||
});
|
||||
|
||||
SomeContexts.Add(new SomeContext() {
|
||||
Name = "Son",
|
||||
Context = new ContextStringDictionary() {
|
||||
{ "GivenName", "Thing-2"},
|
||||
{ "Surname", "McArdle" },
|
||||
{ "Team", "Children" },
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
[Test] public void CanSelectDictionaryValuesByKey() {
|
||||
var lambda = itPeople.GetPredicateExpression<ContextStringDictionary>();
|
||||
Console.WriteLine(lambda);
|
||||
|
||||
var filter = lambda.Compile();
|
||||
Func<ContextStringDictionary, bool> filterText = x => (x.ContainsKey("Department") && (x["Department"] != null) && x["Department"].Equals("IT"));
|
||||
var filteredContexts = SomeContexts.Select(x => x.Context).Where(filterText)?.ToList();
|
||||
|
||||
Assert.NotNull(filteredContexts);
|
||||
Assert.AreEqual(filteredContexts.Count, 2);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -8,7 +8,7 @@ using System.Threading.Tasks;
|
|||
namespace McRule.Tests {
|
||||
public static class TestPolicies {
|
||||
|
||||
#region testPolicies
|
||||
#region peopleTests
|
||||
|
||||
public static ExpressionPolicy everyKindInclusive = new ExpressionPolicy {
|
||||
Name = "Any kind including null",
|
||||
|
@ -104,5 +104,19 @@ namespace McRule.Tests {
|
|||
};
|
||||
|
||||
#endregion testPolicies
|
||||
|
||||
#region someContext policies
|
||||
|
||||
public static ExpressionPolicy itPeople = new ExpressionPolicy {
|
||||
Rules = new List<ExpressionRule>
|
||||
{
|
||||
("ContextDictionary", "Department", "IT").ToFilterRule(),
|
||||
("ContextStringDictionary", "Department", "IT").ToFilterRule(),
|
||||
("SomeContext", "Context.Department", "IT").ToFilterRule(), // Same rule but with nested selector
|
||||
},
|
||||
RuleOperator = RuleOperator.And
|
||||
};
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
|
|
@ -277,26 +277,78 @@ public class PolicyToEFExpressionGenerator : ExpressionGeneratorBase
|
|||
}
|
||||
}
|
||||
|
||||
public abstract class ExpressionGeneratorBase : ExpressionGenerator
|
||||
{
|
||||
|
||||
|
||||
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)
|
||||
{
|
||||
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.");
|
||||
}
|
||||
|
||||
|
||||
private class MemberResolveResult<T> {
|
||||
internal List<Expression<Func<T, bool>>> PreChecks { get; set; } = new List<Expression<Func<T, bool>>>();
|
||||
|
||||
internal Expression Member { get; set; }
|
||||
|
||||
internal void AddNewPreCheck(Expression<Func<T, bool>> lambda) {
|
||||
PreChecks.Add(lambda);
|
||||
}
|
||||
|
||||
internal Expression<Func<T, bool>> GetPrecheckFunc() {
|
||||
if (PreChecks.Count == 0) {
|
||||
return default(Expression<Func<T, bool>>);
|
||||
}
|
||||
|
||||
return CombineAnd<T>(PreChecks);
|
||||
}
|
||||
}
|
||||
|
||||
private MemberResolveResult<T> GetMemberByNameForType<T>(string propertyName, ParameterExpression parameter) {
|
||||
|
||||
var result = new MemberResolveResult<T>();
|
||||
|
||||
Expression opLeft = parameter;
|
||||
|
||||
foreach (string p in propertyName.Split(".")) {
|
||||
if (opLeft.Type.GetInterfaces().Contains(typeof(IDictionary))) {
|
||||
|
||||
var dictKey = Expression.Constant(p);
|
||||
|
||||
// Create generic method which is bound with the Call Expression below
|
||||
var containsKeyRuntimeMethod = opLeft.Type.GetMethod("ContainsKey");
|
||||
|
||||
var containsKeyCall = Expression.Call(opLeft, containsKeyRuntimeMethod, dictKey);
|
||||
//var preCheckLambda = Expression.Lambda(containsKeyCall, false, new ParameterExpression[] { opLeft, dictKey });
|
||||
result.AddNewPreCheck(Expression.Lambda<Func<T,bool>>(containsKeyCall, false, Expression.Parameter(opLeft.Type, "x")));
|
||||
|
||||
opLeft = Expression.Property(opLeft, "Item", dictKey);
|
||||
|
||||
} else {
|
||||
opLeft = Expression.PropertyOrField(opLeft, p);
|
||||
}
|
||||
}
|
||||
result.Member = opLeft;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Dynamically build an expression suitable for filtering in a Where clause
|
||||
/// </summary>
|
||||
public Expression<Func<T, bool>> GetPredicateExpressionForType<T>(string property, string value)
|
||||
{
|
||||
var parameter = Expression.Parameter(typeof(T), "x");
|
||||
Expression opLeft = parameter;
|
||||
foreach (string p in property.Split(".")) opLeft = Expression.PropertyOrField(opLeft, p);
|
||||
var resolvedMember = GetMemberByNameForType<T>(property, parameter);
|
||||
Expression opLeft = resolvedMember.Member;
|
||||
|
||||
|
||||
(bool literalFound, LiteralValue? processedValue) = GetStringValueLiteral(value);
|
||||
var opRight = Expression.Constant(value);
|
||||
Expression? comparison = null;
|
||||
Expression? comparison = resolvedMember.GetPrecheckFunc();
|
||||
|
||||
// For IComparable types on the left hand side, attempt to parse the right hand side
|
||||
// into the same type and use <,>,<=,>=,= prefixes to infer BinaryExpression type.
|
||||
|
@ -350,19 +402,20 @@ public abstract class ExpressionGeneratorBase : ExpressionGenerator
|
|||
|
||||
if (value.StartsWith("*") && value.EndsWith("*"))
|
||||
{
|
||||
comparison = AddStringPropertyExpression<T>(strParam, value.Trim('*'), "Contains", ignoreCase);
|
||||
comparison = PredicateBuilder.And<T>((Expression<Func<T, bool>>)comparison, AddStringPropertyExpression<T>(strParam, value.Trim('*'), "Contains", ignoreCase));
|
||||
}
|
||||
else if (value.StartsWith("*"))
|
||||
{
|
||||
comparison = AddStringPropertyExpression<T>(strParam, value.TrimStart('*'), "EndsWith", ignoreCase);
|
||||
comparison = PredicateBuilder.And<T>((Expression<Func<T, bool>>)comparison, AddStringPropertyExpression<T>(strParam, value.TrimStart('*'), "EndsWith", ignoreCase));
|
||||
|
||||
}
|
||||
else if (value.EndsWith("*"))
|
||||
{
|
||||
comparison = AddStringPropertyExpression<T>(strParam, value.TrimEnd('*'), "StartsWith", ignoreCase);
|
||||
comparison = PredicateBuilder.And<T>((Expression<Func<T, bool>>)comparison, AddStringPropertyExpression<T>(strParam, value.TrimEnd('*'), "StartsWith", ignoreCase));
|
||||
}
|
||||
else
|
||||
{
|
||||
comparison = AddStringPropertyExpression<T>(strParam, value, "Equals", ignoreCase);
|
||||
comparison = PredicateBuilder.And<T>((Expression<Func<T, bool>>)comparison, AddStringPropertyExpression<T>(strParam, value, "Equals", ignoreCase));
|
||||
}
|
||||
|
||||
if (negateResult)
|
||||
|
@ -410,11 +463,15 @@ public abstract class ExpressionGeneratorBase : ExpressionGenerator
|
|||
}
|
||||
|
||||
// The value may have the right type and should just be returned.
|
||||
if (comparison is Expression<Func<T, bool>> result && result != default(Expression<Func<T, bool>>)) {
|
||||
return result;
|
||||
Expression<Func<T, bool>> result = default(Expression<Func<T, bool>>);
|
||||
if (comparison is Expression<Func<T, bool>> checkedResult && checkedResult != default(Expression<Func<T, bool>>)) {
|
||||
result = checkedResult;
|
||||
}
|
||||
else {
|
||||
result = Expression.Lambda<Func<T, bool>>(comparison ?? Expression.Equal(opLeft, opRight), parameter);
|
||||
}
|
||||
|
||||
return Expression.Lambda<Func<T, bool>>(comparison ?? Expression.Equal(opLeft, opRight), parameter);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue