Merge pull request #1103 from abpframework/maliming/Specifications

Add Volo.Abp.Specifications project.
pull/1108/head
Halil İbrahim Kalkan 7 years ago committed by GitHub
commit 8dadc9db88
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

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

@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\common.props" />
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<AssemblyName>Volo.Abp.Specifications</AssemblyName>
<PackageId>Volo.Abp.Specifications</PackageId>
<AssetTargetFallback>$(AssetTargetFallback);portable-net45+win8+wp8+wpa81;</AssetTargetFallback>
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
<RootNamespace />
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Volo.Abp.Core\Volo.Abp.Core.csproj" />
</ItemGroup>
</Project>

@ -0,0 +1,9 @@
using Volo.Abp.Modularity;
namespace Volo.Abp.Specifications
{
public class AbpSpecificationsModule : AbpModule
{
}
}

@ -0,0 +1,36 @@
using System;
using System.Linq.Expressions;
namespace Volo.Abp.Specifications
{
/// <summary>
/// Represents the combined specification which indicates that the first specification
/// can be satisifed by the given object whereas the second one cannot.
/// </summary>
/// <typeparam name="T">The type of the object to which the specification is applied.</typeparam>
public class AndNotSpecification<T> : CompositeSpecification<T>
{
/// <summary>
/// Constructs a new instance of <see cref="AndNotSpecification{T}"/> class.
/// </summary>
/// <param name="left">The first specification.</param>
/// <param name="right">The second specification.</param>
public AndNotSpecification(ISpecification<T> left, ISpecification<T> right) : base(left, right)
{
}
/// <summary>
/// Gets the LINQ expression which represents the current specification.
/// </summary>
/// <returns>The LINQ expression.</returns>
public override Expression<Func<T, bool>> ToExpression()
{
var rightExpression = Right.ToExpression();
var bodyNot = Expression.Not(rightExpression.Body);
var bodyNotExpression = Expression.Lambda<Func<T, bool>>(bodyNot, rightExpression.Parameters);
return Left.ToExpression().And(bodyNotExpression);
}
}
}

@ -0,0 +1,31 @@
using System;
using System.Linq.Expressions;
namespace Volo.Abp.Specifications
{
/// <summary>
/// Represents the combined specification which indicates that both of the given
/// specifications should be satisfied by the given object.
/// </summary>
/// <typeparam name="T">The type of the object to which the specification is applied.</typeparam>
public class AndSpecification<T> : CompositeSpecification<T>
{
/// <summary>
/// Constructs a new instance of <see cref="AndSpecification{T}"/> class.
/// </summary>
/// <param name="left">The first specification.</param>
/// <param name="right">The second specification.</param>
public AndSpecification(ISpecification<T> left, ISpecification<T> right) : base(left, right)
{
}
/// <summary>
/// Gets the LINQ expression which represents the current specification.
/// </summary>
/// <returns>The LINQ expression.</returns>
public override Expression<Func<T, bool>> ToExpression()
{
return Left.ToExpression().And(Right.ToExpression());
}
}
}

@ -0,0 +1,22 @@
using System;
using System.Linq.Expressions;
namespace Volo.Abp.Specifications
{
/// <summary>
/// Represents the specification that can be satisfied by the given object
/// in any circumstance.
/// </summary>
/// <typeparam name="T">The type of the object to which the specification is applied.</typeparam>
public sealed class AnySpecification<T> : Specification<T>
{
/// <summary>
/// Gets the LINQ expression which represents the current specification.
/// </summary>
/// <returns>The LINQ expression.</returns>
public override Expression<Func<T, bool>> ToExpression()
{
return o => true;
}
}
}

@ -0,0 +1,30 @@
namespace Volo.Abp.Specifications
{
/// <summary>
/// Represents the base class for composite specifications.
/// </summary>
/// <typeparam name="T">The type of the object to which the specification is applied.</typeparam>
public abstract class CompositeSpecification<T> : Specification<T>, ICompositeSpecification<T>
{
/// <summary>
/// Constructs a new instance of <see cref="CompositeSpecification{T}"/> class.
/// </summary>
/// <param name="left">The first specification.</param>
/// <param name="right">The second specification.</param>
protected CompositeSpecification(ISpecification<T> left, ISpecification<T> right)
{
Left = left;
Right = right;
}
/// <summary>
/// Gets the first specification.
/// </summary>
public ISpecification<T> Left { get; }
/// <summary>
/// Gets the second specification.
/// </summary>
public ISpecification<T> Right { get; }
}
}

@ -0,0 +1,55 @@
using System;
using System.Linq;
using System.Linq.Expressions;
namespace Volo.Abp.Specifications
{
/// <summary>
/// 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.
/// </summary>
public static class ExpressionFuncExtender
{
private static Expression<T> Compose<T>(this Expression<T> first, Expression<T> second,
Func<Expression, Expression, Expression> 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<T>(merge(first.Body, secondBody), first.Parameters);
}
/// <summary>
/// Combines two given expressions by using the AND semantics.
/// </summary>
/// <typeparam name="T">The type of the object.</typeparam>
/// <param name="first">The first part of the expression.</param>
/// <param name="second">The second part of the expression.</param>
/// <returns>The combined expression.</returns>
public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> first,
Expression<Func<T, bool>> second)
{
return first.Compose(second, Expression.AndAlso);
}
/// <summary>
/// Combines two given expressions by using the OR semantics.
/// </summary>
/// <typeparam name="T">The type of the object.</typeparam>
/// <param name="first">The first part of the expression.</param>
/// <param name="second">The second part of the expression.</param>
/// <returns>The combined expression.</returns>
public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> first,
Expression<Func<T, bool>> second)
{
return first.Compose(second, Expression.OrElse);
}
}
}

@ -0,0 +1,34 @@
using System;
using System.Linq.Expressions;
namespace Volo.Abp.Specifications
{
/// <summary>
/// Represents the specification which is represented by the corresponding
/// LINQ expression.
/// </summary>
/// <typeparam name="T">The type of the object to which the specification is applied.</typeparam>
public class ExpressionSpecification<T> : Specification<T>
{
private readonly Expression<Func<T, bool>> _expression;
/// <summary>
/// Initializes a new instance of <c>ExpressionSpecification&lt;T&gt;</c> class.
/// </summary>
/// <param name="expression">The LINQ expression which represents the current
/// specification.</param>
public ExpressionSpecification(Expression<Func<T, bool>> expression)
{
_expression = expression;
}
/// <summary>
/// Gets the LINQ expression which represents the current specification.
/// </summary>
/// <returns>The LINQ expression.</returns>
public override Expression<Func<T, bool>> ToExpression()
{
return _expression;
}
}
}

@ -0,0 +1,19 @@
namespace Volo.Abp.Specifications
{
/// <summary>
/// Represents that the implemented classes are composite specifications.
/// </summary>
/// <typeparam name="T">The type of the object to which the specification is applied.</typeparam>
public interface ICompositeSpecification<T> : ISpecification<T>
{
/// <summary>
/// Gets the left side of the specification.
/// </summary>
ISpecification<T> Left { get; }
/// <summary>
/// Gets the right side of the specification.
/// </summary>
ISpecification<T> Right { get; }
}
}

@ -0,0 +1,28 @@
using System;
using System.Linq.Expressions;
namespace Volo.Abp.Specifications
{
/// <summary>
/// Represents that the implemented classes are specifications. For more
/// information about the specification pattern, please refer to
/// http://martinfowler.com/apsupp/spec.pdf.
/// </summary>
/// <typeparam name="T">The type of the object to which the specification is applied.</typeparam>
public interface ISpecification<T>
{
/// <summary>
/// Returns a <see cref="bool"/> value which indicates whether the specification
/// is satisfied by the given object.
/// </summary>
/// <param name="obj">The object to which the specification is applied.</param>
/// <returns>True if the specification is satisfied, otherwise false.</returns>
bool IsSatisfiedBy(T obj);
/// <summary>
/// Gets the LINQ expression which represents the current specification.
/// </summary>
/// <returns>The LINQ expression.</returns>
Expression<Func<T, bool>> ToExpression();
}
}

@ -0,0 +1,19 @@
namespace Volo.Abp.Specifications
{
/// <summary>
/// Represents that the implemented classes are specification parsers that
/// parses the given specification to a domain specific criteria object, such
/// as the <c>ICriteria</c> instance in NHibernate.
/// </summary>
/// <typeparam name="TCriteria">The type of the domain specific criteria.</typeparam>
public interface ISpecificationParser<out TCriteria>
{
/// <summary>
/// Parses the given specification to a domain specific criteria object.
/// </summary>
/// <typeparam name="T">The type of the object to which the specification is applied.</typeparam>
/// <param name="specification">The specified specification instance.</param>
/// <returns>The instance of the domain specific criteria.</returns>
TCriteria Parse<T>(ISpecification<T> specification);
}
}

@ -0,0 +1,22 @@
using System;
using System.Linq.Expressions;
namespace Volo.Abp.Specifications
{
/// <summary>
/// Represents the specification that can be satisfied by the given object
/// in no circumstance.
/// </summary>
/// <typeparam name="T">The type of the object to which the specification is applied.</typeparam>
public sealed class NoneSpecification<T> : Specification<T>
{
/// <summary>
/// Gets the LINQ expression which represents the current specification.
/// </summary>
/// <returns>The LINQ expression.</returns>
public override Expression<Func<T, bool>> ToExpression()
{
return o => false;
}
}
}

@ -0,0 +1,36 @@
using System;
using System.Linq.Expressions;
namespace Volo.Abp.Specifications
{
/// <summary>
/// Represents the specification which indicates the semantics opposite to the given specification.
/// </summary>
/// <typeparam name="T">The type of the object to which the specification is applied.</typeparam>
public class NotSpecification<T> : Specification<T>
{
private readonly ISpecification<T> _specification;
/// <summary>
/// Initializes a new instance of <see cref="NotSpecification{T}"/> class.
/// </summary>
/// <param name="specification">The specification to be reversed.</param>
public NotSpecification(ISpecification<T> specification)
{
_specification = specification;
}
/// <summary>
/// Gets the LINQ expression which represents the current specification.
/// </summary>
/// <returns>The LINQ expression.</returns>
public override Expression<Func<T, bool>> ToExpression()
{
var expression = _specification.ToExpression();
return Expression.Lambda<Func<T, bool>>(
Expression.Not(expression.Body),
expression.Parameters
);
}
}
}

@ -0,0 +1,31 @@
using System;
using System.Linq.Expressions;
namespace Volo.Abp.Specifications
{
/// <summary>
/// Represents the combined specification which indicates that either of the given
/// specification should be satisfied by the given object.
/// </summary>
/// <typeparam name="T">The type of the object to which the specification is applied.</typeparam>
public class OrSpecification<T> : CompositeSpecification<T>
{
/// <summary>
/// Initializes a new instance of <see cref="OrSpecification{T}"/> class.
/// </summary>
/// <param name="left">The first specification.</param>
/// <param name="right">The second specification.</param>
public OrSpecification(ISpecification<T> left, ISpecification<T> right) : base(left, right)
{
}
/// <summary>
/// Gets the LINQ expression which represents the current specification.
/// </summary>
/// <returns>The LINQ expression.</returns>
public override Expression<Func<T, bool>> ToExpression()
{
return Left.ToExpression().Or(Right.ToExpression());
}
}
}

@ -0,0 +1,37 @@
using System.Collections.Generic;
using System.Linq.Expressions;
namespace Volo.Abp.Specifications
{
/// <summary>
/// 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.
/// </summary>
internal class ParameterRebinder : ExpressionVisitor
{
private readonly Dictionary<ParameterExpression, ParameterExpression> _map;
internal ParameterRebinder(Dictionary<ParameterExpression, ParameterExpression> map)
{
_map = map ?? new Dictionary<ParameterExpression, ParameterExpression>();
}
internal static Expression ReplaceParameters(Dictionary<ParameterExpression, ParameterExpression> 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);
}
}
}

@ -0,0 +1,38 @@
using System;
using System.Linq.Expressions;
namespace Volo.Abp.Specifications
{
/// <summary>
/// Represents the base class for specifications.
/// </summary>
/// <typeparam name="T">The type of the object to which the specification is applied.</typeparam>
public abstract class Specification<T> : ISpecification<T>
{
/// <summary>
/// Returns a <see cref="bool"/> value which indicates whether the specification
/// is satisfied by the given object.
/// </summary>
/// <param name="obj">The object to which the specification is applied.</param>
/// <returns>True if the specification is satisfied, otherwise false.</returns>
public virtual bool IsSatisfiedBy(T obj)
{
return ToExpression().Compile()(obj);
}
/// <summary>
/// Gets the LINQ expression which represents the current specification.
/// </summary>
/// <returns>The LINQ expression.</returns>
public abstract Expression<Func<T, bool>> ToExpression();
/// <summary>
/// Implicitly converts a specification to expression.
/// </summary>
/// <param name="specification"></param>
public static implicit operator Expression<Func<T, bool>>(Specification<T> specification)
{
return specification.ToExpression();
}
}
}

@ -0,0 +1,72 @@
using JetBrains.Annotations;
namespace Volo.Abp.Specifications
{
public static class SpecificationExtensions
{
/// <summary>
/// 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.
/// </summary>
/// <param name="specification">The specification</param>
/// <param name="other">The specification instance with which the current specification is combined.</param>
/// <returns>The combined specification instance.</returns>
public static ISpecification<T> And<T>([NotNull] this ISpecification<T> specification,
[NotNull] ISpecification<T> other)
{
Check.NotNull(specification, nameof(specification));
Check.NotNull(other, nameof(other));
return new AndSpecification<T>(specification, other);
}
/// <summary>
/// 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.
/// </summary>
/// <param name="specification">The specification</param>
/// <param name="other">The specification instance with which the current specification
/// is combined.</param>
/// <returns>The combined specification instance.</returns>
public static ISpecification<T> Or<T>([NotNull] this ISpecification<T> specification,
[NotNull] ISpecification<T> other)
{
Check.NotNull(specification, nameof(specification));
Check.NotNull(other, nameof(other));
return new OrSpecification<T>(specification, other);
}
/// <summary>
/// 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.
/// </summary>
/// <param name="specification">The specification</param>
/// <param name="other">The specification instance with which the current specification
/// is combined.</param>
/// <returns>The combined specification instance.</returns>
public static ISpecification<T> AndNot<T>([NotNull] this ISpecification<T> specification,
[NotNull] ISpecification<T> other)
{
Check.NotNull(specification, nameof(specification));
Check.NotNull(other, nameof(other));
return new AndNotSpecification<T>(specification, other);
}
/// <summary>
/// Reverses the current specification instance and returns a specification which represents
/// the semantics opposite to the current specification.
/// </summary>
/// <returns>The reversed specification instance.</returns>
public static ISpecification<T> Not<T>([NotNull] this ISpecification<T> specification)
{
Check.NotNull(specification, nameof(specification));
return new NotSpecification<T>(specification);
}
}
}

@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp2.2</TargetFramework>
<LangVersion>latest</LangVersion>
<AssemblyName>Volo.Abp.Specifications.Tests</AssemblyName>
<PackageId>Volo.Abp.Specifications.Tests</PackageId>
<GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles>
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
<RootNamespace />
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\AbpTestBase\AbpTestBase.csproj" />
<ProjectReference Include="..\..\src\Volo.Abp.Specifications\Volo.Abp.Specifications.csproj" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.0.1" />
</ItemGroup>
</Project>

@ -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<Customer> _customers;
public Specification_Tests()
{
_customers = new List<Customer>
{
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<Customer>()) //Implicitly converted to Expression!
.Count()
.ShouldBe(_customers.Count());
}
[Fact]
public void None_Should_Return_Empty()
{
_customers
.Where(new NoneSpecification<Customer>().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<Customer>(c => c.Age >= 18).ToExpression())
.Count()
.ShouldBe(6);
_customers
.Where(new EuropeanCustomerSpecification().And(new ExpressionSpecification<Customer>(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<Customer>
{
public override Expression<Func<Customer, bool>> 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<Customer>
{
public override Expression<Func<Customer, bool>> ToExpression()
{
return c => c.Age >= 18;
}
}
private class BalanceCustomerSpecification : Specification<Customer>
{
public int MinBalance { get; }
public int MaxBalance { get; }
public BalanceCustomerSpecification(int minBalance, int maxBalance)
{
MinBalance = minBalance;
MaxBalance = maxBalance;
}
public override Expression<Func<Customer, bool>> ToExpression()
{
return c => c.Balance >= MinBalance && c.Balance <= MaxBalance;
}
}
private class PremiumCustomerSpecification : AndSpecification<Customer>
{
public PremiumCustomerSpecification()
: base(new Age18PlusCustomerSpecification(), new BalanceCustomerSpecification(20000, int.MaxValue))
{
}
}
}
}
Loading…
Cancel
Save