Browse Source

Add PredicateBuilder class.

Resolve #5039
pull/5045/head
maliming 6 years ago
parent
commit
fc2cd29f2e
  1. 284
      framework/src/Volo.Abp.Core/System/Linq/PredicateOperator.cs
  2. 72
      framework/test/Volo.Abp.Core.Tests/System/Linq/PredicateBuilder_Tests.cs

284
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.
/// <summary> The Predicate Operator </summary>
public enum PredicateOperator
{
/// <summary> The "Or" </summary>
Or,
/// <summary> The "And" </summary>
And
}
/// <summary>
/// See http://www.albahari.com/expressions for information and examples.
/// </summary>
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);
}
}
/// <summary> Start an expression </summary>
public static ExpressionStarter<T> New<T>(Expression<Func<T, bool>> expr = null)
{
return new ExpressionStarter<T>(expr);
}
/// <summary> Create an expression with a stub expression true or false to use when the expression is not yet started. </summary>
public static ExpressionStarter<T> New<T>(bool defaultExpression)
{
return new ExpressionStarter<T>(defaultExpression);
}
/// <summary> Always true </summary>
[Obsolete("Use PredicateBuilder.New() instead.")]
public static Expression<Func<T, bool>> True<T>()
{
return new ExpressionStarter<T>(true);
}
/// <summary> Always false </summary>
[Obsolete("Use PredicateBuilder.New() instead.")]
public static Expression<Func<T, bool>> False<T>()
{
return new ExpressionStarter<T>(false);
}
/// <summary> OR </summary>
public static Expression<Func<T, bool>> Or<T>([NotNull] this Expression<Func<T, bool>> expr1,
[NotNull] Expression<Func<T, bool>> expr2)
{
var expr2Body = new RebindParameterVisitor(expr2.Parameters[0], expr1.Parameters[0]).Visit(expr2.Body);
return Expression.Lambda<Func<T, bool>>(Expression.OrElse(expr1.Body, expr2Body), expr1.Parameters);
}
/// <summary> AND </summary>
public static Expression<Func<T, bool>> And<T>([NotNull] this Expression<Func<T, bool>> expr1,
[NotNull] Expression<Func<T, bool>> expr2)
{
var expr2Body = new RebindParameterVisitor(expr2.Parameters[0], expr1.Parameters[0]).Visit(expr2.Body);
return Expression.Lambda<Func<T, bool>>(Expression.AndAlso(expr1.Body, expr2Body), expr1.Parameters);
}
/// <summary>
/// Extends the specified source Predicate with another Predicate and the specified PredicateOperator.
/// </summary>
/// <typeparam name="T">The type</typeparam>
/// <param name="first">The source Predicate.</param>
/// <param name="second">The second Predicate.</param>
/// <param name="operator">The Operator (can be "And" or "Or").</param>
/// <returns>Expression{Func{T, bool}}</returns>
public static Expression<Func<T, bool>> Extend<T>([NotNull] this Expression<Func<T, bool>> first,
[NotNull] Expression<Func<T, bool>> second, PredicateOperator @operator = PredicateOperator.Or)
{
return @operator == PredicateOperator.Or ? first.Or(second) : first.And(second);
}
/// <summary>
/// Extends the specified source Predicate with another Predicate and the specified PredicateOperator.
/// </summary>
/// <typeparam name="T">The type</typeparam>
/// <param name="first">The source Predicate.</param>
/// <param name="second">The second Predicate.</param>
/// <param name="operator">The Operator (can be "And" or "Or").</param>
/// <returns>Expression{Func{T, bool}}</returns>
public static Expression<Func<T, bool>> Extend<T>([NotNull] this ExpressionStarter<T> first,
[NotNull] Expression<Func<T, bool>> second, PredicateOperator @operator = PredicateOperator.Or)
{
return @operator == PredicateOperator.Or ? first.Or(second) : first.And(second);
}
}
/// <summary>
/// ExpressionStarter{T} which eliminates the default 1=0 or 1=1 stub expressions
/// </summary>
/// <typeparam name="T">The type</typeparam>
public class ExpressionStarter<T>
{
public ExpressionStarter() : this(false)
{
}
public ExpressionStarter(bool defaultExpression)
{
if (defaultExpression)
DefaultExpression = f => true;
else
DefaultExpression = f => false;
}
public ExpressionStarter(Expression<Func<T, bool>> exp) : this(false)
{
_predicate = exp;
}
/// <summary>The actual Predicate. It can only be set by calling Start.</summary>
private Expression<Func<T, bool>> Predicate =>
(IsStarted || !UseDefaultExpression) ? _predicate : DefaultExpression;
private Expression<Func<T, bool>> _predicate;
/// <summary>Determines if the predicate is started.</summary>
public bool IsStarted => _predicate != null;
/// <summary> A default expression to use only when the expression is null </summary>
public bool UseDefaultExpression => DefaultExpression != null;
/// <summary>The default expression</summary>
public Expression<Func<T, bool>> DefaultExpression { get; set; }
/// <summary>Set the Expression predicate</summary>
/// <param name="exp">The first expression</param>
public Expression<Func<T, bool>> Start(Expression<Func<T, bool>> exp)
{
if (IsStarted)
{
throw new Exception("Predicate cannot be started again.");
}
return _predicate = exp;
}
/// <summary>Or</summary>
public Expression<Func<T, bool>> Or([NotNull] Expression<Func<T, bool>> expr2)
{
return (IsStarted) ? _predicate = Predicate.Or(expr2) : Start(expr2);
}
/// <summary>And</summary>
public Expression<Func<T, bool>> And([NotNull] Expression<Func<T, bool>> expr2)
{
return (IsStarted) ? _predicate = Predicate.And(expr2) : Start(expr2);
}
/// <summary> Show predicate string </summary>
public override string ToString()
{
return Predicate?.ToString();
}
#region Implicit Operators
/// <summary>
/// Allows this object to be implicitely converted to an Expression{Func{T, bool}}.
/// </summary>
/// <param name="right"></param>
public static implicit operator Expression<Func<T, bool>>(ExpressionStarter<T> right)
{
return right?.Predicate;
}
/// <summary>
/// Allows this object to be implicitely converted to an Expression{Func{T, bool}}.
/// </summary>
/// <param name="right"></param>
public static implicit operator Func<T, bool>(ExpressionStarter<T> right)
{
return right == null ? null :
(right.IsStarted || right.UseDefaultExpression) ? right.Predicate.Compile() : null;
}
/// <summary>
/// Allows this object to be implicitely converted to an Expression{Func{T, bool}}.
/// </summary>
/// <param name="right"></param>
public static implicit operator ExpressionStarter<T>(Expression<Func<T, bool>> right)
{
return right == null ? null : new ExpressionStarter<T>(right);
}
#endregion
#region Implement Expression<TDelagate> methods and properties
#if !(NET35)
/// <summary></summary>
public Func<T, bool> Compile()
{
return Predicate.Compile();
}
#endif
#if !(NET35 || WINDOWS_APP || NETSTANDARD || PORTABLE || PORTABLE40 || UAP)
/// <summary></summary>
public Func<T, bool> Compile(DebugInfoGenerator debugInfoGenerator) { return Predicate.Compile(debugInfoGenerator); }
/// <summary></summary>
public Expression<Func<T, bool>> Update(Expression body, IEnumerable<ParameterExpression> parameters) { return Predicate.Update(body, parameters); }
#endif
#endregion
#region Implement LamdaExpression methods and properties
/// <summary></summary>
public Expression Body => Predicate.Body;
/// <summary></summary>
public ExpressionType NodeType => Predicate.NodeType;
/// <summary></summary>
public ReadOnlyCollection<ParameterExpression> Parameters => Predicate.Parameters;
/// <summary></summary>
public Type Type => Predicate.Type;
#if !(NET35)
/// <summary></summary>
public string Name => Predicate.Name;
/// <summary></summary>
public Type ReturnType => Predicate.ReturnType;
/// <summary></summary>
public bool TailCall => Predicate.TailCall;
#endif
#if !(NET35 || WINDOWS_APP || NETSTANDARD || PORTABLE || PORTABLE40 || UAP)
/// <summary></summary>
public void CompileToMethod(MethodBuilder method) { Predicate.CompileToMethod(method); }
/// <summary></summary>
public void CompileToMethod(MethodBuilder method, DebugInfoGenerator debugInfoGenerator) { Predicate.CompileToMethod(method, debugInfoGenerator); }
#endif
#endregion
#region Implement Expression methods and properties
#if !(NET35)
/// <summary></summary>
public virtual bool CanReduce => Predicate.CanReduce;
#endif
#endregion
}
}

72
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<TestObj>();
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<TestObj>();
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; }
}
}
}
Loading…
Cancel
Save