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