From fc2cd29f2e58a87f7d64e8076f167cbe23ee0914 Mon Sep 17 00:00:00 2001 From: maliming <6908465+maliming@users.noreply.github.com> Date: Thu, 13 Aug 2020 17:44:46 +0800 Subject: [PATCH] Add PredicateBuilder class. Resolve #5039 --- .../System/Linq/PredicateOperator.cs | 284 ++++++++++++++++++ .../System/Linq/PredicateBuilder_Tests.cs | 72 +++++ 2 files changed, 356 insertions(+) create mode 100644 framework/src/Volo.Abp.Core/System/Linq/PredicateOperator.cs create mode 100644 framework/test/Volo.Abp.Core.Tests/System/Linq/PredicateBuilder_Tests.cs diff --git a/framework/src/Volo.Abp.Core/System/Linq/PredicateOperator.cs b/framework/src/Volo.Abp.Core/System/Linq/PredicateOperator.cs new file mode 100644 index 0000000000..a2dd9c3bee --- /dev/null +++ b/framework/src/Volo.Abp.Core/System/Linq/PredicateOperator.cs @@ -0,0 +1,284 @@ +using System.Collections.ObjectModel; +using System.Linq.Expressions; +using JetBrains.Annotations; + +namespace System.Linq +{ + // Codes below are taken from https://github.com/scottksmith95/LINQKit project. + + /// The Predicate Operator + public enum PredicateOperator + { + /// The "Or" + Or, + + /// The "And" + And + } + + /// + /// See http://www.albahari.com/expressions for information and examples. + /// + public static class PredicateBuilder + { + private class RebindParameterVisitor : ExpressionVisitor + { + private readonly ParameterExpression _oldParameter; + private readonly ParameterExpression _newParameter; + + public RebindParameterVisitor(ParameterExpression oldParameter, ParameterExpression newParameter) + { + _oldParameter = oldParameter; + _newParameter = newParameter; + } + + protected override Expression VisitParameter(ParameterExpression node) + { + if (node == _oldParameter) + { + return _newParameter; + } + + return base.VisitParameter(node); + } + } + + /// Start an expression + public static ExpressionStarter New(Expression> expr = null) + { + return new ExpressionStarter(expr); + } + + /// Create an expression with a stub expression true or false to use when the expression is not yet started. + public static ExpressionStarter New(bool defaultExpression) + { + return new ExpressionStarter(defaultExpression); + } + + /// Always true + [Obsolete("Use PredicateBuilder.New() instead.")] + public static Expression> True() + { + return new ExpressionStarter(true); + } + + /// Always false + [Obsolete("Use PredicateBuilder.New() instead.")] + public static Expression> False() + { + return new ExpressionStarter(false); + } + + /// OR + public static Expression> Or([NotNull] this Expression> expr1, + [NotNull] Expression> expr2) + { + var expr2Body = new RebindParameterVisitor(expr2.Parameters[0], expr1.Parameters[0]).Visit(expr2.Body); + return Expression.Lambda>(Expression.OrElse(expr1.Body, expr2Body), expr1.Parameters); + } + + /// AND + public static Expression> And([NotNull] this Expression> expr1, + [NotNull] Expression> expr2) + { + var expr2Body = new RebindParameterVisitor(expr2.Parameters[0], expr1.Parameters[0]).Visit(expr2.Body); + return Expression.Lambda>(Expression.AndAlso(expr1.Body, expr2Body), expr1.Parameters); + } + + /// + /// Extends the specified source Predicate with another Predicate and the specified PredicateOperator. + /// + /// The type + /// The source Predicate. + /// The second Predicate. + /// The Operator (can be "And" or "Or"). + /// Expression{Func{T, bool}} + public static Expression> Extend([NotNull] this Expression> first, + [NotNull] Expression> second, PredicateOperator @operator = PredicateOperator.Or) + { + return @operator == PredicateOperator.Or ? first.Or(second) : first.And(second); + } + + /// + /// Extends the specified source Predicate with another Predicate and the specified PredicateOperator. + /// + /// The type + /// The source Predicate. + /// The second Predicate. + /// The Operator (can be "And" or "Or"). + /// Expression{Func{T, bool}} + public static Expression> Extend([NotNull] this ExpressionStarter first, + [NotNull] Expression> second, PredicateOperator @operator = PredicateOperator.Or) + { + return @operator == PredicateOperator.Or ? first.Or(second) : first.And(second); + } + } + + /// + /// ExpressionStarter{T} which eliminates the default 1=0 or 1=1 stub expressions + /// + /// The type + public class ExpressionStarter + { + public ExpressionStarter() : this(false) + { + } + + public ExpressionStarter(bool defaultExpression) + { + if (defaultExpression) + DefaultExpression = f => true; + else + DefaultExpression = f => false; + } + + public ExpressionStarter(Expression> exp) : this(false) + { + _predicate = exp; + } + + /// The actual Predicate. It can only be set by calling Start. + private Expression> Predicate => + (IsStarted || !UseDefaultExpression) ? _predicate : DefaultExpression; + + private Expression> _predicate; + + /// Determines if the predicate is started. + public bool IsStarted => _predicate != null; + + /// A default expression to use only when the expression is null + public bool UseDefaultExpression => DefaultExpression != null; + + /// The default expression + public Expression> DefaultExpression { get; set; } + + /// Set the Expression predicate + /// The first expression + public Expression> Start(Expression> exp) + { + if (IsStarted) + { + throw new Exception("Predicate cannot be started again."); + } + + return _predicate = exp; + } + + /// Or + public Expression> Or([NotNull] Expression> expr2) + { + return (IsStarted) ? _predicate = Predicate.Or(expr2) : Start(expr2); + } + + /// And + public Expression> And([NotNull] Expression> expr2) + { + return (IsStarted) ? _predicate = Predicate.And(expr2) : Start(expr2); + } + + /// Show predicate string + public override string ToString() + { + return Predicate?.ToString(); + } + + #region Implicit Operators + + /// + /// Allows this object to be implicitely converted to an Expression{Func{T, bool}}. + /// + /// + public static implicit operator Expression>(ExpressionStarter right) + { + return right?.Predicate; + } + + /// + /// Allows this object to be implicitely converted to an Expression{Func{T, bool}}. + /// + /// + public static implicit operator Func(ExpressionStarter right) + { + return right == null ? null : + (right.IsStarted || right.UseDefaultExpression) ? right.Predicate.Compile() : null; + } + + /// + /// Allows this object to be implicitely converted to an Expression{Func{T, bool}}. + /// + /// + public static implicit operator ExpressionStarter(Expression> right) + { + return right == null ? null : new ExpressionStarter(right); + } + + #endregion + + #region Implement Expression methods and properties + +#if !(NET35) + + /// + public Func Compile() + { + return Predicate.Compile(); + } +#endif + +#if !(NET35 || WINDOWS_APP || NETSTANDARD || PORTABLE || PORTABLE40 || UAP) + /// + public Func Compile(DebugInfoGenerator debugInfoGenerator) { return Predicate.Compile(debugInfoGenerator); } + + /// + public Expression> Update(Expression body, IEnumerable parameters) { return Predicate.Update(body, parameters); } +#endif + + #endregion + + #region Implement LamdaExpression methods and properties + + /// + public Expression Body => Predicate.Body; + + + /// + public ExpressionType NodeType => Predicate.NodeType; + + /// + public ReadOnlyCollection Parameters => Predicate.Parameters; + + /// + public Type Type => Predicate.Type; + +#if !(NET35) + /// + public string Name => Predicate.Name; + + /// + public Type ReturnType => Predicate.ReturnType; + + /// + public bool TailCall => Predicate.TailCall; +#endif + +#if !(NET35 || WINDOWS_APP || NETSTANDARD || PORTABLE || PORTABLE40 || UAP) + /// + public void CompileToMethod(MethodBuilder method) { Predicate.CompileToMethod(method); } + + /// + public void CompileToMethod(MethodBuilder method, DebugInfoGenerator debugInfoGenerator) { Predicate.CompileToMethod(method, debugInfoGenerator); } + +#endif + + #endregion + + #region Implement Expression methods and properties + +#if !(NET35) + /// + public virtual bool CanReduce => Predicate.CanReduce; +#endif + + #endregion + } +} diff --git a/framework/test/Volo.Abp.Core.Tests/System/Linq/PredicateBuilder_Tests.cs b/framework/test/Volo.Abp.Core.Tests/System/Linq/PredicateBuilder_Tests.cs new file mode 100644 index 0000000000..d28617bc27 --- /dev/null +++ b/framework/test/Volo.Abp.Core.Tests/System/Linq/PredicateBuilder_Tests.cs @@ -0,0 +1,72 @@ +using Shouldly; +using Xunit; + +namespace System.Linq +{ + public class PredicateBuilder_Tests + { + [Fact] + public void Test1() + { + var args = new TestArgs(); + var predicate = PredicateBuilder.New(); + + predicate = predicate.And(t => args.Value == t.Value); + + var func = predicate.Compile(); + + args.Value = true; + var r2 = func(new TestObj { Value = true }); + r2.ShouldBeTrue(); + + args.Value = false; + var r1 = func(new TestObj { Value = false }); + r1.ShouldBeTrue(); + + args = new TestArgs {Value = true}; + var r3 = func(new TestObj { Value = false }); + r3.ShouldBeFalse(); + + args = new TestArgs { Value = false }; + var r4 = func(new TestObj { Value = false }); + r4.ShouldBeTrue(); + } + + [Fact] + public void Test2() + { + var args = new TestArgs(); + var predicate = PredicateBuilder.New(); + + predicate = predicate.And(t => !args.Value); + + var func = predicate.Compile(); + + args.Value = true; + var r2 = func(new TestObj { Value = true }); + r2.ShouldBeFalse(); + + args.Value = false; + var r1 = func(new TestObj { Value = false }); + r1.ShouldBeTrue(); + + args = new TestArgs { Value = true }; + var r3 = func(new TestObj { Value = false }); + r3.ShouldBeFalse(); + + args = new TestArgs { Value = false }; + var r4 = func(new TestObj { Value = false }); + r4.ShouldBeTrue(); + } + + public class TestArgs + { + public bool Value { get; set; } + } + + public class TestObj + { + public bool Value { get; set; } + } + } +}