NO MERGE. Tests broken.

main
Sean McArde 2024-04-22 18:12:36 -07:00
parent a3f5324776
commit a4a2b12db4
3 changed files with 163 additions and 15 deletions

View File

@ -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);
}
}
}

View File

@ -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
}
}

View File

@ -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;
}