Finally, prepared Specification can be used for objects verification.
According to pattern, base method is IsSatisfiedBy
.
// All array elements must be greater than 0
var allGreaterThanSpec = Specification
.All<int[], int>(Specification
.GreaterThan(0));
var result = allGreaterThanSpec
.IsSatisfiedBy(new[] {1, 2, 10, 15, 123}); // true
AsPredicate
All Specifications can be transform to Func<T, bool>
with AsPredicate
extension method.
var greaterThanSpec = Specification
.GreaterThan(0);
var result = new[] {-1, 2, 0, 15, -123}
.Where(greaterThanSpec.AsPredicate())
.ToArray(); // int[2] { 2, 15 }
Func<T, bool> operator
All built-in Specifications contains implicit Func<T, bool>
operator.
var greaterThanSpec =
new GreaterThanSpecification<int>(0);
new[] {-1, 2, 0, 15, -123}
.Where(greaterThanSpec);
Validation
Specifications that they implement IValidationSpecification<T>
interface, can be used in validation scenarios.
// All array elements must be greater than 0
var allGreaterThanSpec = Specification
.All<int[], int>(Specification
.GreaterThan(0));
var overall = allGreaterThanSpec
.IsSatisfiedBy(new[] { -1, 2, 0, 15, -123 }, out var result); // false
result.ToString();
// One or more elements are not specified
// [0] Object is lower than or equal to[0]
// [2] Object is lower than or equal to[0]
// [4] Object is lower than or equal to[0]
Full content of SpecificationResult
object is described in Concept section.
Linq
ILinqSpecification<T>
types, can be used as Linq expressions, using GetExpression
method.
// Find Mr. Smith
ILinqSpecification<Customer> spec = Specification
.Equal<Customer, string>(c => c.LastName, "Smith");
using (var ctx = new EfCoreDbContext())
{
var customer = ctx
.Customers
.FirstOrDefault(spec.GetExpression());
}
AsExpression
Every Specification can be converted to Linq expression with AsExpression
extension method.
Specification that they not implement ILinqSpecification<T>
interface, can be converted by SpecificationAdapter class.
public class VipCustomerSpecification :
ISpecification<Customer>
{
public bool IsSatisfiedBy(Customer candidate)
{
return candidate
.Comments
.Split(new char[] {';'})
.Contains("Vip");
}
}
// Find VIPs
var vipSpec = new VipCustomerSpecification();
using (var ctx = new EfCoreDbContext())
{
var customer = ctx
.Customers
.Where(vipSpec.AsExpression());
}
BE AWARE Some Specifications converted by AsExpression
, may cause exceptions when using LinqToEntities (LinqToSql).
More information in EF support section.
Expression operator
All built-in Specifications contains implicit Expression<Func<T, bool>>
operator and explicit Expression
operator.
var validEmailSpec = new EmailSpecification();
Expression<Func<string, bool>> expression = validEmailSpec;
Expression baseExpression = (Expression) validEmailSpec;
LinqToEntities
As mentioned above, some Specifications are not working at all with LinqToEntities (eg Specifications with Regex - Match, Email, CreditCard).
Some Specifications support partially LinqToEntities. These specifications, usually has special linqToEntities
flag.
// Specification check if collection is null or empty.
var notEmptyItemsSpec = Specification
.NotEmpty<Customer, ICollection<Item>>(c => c.Items);
var notEmptyItemsSpecEFSupport = Specification
.ForProperty<Customer, ICollection<Item>>(c => c.Items,
// Specification do not check if collection is null. Only if empty.
new EmptySpecification<ICollection<Item>>(linqToEntities: true).Not());
using (var ctx = new EfDbContext())
{
var customer = ctx
.Customers
// NotSupportedException: Only primitive types, enumeration types and entity types are supported.
//.Where(notEmptyItemsSpec.GetExpression()).ToArray();
// Without Exception!
.Where(notEmptyItemsSpecEFSupport.GetExpression()).ToArray();
}
Fluent API is prepared for global LinqToEntities support:
Specification.LinqToEntities
flag should be set to true.
After that, all Specifications with linqToEntities
support, are built with this flag.
// Unblock 'LinqToEntities' support globally.
Specification.LinqToEntities = true;
var notEmptyItemsSpec = Specification
.NotEmpty<Customer, ICollection<Item>>(c => c.Items);
using (var ctx = new EfDbContext())
{
var customer = ctx
.Customers
// Without Exception!
.Where(notEmptyItemsSpec.GetExpression()).ToArray();
}
Specification.LinqToEntities
flag can be set back to false in any time,
but after that, every Specification will be built without LinqToEntities support.