Repository: navozenko/LinqSpecs Branch: master Commit: 0374db964085 Files: 33 Total size: 55.4 KB Directory structure: gitextract_i9vq50lx/ ├── .editorconfig ├── .gitattributes ├── .gitignore ├── LinqSpecs/ │ ├── AdHocSpecification.cs │ ├── FalseSpecification.cs │ ├── LinqSpecs.csproj │ ├── Operators/ │ │ ├── AndSpecification.cs │ │ ├── NotSpecification.cs │ │ └── OrSpecification.cs │ ├── Specification.cs │ ├── TrueSpecification.cs │ └── Utilities/ │ ├── ExpressionExtensions.cs │ ├── ExpressionParameterRebinder.cs │ └── HashCodeHelpers.cs ├── LinqSpecs.DatabaseTests/ │ ├── DomainModel/ │ │ ├── Customer.cs │ │ ├── Order.cs │ │ └── SampleDbContext.cs │ ├── LinqSpecs.DatabaseTests.csproj │ └── Tests.cs ├── LinqSpecs.UnitTests/ │ ├── AdHocSpecificationTests.cs │ ├── AssemblyTests.cs │ ├── FalseSpecificationTests.cs │ ├── Helpers/ │ │ └── SampleRepository.cs │ ├── LinqSpecs.UnitTests.csproj │ ├── Operators/ │ │ ├── AndSpecificationTests.cs │ │ ├── NotSpecificationTests.cs │ │ └── OrSpecificationTests.cs │ ├── SpecificationTests.cs │ └── TrueSpecificationTests.cs ├── LinqSpecs.sln ├── LinqSpecs.snk ├── README.md └── license.txt ================================================ FILE CONTENTS ================================================ ================================================ FILE: .editorconfig ================================================ # EditorConfig to support per-solution formatting # See: https://aka.ms/editorconfigdocs root = true [*] indent_style = space trim_trailing_whitespace = true charset = utf-8-bom insert_final_newline = true [*.{cs}] indent_size = 4 dotnet_sort_system_directives_first = true [*.{xml,config,*proj,nuspec,props,resx,targets,yml,tasks}] indent_size = 2 ================================================ FILE: .gitattributes ================================================ ############################################################################### # Set default behavior to automatically normalize line endings. ############################################################################### * text=auto ############################################################################### # Set default behavior for command prompt diff. # # This is need for earlier builds of msysgit that does not have it on by # default for csharp files. # Note: This is only used by command line ############################################################################### #*.cs diff=csharp ############################################################################### # Set the merge driver for project and solution files # # Merging from the command prompt will add diff markers to the files if there # are conflicts (Merging from VS is not affected by the settings below, in VS # the diff markers are never inserted). Diff markers may cause the following # file extensions to fail to load in VS. An alternative would be to treat # these files as binary and thus will always conflict and require user # intervention with every merge. To do so, just uncomment the entries below ############################################################################### #*.sln merge=binary #*.csproj merge=binary #*.vbproj merge=binary #*.vcxproj merge=binary #*.vcproj merge=binary #*.dbproj merge=binary #*.fsproj merge=binary #*.lsproj merge=binary #*.wixproj merge=binary #*.modelproj merge=binary #*.sqlproj merge=binary #*.wwaproj merge=binary ############################################################################### # behavior for image files # # image files are treated as binary by default. ############################################################################### #*.jpg binary #*.png binary #*.gif binary ############################################################################### # diff behavior for common document formats # # Convert binary document formats to text before diffing them. This feature # is only available from the command line. Turn it on by uncommenting the # entries below. ############################################################################### #*.doc diff=astextplain #*.DOC diff=astextplain #*.docx diff=astextplain #*.DOCX diff=astextplain #*.dot diff=astextplain #*.DOT diff=astextplain #*.pdf diff=astextplain #*.PDF diff=astextplain #*.rtf diff=astextplain #*.RTF diff=astextplain ================================================ FILE: .gitignore ================================================ ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. # User-specific files *.suo *.user *.userosscache *.sln.docstates # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs # Build results [Dd]ebug/ [Dd]ebugPublic/ [Rr]elease/ [Rr]eleases/ x64/ x86/ build/ bld/ [Bb]in/ [Oo]bj/ # Visual Studio 2015 cache/options directory .vs/ # MSTest test Results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* # NUNIT *.VisualState.xml TestResult.xml # Build Results of an ATL Project [Dd]ebugPS/ [Rr]eleasePS/ dlldata.c # DNX project.lock.json artifacts/ *_i.c *_p.c *_i.h *.ilk *.meta *.obj *.pch *.pdb *.pgc *.pgd *.rsp *.sbr *.tlb *.tli *.tlh *.tmp *.tmp_proj *.log *.vspscc *.vssscc .builds *.pidb *.svclog *.scc # Chutzpah Test files _Chutzpah* # Visual C++ cache files ipch/ *.aps *.ncb *.opensdf *.sdf *.cachefile # Visual Studio profiler *.psess *.vsp *.vspx # TFS 2012 Local Workspace $tf/ # Guidance Automation Toolkit *.gpState # ReSharper is a .NET coding add-in _ReSharper*/ *.[Rr]e[Ss]harper *.DotSettings.user # JustCode is a .NET coding add-in .JustCode # TeamCity is a build add-in _TeamCity* # DotCover is a Code Coverage Tool *.dotCover # NCrunch _NCrunch_* .*crunch*.local.xml # MightyMoose *.mm.* AutoTest.Net/ # Web workbench (sass) .sass-cache/ # Installshield output folder [Ee]xpress/ # DocProject is a documentation generator add-in DocProject/buildhelp/ DocProject/Help/*.HxT DocProject/Help/*.HxC DocProject/Help/*.hhc DocProject/Help/*.hhk DocProject/Help/*.hhp DocProject/Help/Html2 DocProject/Help/html # Click-Once directory publish/ # Publish Web Output *.[Pp]ublish.xml *.azurePubxml ## TODO: Comment the next line if you want to checkin your ## web deploy settings but do note that will include unencrypted ## passwords #*.pubxml *.publishproj # NuGet Packages *.nupkg # The packages folder can be ignored because of Package Restore **/packages/* # except build/, which is used as an MSBuild target. !**/packages/build/ # Uncomment if necessary however generally it will be regenerated when needed #!**/packages/repositories.config # Windows Azure Build Output csx/ *.build.csdef # Windows Store app package directory AppPackages/ # Visual Studio cache files # files ending in .cache can be ignored *.[Cc]ache # but keep track of directories ending in .cache !*.[Cc]ache/ # Others ClientBin/ [Ss]tyle[Cc]op.* ~$* *~ *.dbmdl *.dbproj.schemaview *.pfx *.publishsettings node_modules/ orleans.codegen.cs # RIA/Silverlight projects Generated_Code/ # Backup & report files from converting an old project file # to a newer Visual Studio version. Backup files are not needed, # because we have git ;-) _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML UpgradeLog*.htm # SQL Server files *.mdf *.ldf # Business Intelligence projects *.rdl.data *.bim.layout *.bim_*.settings # Microsoft Fakes FakesAssemblies/ # Node.js Tools for Visual Studio .ntvs_analysis.dat # Visual Studio 6 build log *.plg # Visual Studio 6 workspace options file *.opt # LightSwitch generated files GeneratedArtifacts/ _Pvt_Extensions/ ModelManifest.xml ================================================ FILE: LinqSpecs/AdHocSpecification.cs ================================================ using System; using System.Linq.Expressions; using LinqSpecs.Utilities; namespace LinqSpecs { /// /// Allows to write a without writing a class. /// public class AdHocSpecification : Specification { private readonly Lazy _predicateString; public Expression> Predicate { get; } /// /// Creates a custom ad-hoc from the given predicate expression. /// public AdHocSpecification(Expression> predicate) { Predicate = predicate ?? throw new ArgumentNullException(nameof(predicate)); _predicateString = new Lazy(() => Predicate.ToString()); } /// /// Returns an expression that defines this query. /// public override Expression> ToExpression() { return Predicate; } /// /// Determines whether the specified object is equal to the current object. /// public override bool Equals(object? other) { if (other is null) return false; if (ReferenceEquals(this, other)) return true; if (other is AdHocSpecification otherSpec) return _predicateString.Value.Equals(otherSpec._predicateString.Value); return false; } /// /// Returns a hash code for the current object. /// public override int GetHashCode() { return HashCodeHelpers.Combine(_predicateString.Value, GetType()); } } } ================================================ FILE: LinqSpecs/FalseSpecification.cs ================================================ using System; using System.Linq.Expressions; using LinqSpecs.Utilities; namespace LinqSpecs { /// /// Specification which is not satisfied by any object. /// public class FalseSpecification : Specification { /// /// Returns an expression that defines this query. /// public override Expression> ToExpression() { return x => false; } /// /// Determines whether the specified object is equal to the current object. /// public override bool Equals(object? other) { if (other is null) return false; if (ReferenceEquals(this, other)) return true; return GetType() == other.GetType(); } /// /// Returns a hash code for the current object. /// public override int GetHashCode() { return HashCodeHelpers.Combine(GetType()); } } } ================================================ FILE: LinqSpecs/LinqSpecs.csproj ================================================  netstandard1.0;netstandard2.0;netstandard2.1 8.0 enable 3.2.0 José F. Romaniello, Sergey Navozenko Copyright © 2010-2022 true MS-PL https://github.com/navozenko/LinqSpecs linq specification spec ddd https://github.com/navozenko/LinqSpecs true true 1591 true ../LinqSpecs.snk LinqSpecs is a framework that will help you to create specifications for LINQ queries. Supported platforms: - .NET Standard 1.0+ - .NET Framework 4.5+ - .NET Core 2.0+ - .NET 5.0+ ================================================ FILE: LinqSpecs/Operators/AndSpecification.cs ================================================ using System; using System.Linq.Expressions; using LinqSpecs.Utilities; namespace LinqSpecs.Operators { /// /// Combines two specifications by using logical AND operation. /// public class AndSpecification : Specification { public Specification Left { get; } public Specification Right { get; } public AndSpecification(Specification left, Specification right) { Left = left ?? throw new ArgumentNullException(nameof(left)); Right = right ?? throw new ArgumentNullException(nameof(right)); } public override Expression> ToExpression() { var expr1 = Left.ToExpression(); var expr2 = Right.ToExpression(); return expr1.AndAlso(expr2); } public override bool Equals(object? other) { if (other is null) return false; if (ReferenceEquals(this, other)) return true; if (other is AndSpecification otherSpec) return Left.Equals(otherSpec.Left) && Right.Equals(otherSpec.Right); return false; } public override int GetHashCode() { return HashCodeHelpers.Combine(Left, Right, GetType()); } } } ================================================ FILE: LinqSpecs/Operators/NotSpecification.cs ================================================ using System; using System.Linq.Expressions; using LinqSpecs.Utilities; namespace LinqSpecs.Operators { /// /// Negates a source specification. /// public class NotSpecification : Specification { public Specification Source { get; } public NotSpecification(Specification source) { Source = source ?? throw new ArgumentNullException(nameof(source)); } public override Expression> ToExpression() { var expr = Source.ToExpression(); return Expression.Lambda>(Expression.Not(expr.Body), expr.Parameters); } public override bool Equals(object? other) { if (other is null) return false; if (ReferenceEquals(this, other)) return true; if (other is NotSpecification otherSpec) return Source.Equals(otherSpec.Source); return false; } public override int GetHashCode() { return HashCodeHelpers.Combine(Source, GetType()); } } } ================================================ FILE: LinqSpecs/Operators/OrSpecification.cs ================================================ using System; using System.Linq.Expressions; using LinqSpecs.Utilities; namespace LinqSpecs.Operators { /// /// Combines two specifications by using logical OR operation. /// public class OrSpecification : Specification { public Specification Left { get; } public Specification Right { get; } public OrSpecification(Specification left, Specification right) { Left = left ?? throw new ArgumentNullException(nameof(left)); Right = right ?? throw new ArgumentNullException(nameof(right)); } public override Expression> ToExpression() { var expr1 = Left.ToExpression(); var expr2 = Right.ToExpression(); return expr1.OrElse(expr2); } public override bool Equals(object? other) { if (other is null) return false; if (ReferenceEquals(this, other)) return true; if (other is OrSpecification otherSpec) return Left.Equals(otherSpec.Left) && Right.Equals(otherSpec.Right); return false; } public override int GetHashCode() { return HashCodeHelpers.Combine(Left, Right, GetType()); } } } ================================================ FILE: LinqSpecs/Specification.cs ================================================ using System; using System.Linq.Expressions; using LinqSpecs.Operators; namespace LinqSpecs { /// /// Base class for query specifications that can be combined /// using logical AND, OR and NOT operators. /// public abstract class Specification { /// /// Returns an expression that defines this query. /// /// /// Typically calling this method is not needed as the query /// specification can be converted implicitly to an expression /// by just assigning it or passing it as such to another method. /// public abstract Expression> ToExpression(); /// /// Performs an implicit conversion from /// to a LINQ expression. /// public static implicit operator Expression>(Specification spec) { return spec?.ToExpression() ?? throw new ArgumentNullException(nameof(spec)); } /// /// Override operator false for supporting && and || operations /// public static bool operator false(Specification spec) { return false; } /// /// Override operator true for supporting && and || operations /// public static bool operator true(Specification spec) { return false; } /// /// Allows to combine two query specifications using a logical AND operation. /// public static Specification operator &(Specification spec1, Specification spec2) { return new AndSpecification(spec1, spec2); } /// /// Allows to combine two query specifications using a logical OR operation. /// public static Specification operator |(Specification spec1, Specification spec2) { return new OrSpecification(spec1, spec2); } /// /// Negates the given specification. /// public static Specification operator !(Specification spec) { return new NotSpecification(spec); } } } ================================================ FILE: LinqSpecs/TrueSpecification.cs ================================================ using System; using System.Linq.Expressions; using LinqSpecs.Utilities; namespace LinqSpecs { /// /// Specification which is satisfied by any object. /// public class TrueSpecification : Specification { /// /// Returns an expression that defines this query. /// public override Expression> ToExpression() { return x => true; } /// /// Determines whether the specified object is equal to the current object. /// public override bool Equals(object? other) { if (other is null) return false; if (ReferenceEquals(this, other)) return true; return GetType() == other.GetType(); } /// /// Returns a hash code for the current object. /// public override int GetHashCode() { return HashCodeHelpers.Combine(GetType()); } } } ================================================ FILE: LinqSpecs/Utilities/ExpressionExtensions.cs ================================================ using System; using System.Linq; using System.Linq.Expressions; namespace LinqSpecs.Utilities { // ------------------------------------------------------------------------------------------ // This code was taken from the MSDN Blog meek: LINQ to Entities: Combining Predicates // http://blogs.msdn.com/b/meek/archive/2008/05/02/linq-to-entities-combining-predicates.aspx // ------------------------------------------------------------------------------------------ internal static class ExpressionExtensions { public static Expression> AndAlso( this Expression> first, Expression> second) { return first.Compose(second, Expression.AndAlso); } public static Expression> OrElse( this Expression> first, Expression> second) { return first.Compose(second, Expression.OrElse); } 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 = ExpressionParameterRebinder.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); } } } ================================================ FILE: LinqSpecs/Utilities/ExpressionParameterRebinder.cs ================================================ using System.Collections.Generic; using System.Linq.Expressions; namespace LinqSpecs.Utilities { // ------------------------------------------------------------------------------------------ // This code was taken from the MSDN Blog meek: LINQ to Entities: Combining Predicates // http://blogs.msdn.com/b/meek/archive/2008/05/02/linq-to-entities-combining-predicates.aspx // ------------------------------------------------------------------------------------------ internal class ExpressionParameterRebinder : ExpressionVisitor { private readonly Dictionary _map; public ExpressionParameterRebinder(Dictionary map) { _map = map ?? new Dictionary(); } public static Expression ReplaceParameters( Dictionary map, Expression exp) { return new ExpressionParameterRebinder(map).Visit(exp); } protected override Expression VisitParameter(ParameterExpression p) { if (_map.TryGetValue(p, out ParameterExpression? replacement)) p = replacement; return base.VisitParameter(p); } } } ================================================ FILE: LinqSpecs/Utilities/HashCodeHelpers.cs ================================================ using System; namespace LinqSpecs.Utilities { internal static class HashCodeHelpers { public static int Combine(T value) { #if NETSTANDARD2_1_OR_GREATER return HashCode.Combine(value); #else unchecked { int hash = 17; hash = hash * 23 + value?.GetHashCode() ?? 0; return hash; } #endif } public static int Combine(T1 value1, T2 value2) { #if NETSTANDARD2_1_OR_GREATER return HashCode.Combine(value1, value2); #else unchecked { int hash = 17; hash = hash * 23 + value1?.GetHashCode() ?? 0; hash = hash * 23 + value2?.GetHashCode() ?? 0; return hash; } #endif } public static int Combine(T1 value1, T2 value2, T3 value3) { #if NETSTANDARD2_1_OR_GREATER return HashCode.Combine(value1, value2, value3); #else unchecked { int hash = 17; hash = hash * 23 + value1?.GetHashCode() ?? 0; hash = hash * 23 + value2?.GetHashCode() ?? 0; hash = hash * 23 + value3?.GetHashCode() ?? 0; return hash; } #endif } } } ================================================ FILE: LinqSpecs.DatabaseTests/DomainModel/Customer.cs ================================================ using System.Collections.Generic; namespace LinqSpecs.DatabaseTests { public class Customer { public Customer(string name) { Name = name; } public int Id { get; private set; } public string Name { get; set; } public virtual ICollection Orders { get; } = new HashSet(); } } ================================================ FILE: LinqSpecs.DatabaseTests/DomainModel/Order.cs ================================================ namespace LinqSpecs.DatabaseTests { public class Order { public Order(string product, int price) { Product = product; Price = price; } public int Id { get; private set; } public virtual Customer? Customer { get; set; } public string Product { get; set; } public int Price { get; set; } } } ================================================ FILE: LinqSpecs.DatabaseTests/DomainModel/SampleDbContext.cs ================================================ using Microsoft.EntityFrameworkCore; namespace LinqSpecs.DatabaseTests { class SampleDbContext : DbContext { public DbSet Customers { get; set; } = null!; public DbSet Orders { get; set; } = null!; protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder.UseSqlite("Data Source=file::memory:?cache=shared"); } } } ================================================ FILE: LinqSpecs.DatabaseTests/LinqSpecs.DatabaseTests.csproj ================================================  net70 enable false true ================================================ FILE: LinqSpecs.DatabaseTests/Tests.cs ================================================ using System.Linq; using NUnit.Framework; namespace LinqSpecs.DatabaseTests { public class Tests { [OneTimeSetUp] public void SetUp() { var customer0 = CreateCustomer("Orderless Customer"); var customer1 = CreateCustomer("Single-order Customer", orderPrices: new[] { 100 }); var customer2 = CreateCustomer("Double-order Customer", orderPrices: new[] { 100, 200 }); var customer3 = CreateCustomer("Triple-order Customer", orderPrices: new[] { 100, 200, 300 }); using var db = new SampleDbContext(); db.Database.EnsureDeleted(); db.Database.EnsureCreated(); db.Customers.AddRange(customer0, customer1, customer2, customer3); db.SaveChanges(); } [OneTimeTearDown] public void TearDown() { using var db = new SampleDbContext(); db.Database.EnsureDeleted(); } [Test] public void NoSpecification() { using var db = new SampleDbContext(); var customers = db.Customers.ToList(); Assert.That(customers, Has.Count.EqualTo(4)); } [Test] public void SimplePropertySpecification() { using var db = new SampleDbContext(); var nameSpec = new AdHocSpecification(x => x.Name.StartsWith("Single")); var customers = db.Customers.Where(nameSpec).Select(x => x.Name).ToList(); Assert.That(customers, Is.EquivalentTo(new[] { "Single-order Customer" })); } [Test] public void NavigationPropertySpecification() { using var db = new SampleDbContext(); var hasOrdersSpec = new AdHocSpecification(x => x.Orders.Any()); var customers = db.Customers.Where(hasOrdersSpec).Select(x => x.Name).ToList(); Assert.That( customers, Is.EquivalentTo(new[] { "Single-order Customer", "Double-order Customer", "Triple-order Customer" }) ); } [Test] public void СombinedSpecification() { using var db = new SampleDbContext(); var hasMultipleOrdersSpec = new AdHocSpecification(x => x.Orders.Count() > 1); var highTotalPriceSpec = new AdHocSpecification(x => x.Orders.Sum(y => y.Price) > 500); var vipSpec = hasMultipleOrdersSpec && highTotalPriceSpec; var customers = db.Customers.Where(vipSpec).Select(x => x.Name).ToList(); Assert.That(customers, Is.EquivalentTo(new[] { "Triple-order Customer" })); } private static Customer CreateCustomer(string name, params int[] orderPrices) { var customer = new Customer(name); foreach (var orderPrice in orderPrices) customer.Orders.Add(new Order("Some Product", orderPrice)); return customer; } } } ================================================ FILE: LinqSpecs.UnitTests/AdHocSpecificationTests.cs ================================================ using System; using NUnit.Framework; namespace LinqSpecs.Tests { [TestFixture] public class AdHocSpecificationTests { [Test] public void Constructor_should_throw_exception_when_argument_is_null() { Assert.Throws(() => new AdHocSpecification(null!)); } [Test] public void Predicate_should_work() { var specification = new AdHocSpecification(n => n.StartsWith("J")); var result = new SampleRepository().Find(specification); CollectionAssert.Contains(result, "Jose"); CollectionAssert.Contains(result, "Julian"); CollectionAssert.DoesNotContain(result, "Manuel"); } [Test] public void Equals_returns_true_when_another_specification_has_identical_predicate() { var spec1 = new AdHocSpecification(x => x.Length > 1 && x.StartsWith("J")); var spec2 = new AdHocSpecification(x => x.Length > 1 && x.StartsWith("J")); Assert.IsTrue(spec1.Equals(spec1)); Assert.IsTrue(spec1.Equals(spec2)); } [Test] public void Equals_returns_false_when_another_specification_has_different_predicate() { var spec1 = new AdHocSpecification(x => x.Length > 1); var spec2 = new AdHocSpecification(x => x.Length > 2); Assert.IsFalse(spec1.Equals(spec2)); } [Test] public void Equals_returns_false_when_other_specification_has_different_type() { var spec = new AdHocSpecification(x => true); Assert.IsFalse(spec.Equals(10)); Assert.IsFalse(spec.Equals(new AdHocSpecification(x => true))); Assert.IsFalse(spec.Equals(new AdHocSpecification(x => true))); Assert.IsFalse(spec.Equals(new TrueSpecification())); Assert.IsFalse(spec.Equals(null!)); } [Test] public void GetHashCode_retuns_same_value_for_equal_specifications() { var spec1 = new AdHocSpecification(x => x.Length > 1); var spec2 = new AdHocSpecification(x => x.Length > 1); Assert.AreEqual(spec1.GetHashCode(), spec2.GetHashCode()); } } } ================================================ FILE: LinqSpecs.UnitTests/AssemblyTests.cs ================================================ using NUnit.Framework; namespace LinqSpecs.Tests { [TestFixture] public class AssemblyTests { [Test] public void Assembly_should_have_strong_name() { string? assemblyName = typeof(Specification<>).Assembly.FullName; Assert.That(assemblyName, Does.StartWith("LinqSpecs,")); Assert.That(assemblyName, Does.EndWith("PublicKeyToken=db098e6f22bae212")); } } } ================================================ FILE: LinqSpecs.UnitTests/FalseSpecificationTests.cs ================================================ using System; using NUnit.Framework; namespace LinqSpecs.Tests { [TestFixture] public class FalseSpecificationTests { [Test] public void Should_work() { var spec = new FalseSpecification(); var result = new SampleRepository().Find(spec); CollectionAssert.IsEmpty(result); } [Test] public void Equals_returns_true_when_both_sides_are_equals() { var spec1 = new FalseSpecification(); var spec2 = new FalseSpecification(); Assert.IsTrue(spec1.Equals(spec2)); } [Test] public void Equals_returns_false_when_both_sides_are_not_equals() { var spec = new FalseSpecification(); Assert.IsFalse(spec.Equals(10)); Assert.IsFalse(spec.Equals(new AdHocSpecification(x => true))); Assert.IsFalse(spec.Equals(new FalseSpecification())); Assert.IsFalse(spec.Equals(null!)); } [Test] public void GetHashCode_retuns_same_value_for_equal_specifications() { var spec1 = new FalseSpecification(); var spec2 = new FalseSpecification(); Assert.AreEqual(spec1.GetHashCode(), spec2.GetHashCode()); } } } ================================================ FILE: LinqSpecs.UnitTests/Helpers/SampleRepository.cs ================================================ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; namespace LinqSpecs.Tests { public class SampleRepository : ReadOnlyCollection { public SampleRepository() : base(new[] { "Jose", "Manuel", "Julian" }) { } public IEnumerable Find(Specification specfication) { return this.AsQueryable().Where(specfication.ToExpression()); } } } ================================================ FILE: LinqSpecs.UnitTests/LinqSpecs.UnitTests.csproj ================================================  net48;net70 false 10.0 enable true true ../LinqSpecs.snk ================================================ FILE: LinqSpecs.UnitTests/Operators/AndSpecificationTests.cs ================================================ using System; using System.Collections.Generic; using LinqSpecs.Operators; using NUnit.Framework; namespace LinqSpecs.Tests { //Note; no matter if you are using & operator, or && operator.. both works as an &&. [TestFixture] public class AndSpecificationTests { [Test] public void Constructor_should_throw_exception_when_argument_is_null() { var spec = new AdHocSpecification(x => x.Length == 1); Assert.Throws(() => new AndSpecification(spec, null!)); Assert.Throws(() => new AndSpecification(null!, spec)); } [Test] public void And_should_work() { var startWithJ = new AdHocSpecification(n => n.StartsWith("J")); var endsWithE = new AdHocSpecification(n => n.EndsWith("e")); var specfication = new AndSpecification(startWithJ, endsWithE); IEnumerable result = new SampleRepository().Find(specfication); CollectionAssert.Contains(result, "Jose"); CollectionAssert.DoesNotContain(result, "Julian"); CollectionAssert.DoesNotContain(result, "Manuel"); } [Test] public void Equals_returns_true_when_both_sides_are_equals() { var s1 = new AdHocSpecification(x => x.Length > 1); var s2 = new AdHocSpecification(x => x.Length > 2); var spec = s1 & s2; Assert.IsInstanceOf>(spec); Assert.IsTrue(spec.Equals(spec)); Assert.IsTrue(spec.Equals(s1 & s2)); Assert.IsTrue(spec.Equals(s1 && s2)); // & or && both operators behave as && } [Test] public void Equals_returns_false_when_both_sides_are_not_equals() { var s1 = new AdHocSpecification(x => x.Length > 1); var s2 = new AdHocSpecification(x => x.Length > 2); var s3 = new AdHocSpecification(x => x.Length > 3); var spec = s1 & s2; Assert.IsInstanceOf>(spec); Assert.IsFalse(spec.Equals(10)); Assert.IsFalse(spec.Equals(s1)); Assert.IsFalse(spec.Equals(s2)); Assert.IsFalse(spec.Equals(s2 & s1)); // AndAlso is not commutable Assert.IsFalse(spec.Equals(s1 & s3)); Assert.IsFalse(spec.Equals(s3 & s2)); Assert.IsFalse(spec.Equals(null!)); } [Test] public void GetHashCode_retuns_same_value_for_equal_specifications() { var s1 = new AdHocSpecification(x => x.Length > 1); var s2 = new AdHocSpecification(x => x.Length > 2); var s3 = new AdHocSpecification(x => x.Length > 3); var spec1 = s1 & s2 & s3; var spec2 = s1 & s2 & s3; Assert.IsInstanceOf>(spec1); Assert.IsInstanceOf>(spec2); Assert.AreEqual(spec1.GetHashCode(), spec2.GetHashCode()); } } } ================================================ FILE: LinqSpecs.UnitTests/Operators/NotSpecificationTests.cs ================================================ using System; using LinqSpecs.Operators; using NUnit.Framework; namespace LinqSpecs.Tests { [TestFixture] public class NotSpecificationTests { [Test] public void Constructor_should_throw_exception_when_argument_is_null() { Assert.Throws(() => new NotSpecification(null!)); } [Test] public void Negate_should_work() { var startWithJ = new AdHocSpecification(n => n.StartsWith("J")); var specification = new NotSpecification(startWithJ); var result = new SampleRepository().Find(specification); CollectionAssert.DoesNotContain(result, "Jose"); CollectionAssert.DoesNotContain(result, "Julian"); CollectionAssert.Contains(result, "Manuel"); } [Test] public void Equals_return_true_when_the_negated_spec_are_equals() { var sourceSpec = new AdHocSpecification(x => x.Length > 1); var spec = !sourceSpec; Assert.IsInstanceOf>(spec); Assert.IsTrue(spec.Equals(spec)); Assert.IsTrue(spec.Equals(!sourceSpec)); } [Test] public void Equals_return_false_when_the_negated_spec_are_not_equals() { var sourceSpec1 = new AdHocSpecification(x => x.Length > 1); var sourceSpec2 = new AdHocSpecification(x => x.Length > 2); var spec = !sourceSpec1; Assert.IsInstanceOf>(spec); Assert.IsFalse(spec.Equals(10)); Assert.IsFalse(spec.Equals(sourceSpec1)); Assert.IsFalse(spec.Equals(sourceSpec2)); Assert.IsFalse(spec.Equals(!sourceSpec2)); Assert.IsFalse(spec.Equals(null!)); } [Test] public void GetHashCode_retuns_same_value_for_equal_specifications() { var sourceSpec = new AdHocSpecification(x => x.Length > 0); var spec1 = !sourceSpec; var spec2 = !sourceSpec; Assert.IsInstanceOf>(spec1); Assert.IsInstanceOf>(spec2); Assert.AreEqual(spec1.GetHashCode(), spec2.GetHashCode()); } } } ================================================ FILE: LinqSpecs.UnitTests/Operators/OrSpecificationTests.cs ================================================ using System; using LinqSpecs.Operators; using NUnit.Framework; namespace LinqSpecs.Tests { [TestFixture] public class OrSpecificationTests { [Test] public void Constructor_should_throw_exception_when_argument_is_null() { var spec = new AdHocSpecification(x => x.Length == 1); Assert.Throws(() => new OrSpecification(spec, null!)); Assert.Throws(() => new OrSpecification(null!, spec)); } [Test] public void Or_should_work() { var startWithJ = new AdHocSpecification(n => n.StartsWith("J")); var endsWithN = new AdHocSpecification(n => n.EndsWith("n")); var result = new SampleRepository() .Find(new OrSpecification(startWithJ, endsWithN)); CollectionAssert.Contains(result, "Jose"); CollectionAssert.Contains(result, "Julian"); CollectionAssert.DoesNotContain(result, "Manuel"); } [Test] public void Equals_returns_true_when_both_sides_are_equals() { var s1 = new AdHocSpecification(x => x.Length > 1); var s2 = new AdHocSpecification(x => x.Length > 2); var spec = s1 | s2; Assert.IsInstanceOf>(spec); Assert.IsTrue(spec.Equals(spec)); Assert.IsTrue(spec.Equals(s1 | s2)); Assert.IsTrue(spec.Equals(s1 || s2)); // | or || both operators behave as || } [Test] public void Equals_returns_false_when_both_sides_are_not_equals() { var s1 = new AdHocSpecification(x => x.Length > 1); var s2 = new AdHocSpecification(x => x.Length > 2); var s3 = new AdHocSpecification(x => x.Length > 3); var spec = s1 | s2; Assert.IsInstanceOf>(spec); Assert.IsFalse(spec.Equals(10)); Assert.IsFalse(spec.Equals(s1)); Assert.IsFalse(spec.Equals(s2)); Assert.IsFalse(spec.Equals(s2 | s1)); // OrElse is not commutable Assert.IsFalse(spec.Equals(s1 | s3)); Assert.IsFalse(spec.Equals(s3 | s2)); Assert.IsFalse(spec.Equals(null!)); } [Test] public void GetHashCode_retuns_same_value_for_equal_specifications() { var s1 = new AdHocSpecification(x => x.Length > 1); var s2 = new AdHocSpecification(x => x.Length > 2); var s3 = new AdHocSpecification(x => x.Length > 3); var spec1 = s1 | s2 | s3; var spec2 = s1 | s2 | s3; Assert.IsInstanceOf>(spec1); Assert.IsInstanceOf>(spec2); Assert.AreEqual(spec1.GetHashCode(), spec2.GetHashCode()); } } } ================================================ FILE: LinqSpecs.UnitTests/SpecificationTests.cs ================================================ using System; using System.Linq.Expressions; using NUnit.Framework; namespace LinqSpecs.Tests { [TestFixture] public class SpecificationTests { [Test] public void Implicit_operator_should_convert_specification_to_expression() { Specification spec = new AdHocSpecification(s => s.Length == 2); Expression> expr = spec; Assert.IsTrue(expr.Compile().Invoke("ab")); Assert.IsFalse(expr.Compile().Invoke("abcd")); } [Test] public void Implicit_operator_should_throw_exception_when_argument_is_null() { Specification spec = null!; Assert.Throws(() => { Expression> expr = spec; }); } [Test] public void And_operator_should_work() { var startWithJ = new AdHocSpecification(n => n.StartsWith("J")); var endsWithE = new AdHocSpecification(n => n.EndsWith("e")); var result = new SampleRepository().Find(startWithJ & endsWithE); CollectionAssert.Contains(result, "Jose"); CollectionAssert.DoesNotContain(result, "Julian"); CollectionAssert.DoesNotContain(result, "Manuel"); } [Test] public void Or_operator_should_work() { var startWithJ = new AdHocSpecification(n => n.StartsWith("J")); var endsWithN = new AdHocSpecification(n => n.EndsWith("n")); var result = new SampleRepository().Find(startWithJ | endsWithN); CollectionAssert.Contains(result, "Jose"); CollectionAssert.Contains(result, "Julian"); CollectionAssert.DoesNotContain(result, "Manuel"); } [Test] public void Negate_operator_should_work() { var startWithJ = new AdHocSpecification(n => n.StartsWith("J")); var result = new SampleRepository().Find(!startWithJ); CollectionAssert.DoesNotContain(result, "Jose"); CollectionAssert.DoesNotContain(result, "Julian"); CollectionAssert.Contains(result, "Manuel"); } [Test] public void AndAlso_operator_is_equivalent_to_And_operator() { var spec1 = new AdHocSpecification(n => n.Length > 2); var spec2 = new AdHocSpecification(n => n.Length < 5); Assert.AreEqual(spec1 & spec2, spec1 && spec2); } [Test] public void OrElse_operator_is_equivalent_to_Or_operator() { var spec1 = new AdHocSpecification(n => n.Length < 2); var spec2 = new AdHocSpecification(n => n.Length > 5); Assert.AreEqual(spec1 | spec2, spec1 || spec2); } [Test] public void Combination_of_boolean_operators_should_work() { var startWithM = new AdHocSpecification(n => n.StartsWith("M")); var endsWithN = new AdHocSpecification(n => n.EndsWith("n")); var containsU = new AdHocSpecification(n => n.Contains("u")); var result = new SampleRepository().Find(startWithM | (!endsWithN & !containsU)); CollectionAssert.Contains(result, "Jose"); CollectionAssert.DoesNotContain(result, "Julian"); CollectionAssert.Contains(result, "Manuel"); } } } ================================================ FILE: LinqSpecs.UnitTests/TrueSpecificationTests.cs ================================================ using System; using NUnit.Framework; namespace LinqSpecs.Tests { [TestFixture] public class TrueSpecificationTests { [Test] public void Should_work() { var spec = new TrueSpecification(); var result = new SampleRepository().Find(spec); CollectionAssert.Contains(result, "Jose"); CollectionAssert.Contains(result, "Julian"); CollectionAssert.Contains(result, "Manuel"); } [Test] public void Equals_returns_true_when_both_sides_are_equals() { var spec1 = new TrueSpecification(); var spec2 = new TrueSpecification(); Assert.IsTrue(spec1.Equals(spec2)); } [Test] public void Equals_returns_false_when_both_sides_are_not_equals() { var spec = new TrueSpecification(); Assert.IsFalse(spec.Equals(10)); Assert.IsFalse(spec.Equals(new AdHocSpecification(x => true))); Assert.IsFalse(spec.Equals(new TrueSpecification())); Assert.IsFalse(spec.Equals(null!)); } [Test] public void GetHashCode_retuns_same_value_for_equal_specifications() { var spec1 = new TrueSpecification(); var spec2 = new TrueSpecification(); Assert.AreEqual(spec1.GetHashCode(), spec2.GetHashCode()); } } } ================================================ FILE: LinqSpecs.sln ================================================  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 16.0.30128.74 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LinqSpecs", "LinqSpecs\LinqSpecs.csproj", "{D60875A3-0DE2-409D-86A0-F540224D054C}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LinqSpecs.UnitTests", "LinqSpecs.UnitTests\LinqSpecs.UnitTests.csproj", "{0FBC6204-F18A-4D96-9C5E-39F925E7427C}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LinqSpecs.DatabaseTests", "LinqSpecs.DatabaseTests\LinqSpecs.DatabaseTests.csproj", "{B269AA58-028E-4BA7-ADBC-3848A4018CAE}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{F7AD41A5-AE94-4151-9527-13EC4FFA18C8}" ProjectSection(SolutionItems) = preProject .editorconfig = .editorconfig EndProjectSection EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {D60875A3-0DE2-409D-86A0-F540224D054C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D60875A3-0DE2-409D-86A0-F540224D054C}.Debug|Any CPU.Build.0 = Debug|Any CPU {D60875A3-0DE2-409D-86A0-F540224D054C}.Release|Any CPU.ActiveCfg = Release|Any CPU {D60875A3-0DE2-409D-86A0-F540224D054C}.Release|Any CPU.Build.0 = Release|Any CPU {0FBC6204-F18A-4D96-9C5E-39F925E7427C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {0FBC6204-F18A-4D96-9C5E-39F925E7427C}.Debug|Any CPU.Build.0 = Debug|Any CPU {0FBC6204-F18A-4D96-9C5E-39F925E7427C}.Release|Any CPU.ActiveCfg = Release|Any CPU {0FBC6204-F18A-4D96-9C5E-39F925E7427C}.Release|Any CPU.Build.0 = Release|Any CPU {B269AA58-028E-4BA7-ADBC-3848A4018CAE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B269AA58-028E-4BA7-ADBC-3848A4018CAE}.Debug|Any CPU.Build.0 = Debug|Any CPU {B269AA58-028E-4BA7-ADBC-3848A4018CAE}.Release|Any CPU.ActiveCfg = Release|Any CPU {B269AA58-028E-4BA7-ADBC-3848A4018CAE}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {2B57D1D0-447D-4C30-A33C-51CA138160CE} EndGlobalSection EndGlobal ================================================ FILE: README.md ================================================ ![](https://github.com/navozenko/LinqSpecs/blob/master/logo.png) LinqSpecs is a framework that will help you to create specifications for LINQ queries that can be executed by a remote server. You can read more about the specification pattern in [Wikipedia](http://en.wikipedia.org/wiki/Specification_pattern). Almost all users of LINQ create specifications in their daily work, but most of them write those specifications scattered all over the code. The idea behind this project is to help the user to write, test and expose specifications as first-class objects. You will learn how to use LinqSpecs in this brief document. [![NuGet](https://img.shields.io/nuget/v/LinqSpecs.svg)](https://nuget.org/packages/LinqSpecs) [![Nuget](https://img.shields.io/nuget/dt/LinqSpecs.svg)](https://nuget.org/packages/LinqSpecs) ### Defining simple specifications In order to define our first specification named "CustomerFromCountrySpec" we need to inherit from Specification\: ```csharp public abstract class Specification { public abstract Expression> ToExpression(); } ``` So this is our implementation: ```csharp using LinqSpecs; public enum Country { Argentina, France, Italia, ... } public class CustomerFromCountrySpec : Specification { public Country Country { get; set; } public CustomerFromCountrySpec(Country country) { Country = country; } public override Expression> ToExpression() { return c => c.Country == Country; } } ``` Simple as is, to use this class, your repository or DAO should implement these kind of methods: ```csharp public IEnumerable Find(Specification specification) { return [a queryable source].Where(specification).ToList(); } public int Count(Specification specification) { return [a queryable source].Count(specification); } ``` The usage is very simple: ```csharp var spec = new CustomerFromCountrySpec(Country.Argentina); var customersFromArgentina = customerRepository.Find(spec); ``` ### Alternative way to expose specifications An alternative way of exposing specifications is with a static class: ```csharp public static class CustomerSpecs { public static Specification FromCountry(Country country) { return new CustomerFromCountrySpec(country); } public static Specification EligibleForDiscount(decimal discount) { return new AdHocSpecification( c => c.IsPreferred && !c.HasDebt && c.LastPurchaseDate > DateTime.Today.AddDays(-30)); } } ``` Usage: ```csharp customerRepository.Find( CustomerSpecs.FromCountry(argentina) && CustomerSpecs.EligibleForDiscount(3223)); ``` ### Logical operations AND, OR, NOT One of the most interesting features of LinqSpecs is that you can combine known specifications with "!", "&&" and "||" operators. For example: ```csharp var spec1 = new CustomerFromCountrySpec(Country.Argentina); var spec2 = new CustomerPreferredSpec(); var result = customerRepository.Find(spec1 && !spec2); ``` This code returns all customers from Argentina that are not preferred. The & operator work as an && (AndAlso) operator. The same for | and ||. ### Comparing The result of and'ing, or'ing and negating specifications implements equality members. That's said: ```csharp // This returns true (spec1 && spec2).Equals(spec1 && spec2); // This returns true (spec1 && (spec2 || !spec3)).Equals(spec1 && (spec2 || !spec3)); // This returns false, because AndAlso and OrElse are not commutable operations (spec1 && spec2).Equals(spec2 && spec1); ``` This is an useful feature when you are writing Asserts in your unit tests. ### AdHocSpecification The AdHocSpecification is an alternative way to write a specification without writing a class. You should not abuse of them, and try to write those in a single place as explained above. ```csharp var spec = new AdHocSpecification(c => c.IsPreferred && !c.HasDebt); ``` ### TrueSpecification and FalseSpecification The TrueSpecification is satisfied by any object. The FalseSpecification is not satisfied by any object. ```csharp // This returns all customers customerRepository.Find(new TrueSpecification()); // This returns nothing customerRepository.Find(new FalseSpecification()); ``` These specifications can be useful when you want to retrieve all items from a data source or when you are building a chain of several specifications. For example: ```csharp Specification spec = new FalseSpecification(); foreach (var country in countries) spec |= new CustomerFromCountrySpec(country); return spec; ``` ### In-memory queries Although LinqSpecs is targeted towards IQueryable\ data source, it is possible to use LinqSpecs specifications for filtering IEnumerable\ collections and also for other checks in memory: ```csharp IEnumerable customers = ... var spec = new CustomerFromCountrySpec(Country.Argentina); var result = customers.Where(spec.ToExpression().Compile()); ``` Compiling of expression tree into a delegate is a very slow operation, so it's a good idea to cache the result of a compilation for reuse if it's possible. # Supported platforms - .NET Standard 2.0+ - .NET Framework 4.0+ - .NET Core 2.0+ # License LinqSpecs is open-sourced software licensed under the [Microsoft Public License (MS-PL)](https://opensource.org/licenses/MS-PL). # Contributors LinqSpecs was created by [José F. Romaniello](https://github.com/jfromaniello) and [Sergey Navozenko](https://github.com/navozenko). ================================================ FILE: license.txt ================================================ Microsoft Public License (Ms-PL) This license governs use of the accompanying software. If you use the software, you accept this license. If you do not accept the license, do not use the software. 1. Definitions The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under U.S. copyright law. A "contribution" is the original software, or any additions or changes to the software. A "contributor" is any person that distributes its contribution under this license. "Licensed patents" are a contributor's patent claims that read directly on its contribution. 2. Grant of Rights (A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create. (B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative works of the contribution in the software. 3. Conditions and Limitations (A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks. (B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software, your patent license from such contributor to the software ends automatically. (C) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution notices that are present in the software. (D) If you distribute any portion of the software in source code form, you may do so only under this license by including a complete copy of this license with your distribution. If you distribute any portion of the software in compiled or object code form, you may only do so under a license that complies with this license. (E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, guarantees or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent permitted under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular purpose and non-infringement.