Main class of package - Specification<T> - should be inherited by your own classes in order to use its functionality and extensions.
Example of basic usage:
// Some model.publicclassBook{publicstring Title { get; set; }publicstring Author { get; set; }publicint Year { get; set; }publicint Price { get; set; }}// Specification matching only books written after some year.publicclassBookYearIsGreaterThanSpec:Specification<Book>{publicint Year { get; }publicBookYearIsGreaterThanSpec(int year) => Year = year; // The only method to be overridden - ToExpression(). // It should return expression that gives needed condition.publicoverrideExpression<Func<Book,bool>> ToExpression() {return x =>x.Year> Year; }}// Specification matching only books which have price less than some value.publicclassBookPriceIsLessThanSpec:Specification<Book>{publicint Price { get; }publicBookPriceIsLessThanSpec(int price)=> Price = price;publicoverrideExpression<Func<Book,bool>> ToExpression() {return x =>x.Price< Price; }}
Use specifications to filter models collection:
// Books collection.var books =newList<Book>{newBook { Title ="Don Quixote", Author ="Miguel de Cervantes", Year =1605, Price =16 },newBook { Title ="The Brothers Karamazov", Author ="Fyodor Dostoevsky", Year =1879, Price =13 },newBook { Title ="The Lord of the Rings", Author ="J.R.R. Tolkien", Year =1954, Price =32 },newBook { Title ="One Hundred Years of Solitude", Author ="Gabriel Garcia Marquez", Year =1967, Price =21 },};// Create specification instances.var newBookSpec =newBookYearIsGreaterThanSpec(1950);var cheapBookSpec =newBookPriceIsLessThanSpec(25);// Then filter books collection.IEnumerable<Book> newBooks = books .Where(x =>newBookSpec.IsSatisfiedBy(x)); // Gives "The Lord of the Rings" and "One Hundred Years of Solitude".IEnumerable<Book> cheapBooks = books .Where(x => cheapBookSpec.IsSatisfiedBy(x)); // Gives "Don Quixote", "The Brothers Karamazov" and "One Hundred Years of Solitude".
// You also can combine two specifications.// For example: Cheap and "new" books.Specification<Book> cheapAndNewBooksSpec =cheapBookSpec.And(newBookSpec);IEnumerable<Book> cheapAndNewBooks = books .Where(x =>cheapAndNewBooksSpec.IsSatisfiedBy(x)); // Gives only "One Hundred Years of Solitude".// For example: Cheap and "old" books.Specification<Book> cheapAndOldBooksSpec =cheapBookSpec.AndNot(newBookSpec);IEnumerable<Book> cheapAndOldBooks = books .Where(x =>cheapAndOldBooksSpec.IsSatisfiedBy(x)); // Gives only "Don Quixote" and "The Brothers Karamazov".
The whole list of Specification<T> extensions is provided below:
Additionally two predefined specifications are available:
Entity Framework
Structr.Specifications may be helpful with Entity Framework 6 or Entity Framework Core. Use ToExpression() method with Where() for filtering entities in DbContext:
// Create specification instances.var newBookSpec =newBookYearIsGreaterThanSpec(1950);var cheapBookSpec =newBookPriceIsLessThanSpec(25);// Then filter books entities.List<Book> newBooks =dbContext.Books .Where(x =>newBookSpec.ToExpression(x)) .ToList(); // Gives "The Lord of the Rings" and "One Hundred Years of Solitude".List<Book> cheapBooks = books .Where(x =>cheapBookSpec.ToExpression(x)) .ToList(); // Gives "Don Quixote", "The Brothers Karamazov" and "One Hundred Years of Solitude".