diff --git a/framework/Volo.Abp.sln b/framework/Volo.Abp.sln
index 41eb7f1aeb..b0b7803963 100644
--- a/framework/Volo.Abp.sln
+++ b/framework/Volo.Abp.sln
@@ -228,7 +228,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.Cli.Tests", "test\
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.EntityFrameworkCore.Sqlite", "src\Volo.Abp.EntityFrameworkCore.Sqlite\Volo.Abp.EntityFrameworkCore.Sqlite.csproj", "{58CF8957-5045-4F81-884D-72DF48F721CC}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Cli.Core", "src\Volo.Abp.Cli.Core\Volo.Abp.Cli.Core.csproj", "{3DA9923E-048E-4FE7-9748-3A0194F5D196}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.Cli.Core", "src\Volo.Abp.Cli.Core\Volo.Abp.Cli.Core.csproj", "{3DA9923E-048E-4FE7-9748-3A0194F5D196}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.Specifications", "src\Volo.Abp.Specifications\Volo.Abp.Specifications.csproj", "{2C621EED-563C-4F81-A75E-50879E173544}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Specifications.Tests", "test\Volo.Abp.Specifications.Tests\Volo.Abp.Specifications.Tests.csproj", "{D078553A-C70C-4F56-B3E2-9C5BA6384C72}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -684,6 +688,14 @@ Global
{3DA9923E-048E-4FE7-9748-3A0194F5D196}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3DA9923E-048E-4FE7-9748-3A0194F5D196}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3DA9923E-048E-4FE7-9748-3A0194F5D196}.Release|Any CPU.Build.0 = Release|Any CPU
+ {2C621EED-563C-4F81-A75E-50879E173544}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {2C621EED-563C-4F81-A75E-50879E173544}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {2C621EED-563C-4F81-A75E-50879E173544}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {2C621EED-563C-4F81-A75E-50879E173544}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D078553A-C70C-4F56-B3E2-9C5BA6384C72}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D078553A-C70C-4F56-B3E2-9C5BA6384C72}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D078553A-C70C-4F56-B3E2-9C5BA6384C72}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D078553A-C70C-4F56-B3E2-9C5BA6384C72}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -801,6 +813,8 @@ Global
{92B70EFF-C1B1-4D1D-8BCE-D116908FC6FF} = {447C8A77-E5F0-4538-8687-7383196D04EA}
{58CF8957-5045-4F81-884D-72DF48F721CC} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6}
{3DA9923E-048E-4FE7-9748-3A0194F5D196} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6}
+ {2C621EED-563C-4F81-A75E-50879E173544} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6}
+ {D078553A-C70C-4F56-B3E2-9C5BA6384C72} = {447C8A77-E5F0-4538-8687-7383196D04EA}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {BB97ECF4-9A84-433F-A80B-2A3285BDD1D5}
diff --git a/framework/src/Volo.Abp.Specifications/Volo.Abp.Specifications.csproj b/framework/src/Volo.Abp.Specifications/Volo.Abp.Specifications.csproj
new file mode 100644
index 0000000000..97c236cfc5
--- /dev/null
+++ b/framework/src/Volo.Abp.Specifications/Volo.Abp.Specifications.csproj
@@ -0,0 +1,20 @@
+
+
+
+
+
+ netstandard2.0
+ Volo.Abp.Specifications
+ Volo.Abp.Specifications
+ $(AssetTargetFallback);portable-net45+win8+wp8+wpa81;
+ false
+ false
+ false
+
+
+
+
+
+
+
+
diff --git a/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/AbpSpecificationsModule.cs b/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/AbpSpecificationsModule.cs
new file mode 100644
index 0000000000..6f55973be1
--- /dev/null
+++ b/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/AbpSpecificationsModule.cs
@@ -0,0 +1,9 @@
+using Volo.Abp.Modularity;
+
+namespace Volo.Abp.Specifications
+{
+ public class AbpSpecificationsModule : AbpModule
+ {
+
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/AndNotSpecification.cs b/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/AndNotSpecification.cs
new file mode 100644
index 0000000000..e0d51bd5c3
--- /dev/null
+++ b/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/AndNotSpecification.cs
@@ -0,0 +1,36 @@
+using System;
+using System.Linq.Expressions;
+
+namespace Volo.Abp.Specifications
+{
+ ///
+ /// Represents the combined specification which indicates that the first specification
+ /// can be satisifed by the given object whereas the second one cannot.
+ ///
+ /// The type of the object to which the specification is applied.
+ public class AndNotSpecification : CompositeSpecification
+ {
+ ///
+ /// Constructs a new instance of class.
+ ///
+ /// The first specification.
+ /// The second specification.
+ public AndNotSpecification(ISpecification left, ISpecification right) : base(left, right)
+ {
+ }
+
+ ///
+ /// Gets the LINQ expression which represents the current specification.
+ ///
+ /// The LINQ expression.
+ public override Expression> ToExpression()
+ {
+ var rightExpression = Right.ToExpression();
+
+ var bodyNot = Expression.Not(rightExpression.Body);
+ var bodyNotExpression = Expression.Lambda>(bodyNot, rightExpression.Parameters);
+
+ return Left.ToExpression().And(bodyNotExpression);
+ }
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/AndSpecification.cs b/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/AndSpecification.cs
new file mode 100644
index 0000000000..621287d634
--- /dev/null
+++ b/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/AndSpecification.cs
@@ -0,0 +1,31 @@
+using System;
+using System.Linq.Expressions;
+
+namespace Volo.Abp.Specifications
+{
+ ///
+ /// Represents the combined specification which indicates that both of the given
+ /// specifications should be satisfied by the given object.
+ ///
+ /// The type of the object to which the specification is applied.
+ public class AndSpecification : CompositeSpecification
+ {
+ ///
+ /// Constructs a new instance of class.
+ ///
+ /// The first specification.
+ /// The second specification.
+ public AndSpecification(ISpecification left, ISpecification right) : base(left, right)
+ {
+ }
+
+ ///
+ /// Gets the LINQ expression which represents the current specification.
+ ///
+ /// The LINQ expression.
+ public override Expression> ToExpression()
+ {
+ return Left.ToExpression().And(Right.ToExpression());
+ }
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/AnySpecification.cs b/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/AnySpecification.cs
new file mode 100644
index 0000000000..caf6a5f094
--- /dev/null
+++ b/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/AnySpecification.cs
@@ -0,0 +1,22 @@
+using System;
+using System.Linq.Expressions;
+
+namespace Volo.Abp.Specifications
+{
+ ///
+ /// Represents the specification that can be satisfied by the given object
+ /// in any circumstance.
+ ///
+ /// The type of the object to which the specification is applied.
+ public sealed class AnySpecification : Specification
+ {
+ ///
+ /// Gets the LINQ expression which represents the current specification.
+ ///
+ /// The LINQ expression.
+ public override Expression> ToExpression()
+ {
+ return o => true;
+ }
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/CompositeSpecification.cs b/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/CompositeSpecification.cs
new file mode 100644
index 0000000000..01c1e46d33
--- /dev/null
+++ b/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/CompositeSpecification.cs
@@ -0,0 +1,30 @@
+namespace Volo.Abp.Specifications
+{
+ ///
+ /// Represents the base class for composite specifications.
+ ///
+ /// The type of the object to which the specification is applied.
+ public abstract class CompositeSpecification : Specification, ICompositeSpecification
+ {
+ ///
+ /// Constructs a new instance of class.
+ ///
+ /// The first specification.
+ /// The second specification.
+ protected CompositeSpecification(ISpecification left, ISpecification right)
+ {
+ Left = left;
+ Right = right;
+ }
+
+ ///
+ /// Gets the first specification.
+ ///
+ public ISpecification Left { get; }
+
+ ///
+ /// Gets the second specification.
+ ///
+ public ISpecification Right { get; }
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/ExpressionFuncExtender.cs b/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/ExpressionFuncExtender.cs
new file mode 100644
index 0000000000..dd169d5320
--- /dev/null
+++ b/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/ExpressionFuncExtender.cs
@@ -0,0 +1,55 @@
+using System;
+using System.Linq;
+using System.Linq.Expressions;
+
+namespace Volo.Abp.Specifications
+{
+ ///
+ /// Represents the extender for Expression[Func[T, bool]] type.
+ /// This is part of the solution which solves
+ /// the expression parameter problem when going to Entity Framework.
+ /// For more information about this solution please refer to http://blogs.msdn.com/b/meek/archive/2008/05/02/linq-to-entities-combining-predicates.aspx.
+ ///
+ public static class ExpressionFuncExtender
+ {
+ private static Expression Compose(this Expression first, Expression second,
+ Func merge)
+ {
+ // build parameter map (from parameters of second to parameters of first)
+ var map = first.Parameters.Select((f, i) => new {f, s = second.Parameters[i]})
+ .ToDictionary(p => p.s, p => p.f);
+
+ // replace parameters in the second lambda expression with parameters from the first
+ var secondBody = ParameterRebinder.ReplaceParameters(map, second.Body);
+
+ // apply composition of lambda expression bodies to parameters from the first expression
+ return Expression.Lambda(merge(first.Body, secondBody), first.Parameters);
+ }
+
+ ///
+ /// Combines two given expressions by using the AND semantics.
+ ///
+ /// The type of the object.
+ /// The first part of the expression.
+ /// The second part of the expression.
+ /// The combined expression.
+ public static Expression> And(this Expression> first,
+ Expression> second)
+ {
+ return first.Compose(second, Expression.AndAlso);
+ }
+
+ ///
+ /// Combines two given expressions by using the OR semantics.
+ ///
+ /// The type of the object.
+ /// The first part of the expression.
+ /// The second part of the expression.
+ /// The combined expression.
+ public static Expression> Or(this Expression> first,
+ Expression> second)
+ {
+ return first.Compose(second, Expression.OrElse);
+ }
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/ExpressionSpecification.cs b/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/ExpressionSpecification.cs
new file mode 100644
index 0000000000..14c1bff040
--- /dev/null
+++ b/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/ExpressionSpecification.cs
@@ -0,0 +1,34 @@
+using System;
+using System.Linq.Expressions;
+
+namespace Volo.Abp.Specifications
+{
+ ///
+ /// Represents the specification which is represented by the corresponding
+ /// LINQ expression.
+ ///
+ /// The type of the object to which the specification is applied.
+ public class ExpressionSpecification : Specification
+ {
+ private readonly Expression> _expression;
+
+ ///
+ /// Initializes a new instance of ExpressionSpecification<T> class.
+ ///
+ /// The LINQ expression which represents the current
+ /// specification.
+ public ExpressionSpecification(Expression> expression)
+ {
+ _expression = expression;
+ }
+
+ ///
+ /// Gets the LINQ expression which represents the current specification.
+ ///
+ /// The LINQ expression.
+ public override Expression> ToExpression()
+ {
+ return _expression;
+ }
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/ICompositeSpecification.cs b/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/ICompositeSpecification.cs
new file mode 100644
index 0000000000..2c73409d34
--- /dev/null
+++ b/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/ICompositeSpecification.cs
@@ -0,0 +1,19 @@
+namespace Volo.Abp.Specifications
+{
+ ///
+ /// Represents that the implemented classes are composite specifications.
+ ///
+ /// The type of the object to which the specification is applied.
+ public interface ICompositeSpecification : ISpecification
+ {
+ ///
+ /// Gets the left side of the specification.
+ ///
+ ISpecification Left { get; }
+
+ ///
+ /// Gets the right side of the specification.
+ ///
+ ISpecification Right { get; }
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/ISpecification.cs b/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/ISpecification.cs
new file mode 100644
index 0000000000..d19e28347e
--- /dev/null
+++ b/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/ISpecification.cs
@@ -0,0 +1,28 @@
+using System;
+using System.Linq.Expressions;
+
+namespace Volo.Abp.Specifications
+{
+ ///
+ /// Represents that the implemented classes are specifications. For more
+ /// information about the specification pattern, please refer to
+ /// http://martinfowler.com/apsupp/spec.pdf.
+ ///
+ /// The type of the object to which the specification is applied.
+ public interface ISpecification
+ {
+ ///
+ /// Returns a value which indicates whether the specification
+ /// is satisfied by the given object.
+ ///
+ /// The object to which the specification is applied.
+ /// True if the specification is satisfied, otherwise false.
+ bool IsSatisfiedBy(T obj);
+
+ ///
+ /// Gets the LINQ expression which represents the current specification.
+ ///
+ /// The LINQ expression.
+ Expression> ToExpression();
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/ISpecificationParser.cs b/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/ISpecificationParser.cs
new file mode 100644
index 0000000000..d2c61034e0
--- /dev/null
+++ b/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/ISpecificationParser.cs
@@ -0,0 +1,19 @@
+namespace Volo.Abp.Specifications
+{
+ ///
+ /// Represents that the implemented classes are specification parsers that
+ /// parses the given specification to a domain specific criteria object, such
+ /// as the ICriteria instance in NHibernate.
+ ///
+ /// The type of the domain specific criteria.
+ public interface ISpecificationParser
+ {
+ ///
+ /// Parses the given specification to a domain specific criteria object.
+ ///
+ /// The type of the object to which the specification is applied.
+ /// The specified specification instance.
+ /// The instance of the domain specific criteria.
+ TCriteria Parse(ISpecification specification);
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/NoneSpecification.cs b/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/NoneSpecification.cs
new file mode 100644
index 0000000000..fc2e74fdd6
--- /dev/null
+++ b/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/NoneSpecification.cs
@@ -0,0 +1,22 @@
+using System;
+using System.Linq.Expressions;
+
+namespace Volo.Abp.Specifications
+{
+ ///
+ /// Represents the specification that can be satisfied by the given object
+ /// in no circumstance.
+ ///
+ /// The type of the object to which the specification is applied.
+ public sealed class NoneSpecification : Specification
+ {
+ ///
+ /// Gets the LINQ expression which represents the current specification.
+ ///
+ /// The LINQ expression.
+ public override Expression> ToExpression()
+ {
+ return o => false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/NotSpecification.cs b/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/NotSpecification.cs
new file mode 100644
index 0000000000..34658885cb
--- /dev/null
+++ b/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/NotSpecification.cs
@@ -0,0 +1,36 @@
+using System;
+using System.Linq.Expressions;
+
+namespace Volo.Abp.Specifications
+{
+ ///
+ /// Represents the specification which indicates the semantics opposite to the given specification.
+ ///
+ /// The type of the object to which the specification is applied.
+ public class NotSpecification : Specification
+ {
+ private readonly ISpecification _specification;
+
+ ///
+ /// Initializes a new instance of class.
+ ///
+ /// The specification to be reversed.
+ public NotSpecification(ISpecification specification)
+ {
+ _specification = specification;
+ }
+
+ ///
+ /// Gets the LINQ expression which represents the current specification.
+ ///
+ /// The LINQ expression.
+ public override Expression> ToExpression()
+ {
+ var expression = _specification.ToExpression();
+ return Expression.Lambda>(
+ Expression.Not(expression.Body),
+ expression.Parameters
+ );
+ }
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/OrSpecification.cs b/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/OrSpecification.cs
new file mode 100644
index 0000000000..a6c85f8326
--- /dev/null
+++ b/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/OrSpecification.cs
@@ -0,0 +1,31 @@
+using System;
+using System.Linq.Expressions;
+
+namespace Volo.Abp.Specifications
+{
+ ///
+ /// Represents the combined specification which indicates that either of the given
+ /// specification should be satisfied by the given object.
+ ///
+ /// The type of the object to which the specification is applied.
+ public class OrSpecification : CompositeSpecification
+ {
+ ///
+ /// Initializes a new instance of class.
+ ///
+ /// The first specification.
+ /// The second specification.
+ public OrSpecification(ISpecification left, ISpecification right) : base(left, right)
+ {
+ }
+
+ ///
+ /// Gets the LINQ expression which represents the current specification.
+ ///
+ /// The LINQ expression.
+ public override Expression> ToExpression()
+ {
+ return Left.ToExpression().Or(Right.ToExpression());
+ }
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/ParameterRebinder.cs b/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/ParameterRebinder.cs
new file mode 100644
index 0000000000..603e80cf69
--- /dev/null
+++ b/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/ParameterRebinder.cs
@@ -0,0 +1,37 @@
+using System.Collections.Generic;
+using System.Linq.Expressions;
+
+namespace Volo.Abp.Specifications
+{
+ ///
+ /// Represents the parameter rebinder used for rebinding the parameters
+ /// for the given expressions. This is part of the solution which solves
+ /// the expression parameter problem when going to Entity Framework.
+ /// For more information about this solution please refer to http://blogs.msdn.com/b/meek/archive/2008/05/02/linq-to-entities-combining-predicates.aspx.
+ ///
+ internal class ParameterRebinder : ExpressionVisitor
+ {
+ private readonly Dictionary _map;
+
+ internal ParameterRebinder(Dictionary map)
+ {
+ _map = map ?? new Dictionary();
+ }
+
+ internal static Expression ReplaceParameters(Dictionary map,
+ Expression exp)
+ {
+ return new ParameterRebinder(map).Visit(exp);
+ }
+
+ protected override Expression VisitParameter(ParameterExpression p)
+ {
+ if (_map.TryGetValue(p, out var replacement))
+ {
+ p = replacement;
+ }
+
+ return base.VisitParameter(p);
+ }
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/Specification.cs b/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/Specification.cs
new file mode 100644
index 0000000000..b1ab72caea
--- /dev/null
+++ b/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/Specification.cs
@@ -0,0 +1,38 @@
+using System;
+using System.Linq.Expressions;
+
+namespace Volo.Abp.Specifications
+{
+ ///
+ /// Represents the base class for specifications.
+ ///
+ /// The type of the object to which the specification is applied.
+ public abstract class Specification : ISpecification
+ {
+ ///
+ /// Returns a value which indicates whether the specification
+ /// is satisfied by the given object.
+ ///
+ /// The object to which the specification is applied.
+ /// True if the specification is satisfied, otherwise false.
+ public virtual bool IsSatisfiedBy(T obj)
+ {
+ return ToExpression().Compile()(obj);
+ }
+
+ ///
+ /// Gets the LINQ expression which represents the current specification.
+ ///
+ /// The LINQ expression.
+ public abstract Expression> ToExpression();
+
+ ///
+ /// Implicitly converts a specification to expression.
+ ///
+ ///
+ public static implicit operator Expression>(Specification specification)
+ {
+ return specification.ToExpression();
+ }
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/SpecificationExtensions.cs b/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/SpecificationExtensions.cs
new file mode 100644
index 0000000000..229b3e7a72
--- /dev/null
+++ b/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/SpecificationExtensions.cs
@@ -0,0 +1,72 @@
+using JetBrains.Annotations;
+
+namespace Volo.Abp.Specifications
+{
+ public static class SpecificationExtensions
+ {
+ ///
+ /// Combines the current specification instance with another specification instance
+ /// and returns the combined specification which represents that both the current and
+ /// the given specification must be satisfied by the given object.
+ ///
+ /// The specification
+ /// The specification instance with which the current specification is combined.
+ /// The combined specification instance.
+ public static ISpecification And([NotNull] this ISpecification specification,
+ [NotNull] ISpecification other)
+ {
+ Check.NotNull(specification, nameof(specification));
+ Check.NotNull(other, nameof(other));
+
+ return new AndSpecification(specification, other);
+ }
+
+ ///
+ /// Combines the current specification instance with another specification instance
+ /// and returns the combined specification which represents that either the current or
+ /// the given specification should be satisfied by the given object.
+ ///
+ /// The specification
+ /// The specification instance with which the current specification
+ /// is combined.
+ /// The combined specification instance.
+ public static ISpecification Or([NotNull] this ISpecification specification,
+ [NotNull] ISpecification other)
+ {
+ Check.NotNull(specification, nameof(specification));
+ Check.NotNull(other, nameof(other));
+
+ return new OrSpecification(specification, other);
+ }
+
+ ///
+ /// Combines the current specification instance with another specification instance
+ /// and returns the combined specification which represents that the current specification
+ /// should be satisfied by the given object but the specified specification should not.
+ ///
+ /// The specification
+ /// The specification instance with which the current specification
+ /// is combined.
+ /// The combined specification instance.
+ public static ISpecification AndNot([NotNull] this ISpecification specification,
+ [NotNull] ISpecification other)
+ {
+ Check.NotNull(specification, nameof(specification));
+ Check.NotNull(other, nameof(other));
+
+ return new AndNotSpecification(specification, other);
+ }
+
+ ///
+ /// Reverses the current specification instance and returns a specification which represents
+ /// the semantics opposite to the current specification.
+ ///
+ /// The reversed specification instance.
+ public static ISpecification Not([NotNull] this ISpecification specification)
+ {
+ Check.NotNull(specification, nameof(specification));
+
+ return new NotSpecification(specification);
+ }
+ }
+}
\ No newline at end of file
diff --git a/framework/test/Volo.Abp.Specifications.Tests/Volo.Abp.Specifications.Tests.csproj b/framework/test/Volo.Abp.Specifications.Tests/Volo.Abp.Specifications.Tests.csproj
new file mode 100644
index 0000000000..6f2cbe09f8
--- /dev/null
+++ b/framework/test/Volo.Abp.Specifications.Tests/Volo.Abp.Specifications.Tests.csproj
@@ -0,0 +1,21 @@
+
+
+
+ netcoreapp2.2
+ latest
+ Volo.Abp.Specifications.Tests
+ Volo.Abp.Specifications.Tests
+ true
+ false
+ false
+ false
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/framework/test/Volo.Abp.Specifications.Tests/Volo/Abp/Specifications/Specifications_Tests.cs b/framework/test/Volo.Abp.Specifications.Tests/Volo/Abp/Specifications/Specifications_Tests.cs
new file mode 100644
index 0000000000..dbe4fc6b5d
--- /dev/null
+++ b/framework/test/Volo.Abp.Specifications.Tests/Volo/Abp/Specifications/Specifications_Tests.cs
@@ -0,0 +1,191 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Linq.Expressions;
+using System.Text;
+using Shouldly;
+using Xunit;
+
+namespace Volo.Abp.Specifications
+{
+ public class Specification_Tests
+ {
+ private readonly IQueryable _customers;
+
+ public Specification_Tests()
+ {
+ _customers = new List
+ {
+ new Customer("John", 17, 47000, "England"),
+ new Customer("Tuana", 2, 500, "Turkey"),
+ new Customer("Martin", 43, 16000, "USA"),
+ new Customer("Lee", 32, 24502, "China"),
+ new Customer("Douglas", 42, 42000, "England"),
+ new Customer("Abelard", 14, 2332, "German"),
+ new Customer("Neo", 16, 120000, "USA"),
+ new Customer("Daan", 39, 6000, "Netherlands"),
+ new Customer("Alessandro", 22, 8271, "Italy"),
+ new Customer("Noah", 33, 82192, "Belgium")
+ }.AsQueryable();
+ }
+
+ [Fact]
+ public void Any_Should_Return_All()
+ {
+ _customers
+ .Where(new AnySpecification()) //Implicitly converted to Expression!
+ .Count()
+ .ShouldBe(_customers.Count());
+ }
+
+ [Fact]
+ public void None_Should_Return_Empty()
+ {
+ _customers
+ .Where(new NoneSpecification().ToExpression())
+ .Count()
+ .ShouldBe(0);
+ }
+
+ [Fact]
+ public void Not_Should_Return_Reverse_Result()
+ {
+ _customers
+ .Where(new EuropeanCustomerSpecification().Not().ToExpression())
+ .Count()
+ .ShouldBe(3);
+ }
+
+ [Fact]
+ public void Should_Support_Native_Expressions_And_Combinations()
+ {
+ _customers
+ .Where(new ExpressionSpecification(c => c.Age >= 18).ToExpression())
+ .Count()
+ .ShouldBe(6);
+
+ _customers
+ .Where(new EuropeanCustomerSpecification().And(new ExpressionSpecification(c => c.Age >= 18)).ToExpression())
+ .Count()
+ .ShouldBe(4);
+ }
+
+ [Fact]
+ public void CustomSpecification_Test()
+ {
+ _customers
+ .Where(new EuropeanCustomerSpecification().ToExpression())
+ .Count()
+ .ShouldBe(7);
+
+ _customers
+ .Where(new Age18PlusCustomerSpecification().ToExpression())
+ .Count()
+ .ShouldBe(6);
+
+ _customers
+ .Where(new BalanceCustomerSpecification(10000, 30000).ToExpression())
+ .Count()
+ .ShouldBe(2);
+
+ _customers
+ .Where(new PremiumCustomerSpecification().ToExpression())
+ .Count()
+ .ShouldBe(3);
+ }
+
+ [Fact]
+ public void IsSatisfiedBy_Tests()
+ {
+ new PremiumCustomerSpecification().IsSatisfiedBy(new Customer("David", 49, 55000, "USA")).ShouldBeTrue();
+
+ new PremiumCustomerSpecification().IsSatisfiedBy(new Customer("David", 49, 200, "USA")).ShouldBeFalse();
+ new PremiumCustomerSpecification().IsSatisfiedBy(new Customer("David", 12, 55000, "USA")).ShouldBeFalse();
+ }
+
+ [Fact]
+ public void CustomSpecification_Composite_Tests()
+ {
+ _customers
+ .Where(new EuropeanCustomerSpecification().And(new Age18PlusCustomerSpecification()).ToExpression())
+ .Count()
+ .ShouldBe(4);
+
+ _customers
+ .Where(new EuropeanCustomerSpecification().Not().And(new Age18PlusCustomerSpecification()).ToExpression())
+ .Count()
+ .ShouldBe(2);
+
+ _customers
+ .Where(new Age18PlusCustomerSpecification().AndNot(new EuropeanCustomerSpecification()).ToExpression())
+ .Count()
+ .ShouldBe(2);
+ }
+
+ private class Customer
+ {
+ public string Name { get; private set; }
+
+ public byte Age { get; private set; }
+
+ public long Balance { get; private set; }
+
+ public string Location { get; private set; }
+
+ public Customer(string name, byte age, long balance, string location)
+ {
+ Name = name;
+ Age = age;
+ Balance = balance;
+ Location = location;
+ }
+ }
+
+ private class EuropeanCustomerSpecification : Specification
+ {
+ public override Expression> ToExpression()
+ {
+ return c => c.Location == "England" ||
+ c.Location == "Turkey" ||
+ c.Location == "German" ||
+ c.Location == "Netherlands" ||
+ c.Location == "Italy" ||
+ c.Location == "Belgium";
+ }
+ }
+
+ private class Age18PlusCustomerSpecification : Specification
+ {
+ public override Expression> ToExpression()
+ {
+ return c => c.Age >= 18;
+ }
+ }
+
+ private class BalanceCustomerSpecification : Specification
+ {
+ public int MinBalance { get; }
+
+ public int MaxBalance { get; }
+
+ public BalanceCustomerSpecification(int minBalance, int maxBalance)
+ {
+ MinBalance = minBalance;
+ MaxBalance = maxBalance;
+ }
+
+ public override Expression> ToExpression()
+ {
+ return c => c.Balance >= MinBalance && c.Balance <= MaxBalance;
+ }
+ }
+
+ private class PremiumCustomerSpecification : AndSpecification
+ {
+ public PremiumCustomerSpecification()
+ : base(new Age18PlusCustomerSpecification(), new BalanceCustomerSpecification(20000, int.MaxValue))
+ {
+ }
+ }
+ }
+}