├── .editorconfig
├── .gitattributes
├── .github
└── workflows
│ ├── build.yml
│ ├── ci.yml
│ └── release.yml
├── .gitignore
├── Directory.Packages.props
├── LICENSE.txt
├── QuerySpecification.sln
├── README.md
├── clean.sh
├── exclusion.dic
├── pozitronicon.png
├── pozitronlogo.png
├── readme-nuget.md
├── run-tests.sh
├── setup-sqllocaldb.ps1
├── src
├── Directory.Build.props
├── QuerySpecification.EntityFrameworkCore
│ ├── Evaluators
│ │ ├── AsNoTrackingEvaluator.cs
│ │ ├── AsNoTrackingWithIdentityResolutionEvaluator.cs
│ │ ├── AsSplitQueryEvaluator.cs
│ │ ├── AsTrackingEvaluator.cs
│ │ ├── IgnoreAutoIncludesEvaluator.cs
│ │ ├── IgnoreQueryFiltersEvaluator.cs
│ │ ├── IncludeEvaluator.cs
│ │ ├── IncludeStringEvaluator.cs
│ │ ├── LikeEvaluator.cs
│ │ ├── LikeExtension.cs
│ │ ├── QueryTagEvaluator.cs
│ │ └── SpecificationEvaluator.cs
│ ├── Extensions
│ │ └── IQueryableExtensions.cs
│ ├── GlobalUsings.cs
│ ├── QuerySpecification.EntityFrameworkCore.csproj
│ ├── RepositoryBase.cs
│ └── RepositoryWithMapper.cs
└── QuerySpecification
│ ├── Builders
│ ├── Builder_Cache.cs
│ ├── Builder_Flags.cs
│ ├── Builder_Include.cs
│ ├── Builder_Like.cs
│ ├── Builder_Order.cs
│ ├── Builder_Paging.cs
│ ├── Builder_Select.cs
│ ├── Builder_TagWith.cs
│ ├── Builder_Where.cs
│ ├── IncludableSpecificationBuilder.cs
│ └── SpecificationBuilder.cs
│ ├── DiscoveryAttribute.cs
│ ├── Evaluators
│ ├── IEvaluator.cs
│ ├── IMemoryEvaluator.cs
│ ├── LikeExtension.cs
│ ├── LikeMemoryEvaluator.cs
│ ├── OrderEvaluator.cs
│ ├── PaginationExtensions.cs
│ ├── SpecificationMemoryEvaluator.cs
│ └── WhereEvaluator.cs
│ ├── Exceptions
│ ├── ConcurrentSelectorsException.cs
│ ├── EntityNotFoundException.cs
│ ├── InvalidLikePatternException.cs
│ └── SelectorNotFoundException.cs
│ ├── Expressions
│ ├── IncludeExpression.cs
│ ├── IncludeType.cs
│ ├── LikeExpression.cs
│ ├── OrderExpression.cs
│ ├── OrderType.cs
│ ├── SelectType.cs
│ └── WhereExpression.cs
│ ├── GlobalUsings.cs
│ ├── IProjectionRepository.cs
│ ├── IReadRepositoryBase.cs
│ ├── IRepositoryBase.cs
│ ├── Internals
│ ├── ItemType.cs
│ ├── Iterator.cs
│ ├── SpecFlags.cs
│ ├── SpecItem.cs
│ ├── SpecIterator.cs
│ ├── SpecLike.cs
│ ├── SpecPaging.cs
│ ├── SpecSelectIterator.cs
│ └── TypeDiscovery.cs
│ ├── Paging
│ ├── PagedResult.cs
│ ├── Pagination.cs
│ ├── PaginationSettings.cs
│ └── PagingFilter.cs
│ ├── QuerySpecification.csproj
│ ├── Specification.cs
│ ├── SpecificationExtensions.cs
│ ├── Validators
│ ├── IValidator.cs
│ ├── LikeValidator.cs
│ ├── SpecificationValidator.cs
│ └── WhereValidator.cs
│ └── build
│ └── Pozitron.QuerySpecification.targets
└── tests
├── Directory.Build.props
├── QuerySpecification.AutoDiscovery.Tests
├── GlobalUsings.cs
├── QuerySpecification.AutoDiscovery.Tests.csproj
├── SpecificationEvaluatorTests.cs
├── SpecificationMemoryEvaluatorTests.cs
├── SpecificationValidatorTests.cs
└── TypeDiscoveryTests.cs
├── QuerySpecification.Benchmarks
├── Benchmarks
│ ├── Benchmark0_SpecSize.cs
│ ├── Benchmark1_IQueryable.cs
│ ├── Benchmark2_ToQueryString.cs
│ ├── Benchmark3_DbQuery.cs
│ ├── Benchmark4_Like.cs
│ ├── Benchmark5_Include.cs
│ ├── Benchmark6_IncludeEvaluator.cs
│ ├── Benchmark7_LikeMemoryEvaluator.cs
│ └── Benchmark8_LikeMemoryValidator.cs
├── Data
│ ├── BenchmarkDbContext.cs
│ ├── Company.cs
│ ├── Country.cs
│ ├── Product.cs
│ └── Store.cs
├── Directory.Build.props
├── GloblUsings.cs
├── Program.cs
└── QuerySpecification.Benchmarks.csproj
├── QuerySpecification.EntityFrameworkCore.Tests
├── Evaluators
│ ├── AsNoTrackingEvaluatorTests.cs
│ ├── AsNoTrackingWithIdentityResolutionEvaluatorTests.cs
│ ├── AsSplitQueryEvaluatorTests.cs
│ ├── AsTrackingEvaluatorTests.cs
│ ├── IgnoreAutoIncludesEvaluatorTests.cs
│ ├── IgnoreQueryFiltersEvaluatorTests.cs
│ ├── IncludeEvaluatorTests.cs
│ ├── IncludeStringEvaluatorTests.cs
│ ├── LikeEvaluatorTests.cs
│ ├── LikeExtensionTests.cs
│ ├── OrderEvaluatorTests.cs
│ ├── ParameterReplacerVisitorTests.cs
│ ├── QueryTagEvaluatorTests.cs
│ ├── SpecificationEvaluatorTests.cs
│ └── WhereEvaluatorTests.cs
├── Extensions
│ ├── Extensions_ToPagedResult.cs
│ └── Extensions_WithSpecification.cs
├── Fixture
│ ├── Data
│ │ ├── Address.cs
│ │ ├── Bar.cs
│ │ ├── Company.cs
│ │ ├── Country.cs
│ │ ├── Foo.cs
│ │ ├── Product.cs
│ │ ├── ProductImage.cs
│ │ └── Store.cs
│ ├── IntegrationTest.cs
│ ├── Repository.cs
│ ├── SharedCollection.cs
│ ├── TestDbContext.cs
│ └── TestFactory.cs
├── GlobalUsings.cs
├── QuerySpecification.EntityFrameworkCore.Tests.csproj
├── QueryTests.cs
└── Repositories
│ ├── RepositoryTests.cs
│ ├── Repository_AnyTests.cs
│ ├── Repository_CountTests.cs
│ ├── Repository_FirstTests.cs
│ ├── Repository_ListTests.cs
│ ├── Repository_ProjectToTests.cs
│ └── Repository_WriteTests.cs
└── QuerySpecification.Tests
├── Builders
├── Builder_AsNoTracking.cs
├── Builder_AsNoTrackingWithIdentityResolution.cs
├── Builder_AsSplitQuery.cs
├── Builder_AsTracking.cs
├── Builder_IgnoreAutoIncludes.cs
├── Builder_IgnoreQueryFilters.cs
├── Builder_Include.cs
├── Builder_IncludeString.cs
├── Builder_Like.cs
├── Builder_OrderBy.cs
├── Builder_OrderByDescending.cs
├── Builder_OrderThenBy.cs
├── Builder_OrderThenByDescending.cs
├── Builder_Select.cs
├── Builder_SelectMany.cs
├── Builder_Skip.cs
├── Builder_TagWith.cs
├── Builder_Take.cs
├── Builder_ThenInclude.cs
├── Builder_Where.cs
├── Builder_WithCacheKey.cs
└── SpecificationBuilderTests.cs
├── Evaluators
├── LikeExtensionTests.cs
├── LikeMemoryEvaluatorTests.cs
├── OrderEvaluatorTests.cs
├── PaginationExtensionsTests.cs
├── SpecificationMemoryEvaluatorTests.cs
└── WhereEvaluatorTests.cs
├── Exceptions
├── ConcurrentSelectorsExceptionTests.cs
├── EntityNotFoundExceptionTests.cs
├── InvalidLikePatternExceptionTests.cs
└── SelectorNotFoundExceptionTests.cs
├── GlobalUsings.cs
├── Internals
└── TypeDiscoveryTests.cs
├── Paging
├── PagedResultTests.cs
├── PaginationSettingsTests.cs
└── PaginationTests.cs
├── QuerySpecification.Tests.csproj
├── SpecificationExtensionsTests.cs
├── SpecificationInternalsTests.cs
├── SpecificationTests.cs
└── Validators
├── LikeValidatorTests.cs
├── SpecificationValidatorTests.cs
└── WhereValidatorTests.cs
/.gitattributes:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | # Set default behavior to automatically normalize line endings.
3 | ###############################################################################
4 | * text=auto
5 |
6 | ###############################################################################
7 | # Set default behavior for command prompt diff.
8 | #
9 | # This is need for earlier builds of msysgit that does not have it on by
10 | # default for csharp files.
11 | # Note: This is only used by command line
12 | ###############################################################################
13 | #*.cs diff=csharp
14 |
15 | ###############################################################################
16 | # Set the merge driver for project and solution files
17 | #
18 | # Merging from the command prompt will add diff markers to the files if there
19 | # are conflicts (Merging from VS is not affected by the settings below, in VS
20 | # the diff markers are never inserted). Diff markers may cause the following
21 | # file extensions to fail to load in VS. An alternative would be to treat
22 | # these files as binary and thus will always conflict and require user
23 | # intervention with every merge. To do so, just uncomment the entries below
24 | ###############################################################################
25 | #*.sln merge=binary
26 | #*.csproj merge=binary
27 | #*.vbproj merge=binary
28 | #*.vcxproj merge=binary
29 | #*.vcproj merge=binary
30 | #*.dbproj merge=binary
31 | #*.fsproj merge=binary
32 | #*.lsproj merge=binary
33 | #*.wixproj merge=binary
34 | #*.modelproj merge=binary
35 | #*.sqlproj merge=binary
36 | #*.wwaproj merge=binary
37 |
38 | ###############################################################################
39 | # behavior for image files
40 | #
41 | # image files are treated as binary by default.
42 | ###############################################################################
43 | #*.jpg binary
44 | #*.png binary
45 | #*.gif binary
46 |
47 | ###############################################################################
48 | # diff behavior for common document formats
49 | #
50 | # Convert binary document formats to text before diffing them. This feature
51 | # is only available from the command line. Turn it on by uncommenting the
52 | # entries below.
53 | ###############################################################################
54 | #*.doc diff=astextplain
55 | #*.DOC diff=astextplain
56 | #*.docx diff=astextplain
57 | #*.DOCX diff=astextplain
58 | #*.dot diff=astextplain
59 | #*.DOT diff=astextplain
60 | #*.pdf diff=astextplain
61 | #*.PDF diff=astextplain
62 | #*.rtf diff=astextplain
63 | #*.RTF diff=astextplain
64 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: Full Build
2 |
3 | on:
4 | workflow_dispatch:
5 | push:
6 | branches:
7 | - main
8 |
9 | jobs:
10 | build:
11 | runs-on: ubuntu-latest
12 |
13 | steps:
14 | - name: Checkout
15 | uses: actions/checkout@v4
16 | - name: Setup dotnet
17 | uses: actions/setup-dotnet@v4
18 | with:
19 | dotnet-version: 9.x
20 | dotnet-quality: 'preview'
21 | - name: Build
22 | run: dotnet build --configuration Release
23 | - name: Test
24 | run: dotnet test --configuration Release --no-build --no-restore --collect:"XPlat Code Coverage;Format=opencover"
25 | - name: ReportGenerator
26 | uses: danielpalme/ReportGenerator-GitHub-Action@5.3.9
27 | with:
28 | reports: tests/**/coverage.opencover.xml
29 | targetdir: ${{ runner.temp }}/coveragereport
30 | reporttypes: 'Html;Badges;MarkdownSummaryGithub'
31 | assemblyfilters: -*Tests*
32 | - name: Publish coverage report in build summary
33 | run: cat '${{ runner.temp }}'/coveragereport/SummaryGithub.md >> $GITHUB_STEP_SUMMARY
34 | shell: bash
35 | - name: Create coverage-reports branch and push content
36 | run: |
37 | git fetch
38 | git checkout coverage-reports || git checkout --orphan coverage-reports
39 | git reset --hard
40 | git clean -fd
41 | cp -rp '${{ runner.temp }}'/coveragereport/* ./
42 | echo "queryspecification.fiseni.com" > CNAME
43 | git config user.name github-actions
44 | git config user.email github-actions@github.com
45 | git add .
46 | git commit -m "Update coverage reports [skip ci]" || echo "No changes to commit"
47 | git push origin coverage-reports --force
48 | shell: bash
49 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: Build and Test
2 |
3 | on:
4 | workflow_dispatch:
5 | pull_request:
6 | branches:
7 | - main
8 |
9 | jobs:
10 | build:
11 | runs-on: ubuntu-latest
12 |
13 | steps:
14 | - name: Checkout
15 | uses: actions/checkout@v4
16 | - name: Setup dotnet
17 | uses: actions/setup-dotnet@v4
18 | with:
19 | dotnet-version: 9.x
20 | dotnet-quality: 'preview'
21 | - name: Build
22 | run: dotnet build --configuration Release
23 | - name: Test
24 | run: dotnet test --configuration Release --no-build --no-restore
25 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release to Nuget
2 |
3 | on:
4 | release:
5 | types: [published]
6 |
7 | jobs:
8 | build:
9 | runs-on: ubuntu-latest
10 |
11 | steps:
12 | - name: Checkout
13 | uses: actions/checkout@v4
14 | - name: Setup dotnet
15 | uses: actions/setup-dotnet@v4
16 | with:
17 | dotnet-version: 9.x
18 | dotnet-quality: 'preview'
19 | - name: Build
20 | run: dotnet build --configuration Release
21 | - name: Test
22 | run: dotnet test --configuration Release --no-build --no-restore
23 | - name: Pack
24 | run: dotnet pack --configuration Release --no-build --no-restore --output .
25 | - name: Push to NuGet
26 | run: dotnet nuget push "*.nupkg" --api-key ${{secrets.NUGET_API_KEY}} --source https://api.nuget.org/v3/index.json
27 |
--------------------------------------------------------------------------------
/Directory.Packages.props:
--------------------------------------------------------------------------------
1 |
2 |
3 | true
4 | false
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Fati Iseni
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/clean.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # Fati Iseni
3 |
4 | WorkingDir="$(pwd)"
5 |
6 | ########## Make sure you're not on a root path :)
7 | safetyCheck()
8 | {
9 | declare -a arr=("" "/" "/c" "/d" "c:\\" "d:\\" "C:\\" "D:\\")
10 | for i in "${arr[@]}"
11 | do
12 | if [ "$WorkingDir" = "$i" ]; then
13 | echo "";
14 | echo "You are on a root path. Please run the script from a given directory.";
15 | exit 1;
16 | fi
17 | done
18 | }
19 |
20 | deleteBinObj()
21 | {
22 | echo "Deleting bin and obj directories...";
23 | find "$WorkingDir/" -type d -name "bin" -exec rm -rf {} \; > /dev/null 2>&1;
24 | find "$WorkingDir/" -type d -name "obj" -exec rm -rf {} \; > /dev/null 2>&1;
25 | }
26 |
27 | deleteVSDir()
28 | {
29 | echo "Deleting .vs directories...";
30 | find "$WorkingDir/" -type d -name ".vs" -exec rm -rf {} \; > /dev/null 2>&1;
31 | }
32 |
33 | deleteLogs()
34 | {
35 | echo "Deleting Logs directories...";
36 | find "$WorkingDir/" -type d -name "Logs" -exec rm -rf {} \; > /dev/null 2>&1;
37 | }
38 |
39 | deleteUserCsprojFiles()
40 | {
41 | echo "Deleting *.csproj.user files...";
42 | find "$WorkingDir/" -type f -name "*.csproj.user" -exec rm -rf {} \; > /dev/null 2>&1;
43 | }
44 |
45 | deleteTestResults()
46 | {
47 | echo "Deleting test and coverage artifacts...";
48 | find "$WorkingDir/" -type d -name "TestResults" -exec rm -rf {} \; > /dev/null 2>&1;
49 | }
50 |
51 | deleteLocalGitBranches()
52 | {
53 | echo "Deleting local unused git branches (e.g. no corresponding remote branch)...";
54 | git fetch -p && git branch -vv | awk '/: gone\]/{print $1}' | xargs -I {} git branch -D {}
55 | }
56 |
57 | safetyCheck;
58 | echo "";
59 |
60 | if [ "$1" = "help" ]; then
61 | echo "Usage:";
62 | echo "";
63 | echo -e "clean.sh [obj | vs | logs | user | coverages | branches | all]";
64 | echo "";
65 | echo -e "obj (Default)\t-\tDeletes bin and obj directories.";
66 | echo -e "vs\t\t-\tDeletes .vs directories.";
67 | echo -e "logs\t\t-\tDeletes Logs directories.";
68 | echo -e "user\t\t-\tDeletes *.csproj.user files.";
69 | echo -e "coverages\t-\tDeletes test and coverage artifacts.";
70 | echo -e "branches\t-\tDeletes local unused git branches (e.g. no corresponding remote branch).";
71 | echo -e "all\t\t-\tApply all options";
72 |
73 | elif [ "$1" = "obj" ]; then
74 | deleteBinObj;
75 | elif [ "$1" = "vs" ]; then
76 | deleteVSDir;
77 | elif [ "$1" = "logs" ]; then
78 | deleteLogs;
79 | elif [ "$1" = "user" ]; then
80 | deleteUserCsprojFiles;
81 | elif [ "$1" = "coverages" ]; then
82 | deleteTestResults;
83 | elif [ "$1" = "branches" ]; then
84 | deleteLocalGitBranches;
85 | elif [ "$1" = "all" ]; then
86 | deleteBinObj;
87 | deleteVSDir;
88 | deleteLogs;
89 | deleteUserCsprojFiles;
90 | deleteTestResults;
91 | deleteLocalGitBranches;
92 | else
93 | deleteBinObj;
94 | fi
95 |
--------------------------------------------------------------------------------
/exclusion.dic:
--------------------------------------------------------------------------------
1 | Pozitron
2 | pozitron
3 | microsoft
4 | mssql
5 | mssqllocaldb
6 | localdb
7 | _respawner
8 | respawner
9 | criterias
10 | Unescape
11 | #Dummy test data
12 | Foos
13 | axxa
14 | axya
15 | aaaa
16 | vvvv
17 | irst
18 | irstt
19 | asdf
20 | asdfa
21 | aaab
22 | aaaab
23 | aaaaab
24 | axza
25 | Compilable
26 | netstandard
27 |
--------------------------------------------------------------------------------
/pozitronicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fiseni/QuerySpecification/29d63784f6d4630d7aaf187e4798c8936ee14dd8/pozitronicon.png
--------------------------------------------------------------------------------
/pozitronlogo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fiseni/QuerySpecification/29d63784f6d4630d7aaf187e4798c8936ee14dd8/pozitronlogo.png
--------------------------------------------------------------------------------
/run-tests.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | dotnet tool list -g dotnet-reportgenerator-globaltool > /dev/null 2>&1
4 | exists=$(echo $?)
5 | if [ $exists -ne 0 ]; then
6 | echo "Installing ReportGenerator..."
7 | dotnet tool install -g dotnet-reportgenerator-globaltool
8 | echo "ReportGenerator installed"
9 | fi
10 |
11 | find . -type d -name TestResults -exec rm -rf {} \; > /dev/null 2>&1
12 |
13 | testtarget="$1"
14 |
15 | if [ "$testtarget" = "" ]; then
16 | testtarget="*.sln"
17 | fi
18 |
19 | dotnet build $testtarget --configuration Release
20 | dotnet test $testtarget --configuration Release --no-build --no-restore --collect:"XPlat Code Coverage;Format=opencover"
21 |
22 | reportgenerator \
23 | -reports:tests/**/coverage.opencover.xml \
24 | -targetdir:TestResults \
25 | -reporttypes:"Html;Badges;MarkdownSummaryGithub" \
26 | -assemblyfilters:-*Tests*
27 |
--------------------------------------------------------------------------------
/setup-sqllocaldb.ps1:
--------------------------------------------------------------------------------
1 | # Taken from psake https://github.com/psake/psake
2 |
3 | <#
4 | .SYNOPSIS
5 | This is a helper function that runs a scriptblock and checks the PS variable $lastexitcode
6 | to see if an error occcured. If an error is detected then an exception is thrown.
7 | This function allows you to run command-line programs without having to
8 | explicitly check the $lastexitcode variable.
9 | .EXAMPLE
10 | exec { svn info $repository_trunk } "Error executing SVN. Please verify SVN command-line client is installed"
11 | #>
12 | function Exec
13 | {
14 | [CmdletBinding()]
15 | param(
16 | [Parameter(Position=0,Mandatory=1)][scriptblock]$cmd,
17 | [Parameter(Position=1,Mandatory=0)][string]$errorMessage = ($msgs.error_bad_command -f $cmd)
18 | )
19 | & $cmd
20 | if ($lastexitcode -ne 0) {
21 | throw ("Exec: " + $errorMessage)
22 | }
23 | }
24 |
25 | Write-Host "Downloading"
26 | Import-Module BitsTransfer
27 | Start-BitsTransfer -Source https://download.microsoft.com/download/7/c/1/7c14e92e-bdcb-4f89-b7cf-93543e7112d1/SqlLocalDB.msi -Destination SqlLocalDB.msi
28 | Write-Host "Installing"
29 | Start-Process -FilePath "SqlLocalDB.msi" -Wait -ArgumentList "/qn", "/norestart", "/l*v SqlLocalDBInstall.log", "IACCEPTSQLLOCALDBLICENSETERMS=YES";
30 | <#
31 | Write-Host "Checking"
32 | sqlcmd -l 60 -S "(localdb)\MSSQLLocalDB" -Q "SELECT @@VERSION;"
33 | #>
--------------------------------------------------------------------------------
/src/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Pozitron.QuerySpecification
5 | net8.0;net9.0
6 | enable
7 | enable
8 | latest
9 |
10 | true
11 | false
12 | true
13 |
14 |
15 |
16 | true
17 | snupkg
18 | true
19 | true
20 |
21 |
22 |
23 |
24 |
25 | Fati Iseni
26 | Pozitron Group
27 | Copyright © 2024 Pozitron Group
28 | Pozitron QuerySpecification
29 |
30 | https://github.com/fiseni/QuerySpecification
31 | https://github.com/fiseni/QuerySpecification
32 | true
33 | git
34 | MIT
35 | readme-nuget.md
36 | https://pozitrongroup.com/PozitronLogo.png
37 | pozitronicon.png
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/src/QuerySpecification.EntityFrameworkCore/Evaluators/AsNoTrackingEvaluator.cs:
--------------------------------------------------------------------------------
1 | namespace Pozitron.QuerySpecification;
2 |
3 | ///
4 | /// Evaluator to apply AsNoTracking to the query if the specification has AsNoTracking set to true.
5 | ///
6 | [EvaluatorDiscovery(Order = 100)]
7 | public sealed class AsNoTrackingEvaluator : IEvaluator
8 | {
9 | ///
10 | /// Gets the singleton instance of the class.
11 | ///
12 | public static AsNoTrackingEvaluator Instance = new();
13 | private AsNoTrackingEvaluator() { }
14 |
15 | ///
16 | public IQueryable Evaluate(IQueryable source, Specification specification) where T : class
17 | {
18 | if (specification.AsNoTracking)
19 | {
20 | source = source.AsNoTracking();
21 | }
22 |
23 | return source;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/QuerySpecification.EntityFrameworkCore/Evaluators/AsNoTrackingWithIdentityResolutionEvaluator.cs:
--------------------------------------------------------------------------------
1 | namespace Pozitron.QuerySpecification;
2 |
3 | ///
4 | /// Evaluator to apply AsNoTracking to the query if the specification has AsNoTracking set to true.
5 | ///
6 | [EvaluatorDiscovery(Order = 110)]
7 | public sealed class AsNoTrackingWithIdentityResolutionEvaluator : IEvaluator
8 | {
9 | ///
10 | /// Gets the singleton instance of the class.
11 | ///
12 | public static AsNoTrackingWithIdentityResolutionEvaluator Instance = new();
13 | private AsNoTrackingWithIdentityResolutionEvaluator() { }
14 |
15 | ///
16 | public IQueryable Evaluate(IQueryable source, Specification specification) where T : class
17 | {
18 | if (specification.AsNoTrackingWithIdentityResolution)
19 | {
20 | source = source.AsNoTrackingWithIdentityResolution();
21 | }
22 |
23 | return source;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/QuerySpecification.EntityFrameworkCore/Evaluators/AsSplitQueryEvaluator.cs:
--------------------------------------------------------------------------------
1 | namespace Pozitron.QuerySpecification;
2 |
3 | ///
4 | /// Evaluator to apply AsSplitQuery to the query if the specification has AsSplitQuery set to true.
5 | ///
6 | [EvaluatorDiscovery(Order = 90)]
7 | public sealed class AsSplitQueryEvaluator : IEvaluator
8 | {
9 | ///
10 | /// Gets the singleton instance of the class.
11 | ///
12 | public static AsSplitQueryEvaluator Instance = new();
13 | private AsSplitQueryEvaluator() { }
14 |
15 | ///
16 | public IQueryable Evaluate(IQueryable source, Specification specification) where T : class
17 | {
18 | if (specification.AsSplitQuery)
19 | {
20 | source = source.AsSplitQuery();
21 | }
22 |
23 | return source;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/QuerySpecification.EntityFrameworkCore/Evaluators/AsTrackingEvaluator.cs:
--------------------------------------------------------------------------------
1 | namespace Pozitron.QuerySpecification;
2 |
3 | ///
4 | /// Evaluator to apply AsTracking to the query if the specification has AsTracking set to true.
5 | ///
6 | [EvaluatorDiscovery(Order = 120)]
7 | public sealed class AsTrackingEvaluator : IEvaluator
8 | {
9 | ///
10 | /// Gets the singleton instance of the class.
11 | ///
12 | public static AsTrackingEvaluator Instance = new();
13 | private AsTrackingEvaluator() { }
14 |
15 | ///
16 | public IQueryable Evaluate(IQueryable source, Specification specification) where T : class
17 | {
18 | if (specification.AsTracking)
19 | {
20 | source = source.AsTracking();
21 | }
22 |
23 | return source;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/QuerySpecification.EntityFrameworkCore/Evaluators/IgnoreAutoIncludesEvaluator.cs:
--------------------------------------------------------------------------------
1 | namespace Pozitron.QuerySpecification;
2 |
3 | ///
4 | /// Evaluator to apply IgnoreAutoIncludes to the query if the specification has IgnoreAutoIncludes set to true.
5 | ///
6 | [EvaluatorDiscovery(Order = 70)]
7 | public sealed class IgnoreAutoIncludesEvaluator : IEvaluator
8 | {
9 |
10 | ///
11 | /// Gets the singleton instance of the class.
12 | ///
13 | public static IgnoreAutoIncludesEvaluator Instance = new();
14 | private IgnoreAutoIncludesEvaluator() { }
15 |
16 | ///
17 | public IQueryable Evaluate(IQueryable source, Specification specification) where T : class
18 | {
19 | if (specification.IgnoreAutoIncludes)
20 | {
21 | source = source.IgnoreAutoIncludes();
22 | }
23 |
24 | return source;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/QuerySpecification.EntityFrameworkCore/Evaluators/IgnoreQueryFiltersEvaluator.cs:
--------------------------------------------------------------------------------
1 | namespace Pozitron.QuerySpecification;
2 |
3 | ///
4 | /// Evaluator to apply IgnoreQueryFilters to the query if the specification has IgnoreQueryFilters set to true.
5 | ///
6 | [EvaluatorDiscovery(Order = 80)]
7 | public sealed class IgnoreQueryFiltersEvaluator : IEvaluator
8 | {
9 |
10 | ///
11 | /// Gets the singleton instance of the class.
12 | ///
13 | public static IgnoreQueryFiltersEvaluator Instance = new();
14 | private IgnoreQueryFiltersEvaluator() { }
15 |
16 | ///
17 | public IQueryable Evaluate(IQueryable source, Specification specification) where T : class
18 | {
19 | if (specification.IgnoreQueryFilters)
20 | {
21 | source = source.IgnoreQueryFilters();
22 | }
23 |
24 | return source;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/QuerySpecification.EntityFrameworkCore/Evaluators/IncludeStringEvaluator.cs:
--------------------------------------------------------------------------------
1 | namespace Pozitron.QuerySpecification;
2 |
3 | ///
4 | /// Evaluates a specification to include navigation properties specified by string paths.
5 | ///
6 | [EvaluatorDiscovery(Order = 30)]
7 | public sealed class IncludeStringEvaluator : IEvaluator
8 | {
9 |
10 | ///
11 | /// Gets the singleton instance of the class.
12 | ///
13 | public static IncludeStringEvaluator Instance = new();
14 | private IncludeStringEvaluator() { }
15 |
16 | ///
17 | public IQueryable Evaluate(IQueryable source, Specification specification) where T : class
18 | {
19 | foreach (var item in specification.Items)
20 | {
21 | if (item.Type == ItemType.IncludeString && item.Reference is string includeString)
22 | {
23 | source = source.Include(includeString);
24 | }
25 | }
26 |
27 | return source;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/QuerySpecification.EntityFrameworkCore/Evaluators/LikeExtension.cs:
--------------------------------------------------------------------------------
1 | using System.Data;
2 | using System.Diagnostics;
3 | using System.Reflection;
4 |
5 | namespace Pozitron.QuerySpecification;
6 |
7 | internal static class LikeExtension
8 | {
9 | private static readonly MethodInfo _likeMethodInfo = typeof(DbFunctionsExtensions)
10 | .GetMethod(nameof(DbFunctionsExtensions.Like), [typeof(DbFunctions), typeof(string), typeof(string)])!;
11 |
12 | private static readonly MemberExpression _functions = Expression.Property(null, typeof(EF).GetProperty(nameof(EF.Functions))!);
13 |
14 | // It's required so EF can generate parameterized query.
15 | // In the past I've been creating closures for this, e.g. var patternAsExpression = ((Expression>)(() => pattern)).Body;
16 | // But, that allocates 168 bytes. So, this is more efficient way.
17 | private static MemberExpression StringAsExpression(string value) => Expression.Property(
18 | Expression.Constant(new StringVar(value)),
19 | typeof(StringVar).GetProperty(nameof(StringVar.Format))!);
20 |
21 | // We'll name the property Format just so we match the produced SQL query parameter name (in case of interpolated strings).
22 | private record StringVar(string Format);
23 |
24 | public static IQueryable ApplyLikesAsOrGroup(this IQueryable source, ReadOnlySpan likeItems)
25 | {
26 | Debug.Assert(_likeMethodInfo is not null);
27 |
28 | Expression? combinedExpr = null;
29 | ParameterExpression? mainParam = null;
30 | ParameterReplacerVisitor? visitor = null;
31 |
32 | foreach (var item in likeItems)
33 | {
34 | if (item.Reference is not SpecLike specLike) continue;
35 |
36 | mainParam ??= specLike.KeySelector.Parameters[0];
37 |
38 | var selectorExpr = specLike.KeySelector.Body;
39 | if (mainParam != specLike.KeySelector.Parameters[0])
40 | {
41 | visitor ??= new ParameterReplacerVisitor(specLike.KeySelector.Parameters[0], mainParam);
42 |
43 | // If there are more than 2 likes, we want to avoid creating a new visitor instance (saving 32 bytes per instance).
44 | // We're in a sequential loop, no concurrency issues.
45 | visitor.Update(specLike.KeySelector.Parameters[0], mainParam);
46 | selectorExpr = visitor.Visit(selectorExpr);
47 | }
48 |
49 | var patternExpr = StringAsExpression(specLike.Pattern);
50 |
51 | var likeExpr = Expression.Call(
52 | null,
53 | _likeMethodInfo,
54 | _functions,
55 | selectorExpr,
56 | patternExpr);
57 |
58 | combinedExpr = combinedExpr is null
59 | ? likeExpr
60 | : Expression.OrElse(combinedExpr, likeExpr);
61 | }
62 |
63 | return combinedExpr is null || mainParam is null
64 | ? source
65 | : source.Where(Expression.Lambda>(combinedExpr, mainParam));
66 | }
67 | }
68 |
69 | internal sealed class ParameterReplacerVisitor : ExpressionVisitor
70 | {
71 | private ParameterExpression _oldParameter;
72 | private Expression _newExpression;
73 |
74 | internal ParameterReplacerVisitor(ParameterExpression oldParameter, Expression newExpression) =>
75 | (_oldParameter, _newExpression) = (oldParameter, newExpression);
76 |
77 | internal void Update(ParameterExpression oldParameter, Expression newExpression) =>
78 | (_oldParameter, _newExpression) = (oldParameter, newExpression);
79 |
80 | protected override Expression VisitParameter(ParameterExpression node) =>
81 | node == _oldParameter ? _newExpression : node;
82 | }
83 |
--------------------------------------------------------------------------------
/src/QuerySpecification.EntityFrameworkCore/Evaluators/QueryTagEvaluator.cs:
--------------------------------------------------------------------------------
1 | namespace Pozitron.QuerySpecification;
2 |
3 | ///
4 | /// Evaluator to apply the query tags to the query.
5 | ///
6 | [EvaluatorDiscovery(Order = 60)]
7 | public sealed class QueryTagEvaluator : IEvaluator
8 | {
9 |
10 | ///
11 | /// Gets the singleton instance of the class.
12 | ///
13 | public static QueryTagEvaluator Instance = new();
14 | private QueryTagEvaluator() { }
15 |
16 | ///
17 | public IQueryable Evaluate(IQueryable source, Specification specification) where T : class
18 | {
19 | foreach (var item in specification.Items)
20 | {
21 | if (item.Type == ItemType.QueryTag && item.Reference is string tag)
22 | {
23 | source = source.TagWith(tag);
24 | }
25 | }
26 |
27 | return source;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/QuerySpecification.EntityFrameworkCore/GlobalUsings.cs:
--------------------------------------------------------------------------------
1 | global using Microsoft.EntityFrameworkCore;
2 | global using System.Linq.Expressions;
3 |
--------------------------------------------------------------------------------
/src/QuerySpecification.EntityFrameworkCore/QuerySpecification.EntityFrameworkCore.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Pozitron.QuerySpecification.EntityFrameworkCore
5 | Pozitron.QuerySpecification.EntityFrameworkCore
6 | Pozitron.QuerySpecification.EntityFrameworkCore
7 | EntityFrameworkCore plugin to Pozitron.QuerySpecification containing EF evaluators.
8 | EntityFrameworkCore plugin to Pozitron.QuerySpecification containing EF evaluators.
9 |
10 | 11.2.0
11 | fiseni pozitron query specification efcore
12 |
13 | Refer to Releases page for details.
14 | https://github.com/fiseni/QuerySpecification/releases
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/src/QuerySpecification/Builders/Builder_Cache.cs:
--------------------------------------------------------------------------------
1 | namespace Pozitron.QuerySpecification;
2 |
3 | public static partial class SpecificationBuilderExtensions
4 | {
5 | ///
6 | /// Sets the cache key for the specification.
7 | ///
8 | /// The type of the entity.
9 | /// The type of the result.
10 | /// The specification builder.
11 | /// The cache key to be used.
12 | /// The updated specification builder.
13 | public static ISpecificationBuilder WithCacheKey(
14 | this ISpecificationBuilder builder,
15 | string cacheKey) where T : class
16 | {
17 | WithCacheKey(builder, cacheKey, true);
18 | return (SpecificationBuilder)builder;
19 | }
20 |
21 | ///
22 | /// Sets the cache key for the specification.
23 | ///
24 | /// The type of the entity.
25 | /// The type of the result.
26 | /// The specification builder.
27 | /// The cache key to be used.
28 | /// The condition to evaluate.
29 | /// The updated specification builder.
30 | public static ISpecificationBuilder WithCacheKey(
31 | this ISpecificationBuilder builder,
32 | string cacheKey,
33 | bool condition) where T : class
34 | {
35 | if (condition)
36 | {
37 | builder.Specification.AddOrUpdateInternal(ItemType.CacheKey, cacheKey);
38 | }
39 |
40 | return builder;
41 | }
42 |
43 | ///
44 | /// Sets the cache key for the specification.
45 | ///
46 | /// The type of the entity.
47 | /// The specification builder.
48 | /// The cache key to be used.
49 | /// The updated specification builder.
50 | public static ISpecificationBuilder WithCacheKey(
51 | this ISpecificationBuilder builder,
52 | string cacheKey) where T : class
53 | => WithCacheKey(builder, cacheKey, true);
54 |
55 | ///
56 | /// Sets the cache key for the specification.
57 | ///
58 | /// The type of the entity.
59 | /// The specification builder.
60 | /// The cache key to be used.
61 | /// The condition to evaluate.
62 | /// The updated specification builder.
63 | public static ISpecificationBuilder WithCacheKey(
64 | this ISpecificationBuilder builder,
65 | string cacheKey,
66 | bool condition) where T : class
67 | {
68 | if (condition)
69 | {
70 | builder.Specification.AddOrUpdateInternal(ItemType.CacheKey, cacheKey);
71 | }
72 |
73 | return builder;
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/src/QuerySpecification/Builders/Builder_Select.cs:
--------------------------------------------------------------------------------
1 | namespace Pozitron.QuerySpecification;
2 |
3 | public static partial class SpecificationBuilderExtensions
4 | {
5 | ///
6 | /// Adds a Select clause to the specification.
7 | ///
8 | /// The type of the entity.
9 | /// The type of the result.
10 | /// The specification builder.
11 | /// The selector expression.
12 | public static void Select(
13 | this ISpecificationBuilder builder,
14 | Expression> selector)
15 | {
16 | builder.Specification.AddOrUpdateInternal(ItemType.Select, selector, (int)SelectType.Select);
17 | }
18 |
19 | ///
20 | /// Adds a SelectMany clause to the specification.
21 | ///
22 | /// The type of the entity.
23 | /// The type of the result.
24 | /// The specification builder.
25 | /// The selector expression.
26 | public static void SelectMany(
27 | this ISpecificationBuilder builder,
28 | Expression>> selector)
29 | {
30 | builder.Specification.AddOrUpdateInternal(ItemType.Select, selector, (int)SelectType.SelectMany);
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/QuerySpecification/Builders/Builder_TagWith.cs:
--------------------------------------------------------------------------------
1 | namespace Pozitron.QuerySpecification;
2 |
3 | public static partial class SpecificationBuilderExtensions
4 | {
5 | ///
6 | /// Adds a query tag to the specification.
7 | ///
8 | /// The type of the entity.
9 | /// The type of the result.
10 | /// The specification builder.
11 | /// The query tag.
12 | /// The updated specification builder.
13 | public static ISpecificationBuilder TagWith(
14 | this ISpecificationBuilder builder,
15 | string tag)
16 | {
17 | TagWith(builder, tag, true);
18 | return builder;
19 | }
20 |
21 | ///
22 | /// Adds a query tag to the specification if the condition is true.
23 | ///
24 | /// The type of the entity.
25 | /// The type of the result.
26 | /// The specification builder.
27 | /// The query tag.
28 | /// The condition to evaluate.
29 | /// The updated specification builder.
30 | public static ISpecificationBuilder TagWith(
31 | this ISpecificationBuilder builder,
32 | string tag,
33 | bool condition)
34 | {
35 | if (condition)
36 | {
37 | builder.Specification.AddInternal(ItemType.QueryTag, tag);
38 | }
39 |
40 | return builder;
41 | }
42 |
43 | ///
44 | /// Adds a query tag to the specification.
45 | ///
46 | /// The type of the entity.
47 | /// The specification builder.
48 | /// The query tag.
49 | /// The updated specification builder.
50 | public static ISpecificationBuilder TagWith(
51 | this ISpecificationBuilder builder,
52 | string tag)
53 | => TagWith(builder, tag, true);
54 |
55 | ///
56 | /// Adds a query tag to the specification if the condition is true.
57 | ///
58 | /// The type of the entity.
59 | /// The specification builder.
60 | /// The query tag.
61 | /// The condition to evaluate.
62 | /// The updated specification builder.
63 | public static ISpecificationBuilder TagWith(
64 | this ISpecificationBuilder builder,
65 | string tag,
66 | bool condition)
67 | {
68 | if (condition)
69 | {
70 | builder.Specification.AddInternal(ItemType.QueryTag, tag);
71 | }
72 |
73 | return builder;
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/src/QuerySpecification/Builders/Builder_Where.cs:
--------------------------------------------------------------------------------
1 | namespace Pozitron.QuerySpecification;
2 |
3 | public static partial class SpecificationBuilderExtensions
4 | {
5 | ///
6 | /// Adds a Where clause to the specification.
7 | ///
8 | /// The type of the entity.
9 | /// The type of the result.
10 | /// The specification builder.
11 | /// The predicate expression.
12 | /// The updated specification builder.
13 | public static ISpecificationBuilder Where(
14 | this ISpecificationBuilder builder,
15 | Expression> predicate)
16 | => Where(builder, predicate, true);
17 |
18 | ///
19 | /// Adds a Where clause to the specification if the condition is true.
20 | ///
21 | /// The type of the entity.
22 | /// The type of the result.
23 | /// The specification builder.
24 | /// The predicate expression.
25 | /// The condition to evaluate.
26 | /// The updated specification builder.
27 | public static ISpecificationBuilder Where(
28 | this ISpecificationBuilder builder,
29 | Expression> predicate,
30 | bool condition)
31 | {
32 | if (condition)
33 | {
34 | builder.Specification.AddInternal(ItemType.Where, predicate);
35 | }
36 | return builder;
37 | }
38 |
39 | ///
40 | /// Adds a Where clause to the specification.
41 | ///
42 | /// The type of the entity.
43 | /// The specification builder.
44 | /// The predicate expression.
45 | /// The updated specification builder.
46 | public static ISpecificationBuilder Where(
47 | this ISpecificationBuilder builder,
48 | Expression> predicate)
49 | => Where(builder, predicate, true);
50 |
51 | ///
52 | /// Adds a Where clause to the specification if the condition is true.
53 | ///
54 | /// The type of the entity.
55 | /// The specification builder.
56 | /// The predicate expression.
57 | /// The condition to evaluate.
58 | /// The updated specification builder.
59 | public static ISpecificationBuilder Where(
60 | this ISpecificationBuilder builder,
61 | Expression> predicate,
62 | bool condition)
63 | {
64 | if (condition)
65 | {
66 | builder.Specification.AddInternal(ItemType.Where, predicate);
67 | }
68 | return builder;
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/QuerySpecification/Builders/IncludableSpecificationBuilder.cs:
--------------------------------------------------------------------------------
1 | namespace Pozitron.QuerySpecification;
2 |
3 | ///
4 | /// Represents a specification builder that supports include operations.
5 | ///
6 | /// The type of the entity.
7 | /// The type of the result.
8 | /// The type of the property.
9 | public interface IIncludableSpecificationBuilder : ISpecificationBuilder where T : class
10 | {
11 | }
12 |
13 | ///
14 | /// Represents a specification builder that supports include operations.
15 | ///
16 | /// The type of the entity.
17 | /// The type of the property.
18 | public interface IIncludableSpecificationBuilder : ISpecificationBuilder where T : class
19 | {
20 | }
21 |
22 | internal class IncludableSpecificationBuilder(Specification specification)
23 | : SpecificationBuilder(specification), IIncludableSpecificationBuilder where T : class
24 | {
25 | }
26 |
27 | internal class IncludableSpecificationBuilder(Specification specification)
28 | : SpecificationBuilder(specification), IIncludableSpecificationBuilder where T : class
29 | {
30 | }
31 |
--------------------------------------------------------------------------------
/src/QuerySpecification/Builders/SpecificationBuilder.cs:
--------------------------------------------------------------------------------
1 | namespace Pozitron.QuerySpecification;
2 |
3 | ///
4 | /// Represents a specification builder that supports order operations.
5 | ///
6 | /// The type of the entity.
7 | /// The type of the result.
8 | public interface IOrderedSpecificationBuilder
9 | : ISpecificationBuilder, IOrderedSpecificationBuilder
10 | {
11 | }
12 |
13 | ///
14 | /// Represents a specification builder that supports order operations.
15 | ///
16 | /// The type of the entity.
17 | public interface IOrderedSpecificationBuilder
18 | : ISpecificationBuilder
19 | {
20 | }
21 |
22 | ///
23 | /// Represents a specification builder.
24 | ///
25 | /// The type of the entity.
26 | /// The type of the result.
27 | public interface ISpecificationBuilder
28 | : ISpecificationBuilder
29 | {
30 | new internal Specification Specification { get; }
31 | }
32 |
33 | ///
34 | /// Represents a specification builder.
35 | ///
36 | /// The type of the entity.
37 | public interface ISpecificationBuilder
38 | {
39 | internal Specification Specification { get; }
40 |
41 | ///
42 | /// Adds an item to the specification.
43 | ///
44 | /// The type of the item.
45 | /// The object to be stored in the item.
46 | /// Thrown if value is null
47 | /// Thrown if type is zero or negative.
48 | void Add(int type, object value);
49 |
50 | ///
51 | /// Adds or updates an item in the specification.
52 | ///
53 | /// The type of the item.
54 | /// The object to be stored in the item.
55 | /// Thrown if value is null
56 | /// Thrown if type is zero or negative.
57 | void AddOrUpdate(int type, object value);
58 | }
59 |
60 | internal class SpecificationBuilder(Specification specification)
61 | : SpecificationBuilder(specification), IOrderedSpecificationBuilder, ISpecificationBuilder
62 | {
63 | new public Specification Specification { get; } = specification;
64 | }
65 |
66 | internal class SpecificationBuilder(Specification specification)
67 | : IOrderedSpecificationBuilder, ISpecificationBuilder
68 | {
69 | public Specification Specification { get; } = specification;
70 | public void Add(int type, object value) => Specification.Add(type, value);
71 | public void AddOrUpdate(int type, object value) => Specification.AddOrUpdate(type, value);
72 | }
73 |
--------------------------------------------------------------------------------
/src/QuerySpecification/DiscoveryAttribute.cs:
--------------------------------------------------------------------------------
1 | namespace Pozitron.QuerySpecification;
2 |
3 | ///
4 | /// Specifies whether auto discovery for evaluators and validators is enabled.
5 | ///
6 | [AttributeUsage(AttributeTargets.Assembly)]
7 | public sealed class SpecAutoDiscoveryAttribute : Attribute
8 | {
9 | }
10 |
11 | ///
12 | /// Specifies discovery options for evaluators and validators, such as the order and whether discovery is enabled.
13 | ///
14 | [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
15 | public class DiscoveryAttribute : Attribute
16 | {
17 | ///
18 | /// Gets the order in which the evaluator/validator should be applied. Lower values are applied first.
19 | ///
20 | public int Order { get; set; } = int.MaxValue;
21 |
22 | ///
23 | /// Gets a value indicating whether the evaluator/validator is discoverable.
24 | ///
25 | public bool Enable { get; set; } = true;
26 | }
27 |
28 | ///
29 | /// Specifies discovery options for evaluators, such as the order and whether discovery is enabled.
30 | ///
31 | public sealed class EvaluatorDiscoveryAttribute : DiscoveryAttribute
32 | {
33 | }
34 |
35 | ///
36 | /// Specifies discovery options for validators, such as the order and whether discovery is enabled.
37 | ///
38 | public sealed class ValidatorDiscoveryAttribute : DiscoveryAttribute
39 | {
40 | }
41 |
--------------------------------------------------------------------------------
/src/QuerySpecification/Evaluators/IEvaluator.cs:
--------------------------------------------------------------------------------
1 | namespace Pozitron.QuerySpecification;
2 |
3 | ///
4 | /// Represents an evaluator that processes a specification.
5 | ///
6 | public interface IEvaluator
7 | {
8 | ///
9 | /// Evaluates the given specification on the provided queryable source.
10 | ///
11 | /// The type of the entity.
12 | /// The queryable source.
13 | /// The specification to evaluate.
14 | /// The evaluated queryable source.
15 | IQueryable Evaluate(IQueryable source, Specification specification) where T : class;
16 | }
17 |
--------------------------------------------------------------------------------
/src/QuerySpecification/Evaluators/IMemoryEvaluator.cs:
--------------------------------------------------------------------------------
1 | namespace Pozitron.QuerySpecification;
2 |
3 | ///
4 | /// Represents an in-memory evaluator that processes a specification.
5 | ///
6 | public interface IMemoryEvaluator
7 | {
8 | ///
9 | /// Evaluates the given specification on the provided enumerable source.
10 | ///
11 | /// The type of the entity.
12 | /// The enumerable source.
13 | /// The specification to evaluate.
14 | /// The evaluated enumerable source.
15 | IEnumerable Evaluate(IEnumerable source, Specification specification);
16 | }
17 |
--------------------------------------------------------------------------------
/src/QuerySpecification/Evaluators/OrderEvaluator.cs:
--------------------------------------------------------------------------------
1 | namespace Pozitron.QuerySpecification;
2 |
3 | ///
4 | /// Represents an evaluator for order expressions.
5 | ///
6 | [EvaluatorDiscovery(Order = 50)]
7 | public sealed class OrderEvaluator : IEvaluator, IMemoryEvaluator
8 | {
9 | ///
10 | /// Gets the singleton instance of the class.
11 | ///
12 | public static OrderEvaluator Instance = new();
13 | private OrderEvaluator() { }
14 |
15 | ///
16 | public IQueryable Evaluate(IQueryable source, Specification specification) where T : class
17 | {
18 | IOrderedQueryable? orderedQuery = null;
19 |
20 | foreach (var item in specification.Items)
21 | {
22 | if (item.Type == ItemType.Order && item.Reference is Expression> expr)
23 | {
24 | if (item.Bag == (int)OrderType.OrderBy)
25 | {
26 | orderedQuery = source.OrderBy(expr);
27 | }
28 | else if (item.Bag == (int)OrderType.OrderByDescending)
29 | {
30 | orderedQuery = source.OrderByDescending(expr);
31 | }
32 | else if (item.Bag == (int)OrderType.ThenBy)
33 | {
34 | orderedQuery = orderedQuery!.ThenBy(expr);
35 | }
36 | else if (item.Bag == (int)OrderType.ThenByDescending)
37 | {
38 | orderedQuery = orderedQuery!.ThenByDescending(expr);
39 | }
40 | }
41 | }
42 |
43 | if (orderedQuery is not null)
44 | {
45 | source = orderedQuery;
46 | }
47 |
48 | return source;
49 | }
50 |
51 | ///
52 | public IEnumerable Evaluate(IEnumerable source, Specification specification)
53 | {
54 | var compiledItems = specification.GetCompiledItems();
55 | IOrderedEnumerable? orderedQuery = null;
56 |
57 | foreach (var item in compiledItems)
58 | {
59 | if (item.Type == ItemType.Order && item.Reference is Func compiledExpr)
60 | {
61 | if (item.Bag == (int)OrderType.OrderBy)
62 | {
63 | orderedQuery = source.OrderBy(compiledExpr);
64 | }
65 | else if (item.Bag == (int)OrderType.OrderByDescending)
66 | {
67 | orderedQuery = source.OrderByDescending(compiledExpr);
68 | }
69 | else if (item.Bag == (int)OrderType.ThenBy)
70 | {
71 | orderedQuery = orderedQuery!.ThenBy(compiledExpr);
72 | }
73 | else if (item.Bag == (int)OrderType.ThenByDescending)
74 | {
75 | orderedQuery = orderedQuery!.ThenByDescending(compiledExpr);
76 | }
77 | }
78 | }
79 |
80 | if (orderedQuery is not null)
81 | {
82 | source = orderedQuery;
83 | }
84 |
85 | return source;
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/src/QuerySpecification/Evaluators/WhereEvaluator.cs:
--------------------------------------------------------------------------------
1 | namespace Pozitron.QuerySpecification;
2 |
3 | ///
4 | /// Represents an evaluator for where expressions.
5 | ///
6 | [EvaluatorDiscovery(Order = 10)]
7 | public sealed class WhereEvaluator : IEvaluator, IMemoryEvaluator
8 | {
9 | ///
10 | /// Gets the singleton instance of the class.
11 | ///
12 | public static WhereEvaluator Instance = new();
13 | private WhereEvaluator() { }
14 |
15 | ///
16 | public IQueryable Evaluate(IQueryable source, Specification specification) where T : class
17 | {
18 | foreach (var item in specification.Items)
19 | {
20 | if (item.Type == ItemType.Where && item.Reference is Expression> expr)
21 | {
22 | source = source.Where(expr);
23 | }
24 | }
25 |
26 | return source;
27 | }
28 |
29 | ///
30 | public IEnumerable Evaluate(IEnumerable source, Specification specification)
31 | {
32 | var compiledItems = specification.GetCompiledItems();
33 |
34 | foreach (var item in compiledItems)
35 | {
36 | if (item.Type == ItemType.Where && item.Reference is Func compiledExpr)
37 | {
38 | source = source.Where(compiledExpr);
39 | }
40 | }
41 |
42 | return source;
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/QuerySpecification/Exceptions/ConcurrentSelectorsException.cs:
--------------------------------------------------------------------------------
1 | namespace Pozitron.QuerySpecification;
2 |
3 | ///
4 | /// Exception thrown when concurrent selectors are defined in the specification.
5 | ///
6 | public class ConcurrentSelectorsException : Exception
7 | {
8 | private const string _message = "Concurrent specification selector transforms defined. Ensure only one of the Select() or SelectMany() transforms is used in the same specification!";
9 |
10 | ///
11 | /// Initializes a new instance of the class.
12 | ///
13 | public ConcurrentSelectorsException()
14 | : base(_message)
15 | {
16 | }
17 |
18 | ///
19 | /// Initializes a new instance of the class with a specified inner exception.
20 | ///
21 | /// The exception that is the cause of this exception.
22 | public ConcurrentSelectorsException(Exception innerException)
23 | : base(_message, innerException)
24 | {
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/QuerySpecification/Exceptions/EntityNotFoundException.cs:
--------------------------------------------------------------------------------
1 | namespace Pozitron.QuerySpecification;
2 |
3 | ///
4 | /// Exception thrown when an entity is not found.
5 | ///
6 | public class EntityNotFoundException : Exception
7 | {
8 | ///
9 | /// Initializes a new instance of the class.
10 | ///
11 | public EntityNotFoundException()
12 | : base($"The queried entity was not found!")
13 | {
14 | }
15 |
16 | ///
17 | /// Initializes a new instance of the class with a specified entity name.
18 | ///
19 | /// The name of the entity that was not found.
20 | public EntityNotFoundException(string entityName)
21 | : base($"The queried entity: {entityName} was not found!")
22 | {
23 | }
24 |
25 | ///
26 | /// Initializes a new instance of the class with a specified entity name and inner exception.
27 | ///
28 | /// The name of the entity that was not found.
29 | /// The exception that is the cause of this exception.
30 | public EntityNotFoundException(string entityName, Exception innerException)
31 | : base($"The queried entity: {entityName} was not found!", innerException)
32 | {
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/QuerySpecification/Exceptions/InvalidLikePatternException.cs:
--------------------------------------------------------------------------------
1 | namespace Pozitron.QuerySpecification;
2 |
3 | ///
4 | /// Exception thrown when an invalid like pattern is encountered.
5 | ///
6 | public class InvalidLikePatternException : Exception
7 | {
8 | private const string _message = "Invalid like pattern: ";
9 |
10 | ///
11 | /// Initializes a new instance of the class with a specified pattern.
12 | ///
13 | /// The invalid like pattern.
14 | public InvalidLikePatternException(string pattern)
15 | : base($"{_message}{pattern}")
16 | {
17 | }
18 |
19 | ///
20 | /// Initializes a new instance of the class with a specified pattern and inner exception.
21 | ///
22 | /// The invalid like pattern.
23 | /// The exception that is the cause of this exception.
24 | public InvalidLikePatternException(string pattern, Exception innerException)
25 | : base($"{_message}{pattern}", innerException)
26 | {
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/QuerySpecification/Exceptions/SelectorNotFoundException.cs:
--------------------------------------------------------------------------------
1 | namespace Pozitron.QuerySpecification;
2 |
3 | ///
4 | /// Exception thrown when a selector is not found in the specification.
5 | ///
6 | public class SelectorNotFoundException : Exception
7 | {
8 | private const string _message = "The specification must have a selector transform defined. Ensure either Select() or SelectMany() is used in the specification!";
9 |
10 | ///
11 | /// Initializes a new instance of the class.
12 | ///
13 | public SelectorNotFoundException()
14 | : base(_message)
15 | {
16 | }
17 |
18 | ///
19 | /// Initializes a new instance of the class with a specified inner exception.
20 | ///
21 | /// The exception that is the cause of this exception.
22 | public SelectorNotFoundException(Exception innerException)
23 | : base(_message, innerException)
24 | {
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/QuerySpecification/Expressions/IncludeExpression.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics;
2 |
3 | namespace Pozitron.QuerySpecification;
4 |
5 | ///
6 | /// Represents an include expression used in a specification.
7 | ///
8 | /// The type of the entity.
9 | public sealed class IncludeExpression
10 | {
11 | ///
12 | /// Gets the lambda expression for the include.
13 | ///
14 | public LambdaExpression LambdaExpression { get; }
15 |
16 | ///
17 | /// Gets the type of the include.
18 | ///
19 | public IncludeType Type { get; }
20 |
21 | ///
22 | /// Initializes a new instance of the class.
23 | ///
24 | /// The lambda expression for the include.
25 | /// The type of the include.
26 | public IncludeExpression(LambdaExpression expression, IncludeType type)
27 | {
28 | Debug.Assert(expression is not null);
29 | LambdaExpression = expression;
30 | Type = type;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/QuerySpecification/Expressions/IncludeType.cs:
--------------------------------------------------------------------------------
1 | namespace Pozitron.QuerySpecification;
2 |
3 | ///
4 | /// Specifies the type of include operation in a specification.
5 | ///
6 | public enum IncludeType
7 | {
8 | ///
9 | /// Represents an Include operation.
10 | ///
11 | Include = 1,
12 |
13 | ///
14 | /// Represents a ThenInclude operation after reference include.
15 | ///
16 | ThenIncludeAfterReference = 2,
17 |
18 | ///
19 | /// Represents a ThenInclude operation after collection include.
20 | ///
21 | ThenIncludeAfterCollection = 3
22 | }
23 |
--------------------------------------------------------------------------------
/src/QuerySpecification/Expressions/LikeExpression.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics;
2 |
3 | namespace Pozitron.QuerySpecification;
4 |
5 | ///
6 | /// Represents a like expression used in a specification.
7 | ///
8 | /// The type of the entity.
9 | public sealed class LikeExpression
10 | {
11 | ///
12 | /// Gets the key selector expression.
13 | ///
14 | public Expression> KeySelector { get; }
15 |
16 | ///
17 | /// Gets the pattern to match.
18 | ///
19 | public string Pattern { get; }
20 |
21 | ///
22 | /// Gets the group number.
23 | ///
24 | public int Group { get; }
25 |
26 | ///
27 | /// Initializes a new instance of the class.
28 | ///
29 | /// The key selector expression.
30 | /// The pattern to match.
31 | /// The group number.
32 | public LikeExpression(Expression> keySelector, string pattern, int group = 1)
33 | {
34 | Debug.Assert(keySelector is not null);
35 | Debug.Assert(!string.IsNullOrEmpty(pattern));
36 | KeySelector = keySelector;
37 | Pattern = pattern;
38 | Group = group;
39 | }
40 | }
41 |
42 | ///
43 | /// Represents a compiled like expression used in a specification.
44 | ///
45 | /// The type of the entity.
46 | public sealed class LikeExpressionCompiled
47 | {
48 | ///
49 | /// Gets the compiled key selector function.
50 | ///
51 | public Func KeySelector { get; }
52 |
53 | ///
54 | /// Gets the pattern to match.
55 | ///
56 | public string Pattern { get; }
57 |
58 | ///
59 | /// Gets the group number.
60 | ///
61 | public int Group { get; }
62 |
63 | ///
64 | /// Initializes a new instance of the class.
65 | ///
66 | /// The compiled key selector function.
67 | /// The pattern to match.
68 | /// The group number.
69 | public LikeExpressionCompiled(Func keySelector, string pattern, int group = 1)
70 | {
71 | Debug.Assert(keySelector is not null);
72 | Debug.Assert(!string.IsNullOrEmpty(pattern));
73 | KeySelector = keySelector;
74 | Pattern = pattern;
75 | Group = group;
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/QuerySpecification/Expressions/OrderExpression.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics;
2 |
3 | namespace Pozitron.QuerySpecification;
4 |
5 | ///
6 | /// Represents an order expression used in a specification.
7 | ///
8 | /// The type of the entity.
9 | public sealed class OrderExpression
10 | {
11 | ///
12 | /// Gets the key selector expression.
13 | ///
14 | public Expression> KeySelector { get; }
15 |
16 | ///
17 | /// Gets the type of the order.
18 | ///
19 | public OrderType Type { get; }
20 |
21 | ///
22 | /// Initializes a new instance of the class.
23 | ///
24 | /// The key selector expression.
25 | /// The type of the order.
26 | public OrderExpression(Expression> keySelector, OrderType type)
27 | {
28 | Debug.Assert(keySelector is not null);
29 | KeySelector = keySelector;
30 | Type = type;
31 | }
32 | }
33 |
34 | ///
35 | /// Represents a compiled order expression used in a specification.
36 | ///
37 | /// The type of the entity.
38 | public sealed class OrderExpressionCompiled
39 | {
40 | ///
41 | /// Gets the compiled key selector function.
42 | ///
43 | public Func KeySelector { get; }
44 |
45 | ///
46 | /// Gets the type of the order.
47 | ///
48 | public OrderType Type { get; }
49 |
50 | ///
51 | /// Initializes a new instance of the class.
52 | ///
53 | /// The compiled key selector function.
54 | /// The type of the order.
55 | public OrderExpressionCompiled(Func keySelector, OrderType type)
56 | {
57 | Debug.Assert(keySelector is not null);
58 | KeySelector = keySelector;
59 | Type = type;
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/QuerySpecification/Expressions/OrderType.cs:
--------------------------------------------------------------------------------
1 | namespace Pozitron.QuerySpecification;
2 |
3 | ///
4 | /// Specifies the type of order operation in a specification.
5 | ///
6 | public enum OrderType
7 | {
8 | ///
9 | /// Represents an order by operation.
10 | ///
11 | OrderBy = 1,
12 |
13 | ///
14 | /// Represents an order by descending operation.
15 | ///
16 | OrderByDescending = 2,
17 |
18 | ///
19 | /// Represents a then by operation.
20 | ///
21 | ThenBy = 3,
22 |
23 | ///
24 | /// Represents a then by descending operation.
25 | ///
26 | ThenByDescending = 4
27 | }
28 |
--------------------------------------------------------------------------------
/src/QuerySpecification/Expressions/SelectType.cs:
--------------------------------------------------------------------------------
1 | namespace Pozitron.QuerySpecification;
2 |
3 | ///
4 | /// Specifies the type of select operation in a specification.
5 | ///
6 | public enum SelectType
7 | {
8 | ///
9 | /// Represents a select operation.
10 | ///
11 | Select = 1,
12 |
13 | ///
14 | /// Represents a select many operation.
15 | ///
16 | SelectMany = 2
17 | }
18 |
--------------------------------------------------------------------------------
/src/QuerySpecification/Expressions/WhereExpression.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics;
2 |
3 | namespace Pozitron.QuerySpecification;
4 |
5 | ///
6 | /// Represents a where expression used in a specification.
7 | ///
8 | /// The type of the entity.
9 | public sealed class WhereExpression
10 | {
11 | ///
12 | /// Gets the filter expression.
13 | ///
14 | public Expression> Filter { get; }
15 |
16 | ///
17 | /// Initializes a new instance of the class.
18 | ///
19 | /// The filter expression.
20 | public WhereExpression(Expression> filter)
21 | {
22 | Debug.Assert(filter is not null);
23 | Filter = filter;
24 | }
25 | }
26 |
27 | ///
28 | /// Represents a compiled where expression used in a specification.
29 | ///
30 | /// The type of the entity.
31 | public sealed class WhereExpressionCompiled
32 | {
33 | ///
34 | /// Gets the compiled filter function.
35 | ///
36 | public Func Filter { get; }
37 |
38 | ///
39 | /// Initializes a new instance of the class.
40 | ///
41 | /// The compiled filter function.
42 | public WhereExpressionCompiled(Func filter)
43 | {
44 | Debug.Assert(filter is not null);
45 | Filter = filter;
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/QuerySpecification/GlobalUsings.cs:
--------------------------------------------------------------------------------
1 | global using System.Linq.Expressions;
2 |
--------------------------------------------------------------------------------
/src/QuerySpecification/IProjectionRepository.cs:
--------------------------------------------------------------------------------
1 | namespace Pozitron.QuerySpecification;
2 |
3 | ///
4 | /// Represents a repository for projecting entities to different result types.
5 | ///
6 | /// The type of the entity.
7 | public interface IProjectionRepository where T : class
8 | {
9 | ///
10 | /// Projects the first entity that matches the specification to a result. It throws an exception if no entity is found.
11 | /// It ignores the selector in the specification, and projects the entity to the result type using the Map method.
12 | ///
13 | /// The type of the result.
14 | /// The specification to evaluate.
15 | /// The cancellation token.
16 | /// A task that represents the asynchronous operation. The task result contains the projected result.
17 | ///
18 | Task ProjectToFirstAsync(Specification specification, CancellationToken cancellationToken = default);
19 |
20 | ///
21 | /// Projects the first entity that matches the specification to a result or null if no entity is found.
22 | /// It ignores the selector in the specification, and projects the entity to the result type using the Map method.
23 | ///
24 | /// The type of the result.
25 | /// The specification to evaluate.
26 | /// The cancellation token.
27 | /// A task that represents the asynchronous operation. The task result contains the projected result or null if no entity is found.
28 | Task ProjectToFirstOrDefaultAsync(Specification specification, CancellationToken cancellationToken = default);
29 |
30 | ///
31 | /// Projects the entities that match the specification to a list of results.
32 | /// It ignores the selector in the specification. It projects the entities to the result type using the Map method.
33 | ///
34 | /// The type of the result.
35 | /// The specification to evaluate.
36 | /// The cancellation token.
37 | /// A task that represents the asynchronous operation. The task result contains the list of projected results.
38 | Task> ProjectToListAsync(Specification specification, CancellationToken cancellationToken = default);
39 |
40 | ///
41 | /// Projects the entities that match the specification to a paged list of results.
42 | /// It ignores the selector in the specification, and projects the entities to the result type using the Map method.
43 | /// It ignores the paging filter in the specification, and applies pagination based on the provided paging filter.
44 | ///
45 | /// The type of the result.
46 | /// The specification to evaluate.
47 | /// The paging filter.
48 | /// The cancellation token.
49 | /// A task that represents the asynchronous operation. The task result contains the paged list of projected results.
50 | Task> ProjectToListAsync(Specification specification, PagingFilter filter, CancellationToken cancellationToken = default);
51 | }
52 |
--------------------------------------------------------------------------------
/src/QuerySpecification/IRepositoryBase.cs:
--------------------------------------------------------------------------------
1 | namespace Pozitron.QuerySpecification;
2 |
3 | ///
4 | /// Represents a repository for accessing and modifying entities.
5 | ///
6 | /// The type of the entity.
7 | public interface IRepositoryBase : IReadRepositoryBase where T : class
8 | {
9 | ///
10 | /// Adds a new entity to the repository.
11 | ///
12 | /// The entity to add.
13 | /// The cancellation token.
14 | /// A task that represents the asynchronous operation. The task result contains the added entity.
15 | Task AddAsync(T entity, CancellationToken cancellationToken = default);
16 |
17 | ///
18 | /// Adds a range of new entities to the repository.
19 | ///
20 | /// The entities to add.
21 | /// The cancellation token.
22 | /// A task that represents the asynchronous operation. The task result contains the added entities.
23 | Task> AddRangeAsync(IEnumerable entities, CancellationToken cancellationToken = default);
24 |
25 | ///
26 | /// Updates an existing entity in the repository.
27 | ///
28 | /// The entity to update.
29 | /// The cancellation token.
30 | /// A task that represents the asynchronous operation.
31 | Task UpdateAsync(T entity, CancellationToken cancellationToken = default);
32 |
33 | ///
34 | /// Deletes an existing entity from the repository.
35 | ///
36 | /// The entity to delete.
37 | /// The cancellation token.
38 | /// A task that represents the asynchronous operation.
39 | Task DeleteAsync(T entity, CancellationToken cancellationToken = default);
40 |
41 | ///
42 | /// Deletes a range of existing entities from the repository.
43 | ///
44 | /// The entities to delete.
45 | /// The cancellation token.
46 | /// A task that represents the asynchronous operation.
47 | Task DeleteRangeAsync(IEnumerable entities, CancellationToken cancellationToken = default);
48 |
49 | ///
50 | /// Saves all changes made in the repository.
51 | ///
52 | /// The cancellation token.
53 | /// A task that represents the asynchronous operation. The task result contains the number of state entries written to the database.
54 | Task SaveChangesAsync(CancellationToken cancellationToken = default);
55 | }
56 |
--------------------------------------------------------------------------------
/src/QuerySpecification/Internals/ItemType.cs:
--------------------------------------------------------------------------------
1 | namespace Pozitron.QuerySpecification;
2 |
3 | internal static class ItemType
4 | {
5 | public const int Where = -1;
6 | public const int Order = -2;
7 | public const int Include = -3;
8 | public const int IncludeString = -4;
9 | public const int Like = -5;
10 | public const int Select = -6;
11 | public const int Compiled = -7;
12 |
13 | // We can save 16 bytes (on x64) by storing both Flags and Paging in the same item.
14 | public const int Paging = -8; // Stored in the reference
15 | public const int Flags = -8; // Stored in the bag
16 |
17 | public const int QueryTag = -9;
18 | public const int CacheKey = -10;
19 | }
20 |
--------------------------------------------------------------------------------
/src/QuerySpecification/Internals/Iterator.cs:
--------------------------------------------------------------------------------
1 | using System.Collections;
2 | using System.Diagnostics.CodeAnalysis;
3 |
4 | namespace Pozitron.QuerySpecification;
5 |
6 | internal abstract class Iterator : IEnumerable, IEnumerator
7 | {
8 | private readonly int _threadId = Environment.CurrentManagedThreadId;
9 |
10 | private protected int _state;
11 | private protected TSource _current = default!;
12 |
13 | public Iterator GetEnumerator()
14 | {
15 | var enumerator = _state == 0 && _threadId == Environment.CurrentManagedThreadId ? this : Clone();
16 | enumerator._state = 1;
17 | return enumerator;
18 | }
19 |
20 | public abstract Iterator Clone();
21 | public abstract bool MoveNext();
22 |
23 | public TSource Current => _current;
24 | object? IEnumerator.Current => Current;
25 | IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
26 | IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
27 |
28 | [ExcludeFromCodeCoverage]
29 | void IEnumerator.Reset() => throw new NotSupportedException();
30 |
31 | public virtual void Dispose()
32 | {
33 | _current = default!;
34 | _state = -1;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/QuerySpecification/Internals/SpecFlags.cs:
--------------------------------------------------------------------------------
1 | namespace Pozitron.QuerySpecification;
2 |
3 | [Flags]
4 | internal enum SpecFlags
5 | {
6 | IgnoreQueryFilters = 1,
7 | AsNoTracking = 2,
8 | AsNoTrackingWithIdentityResolution = 4,
9 | AsTracking = 8,
10 | AsSplitQuery = 16,
11 | IgnoreAutoIncludes = 32,
12 | }
13 |
--------------------------------------------------------------------------------
/src/QuerySpecification/Internals/SpecItem.cs:
--------------------------------------------------------------------------------
1 | namespace Pozitron.QuerySpecification;
2 |
3 | internal struct SpecItem
4 | {
5 | public int Type; // 0-4 bytes
6 | public int Bag; // 4-8 bytes
7 | public object? Reference; // 8-16 bytes (on x64)
8 | }
9 |
--------------------------------------------------------------------------------
/src/QuerySpecification/Internals/SpecIterator.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics;
2 |
3 | namespace Pozitron.QuerySpecification;
4 |
5 | internal sealed class SpecIterator : Iterator
6 | {
7 | private readonly SpecItem[] _source;
8 | private readonly int _type;
9 |
10 | public SpecIterator(SpecItem[] source, int type)
11 | {
12 | Debug.Assert(source != null && source.Length > 0);
13 | _type = type;
14 | _source = source;
15 | }
16 |
17 | public override Iterator Clone() =>
18 | new SpecIterator(_source, _type);
19 |
20 | public override bool MoveNext()
21 | {
22 | var index = _state - 1;
23 | var source = _source;
24 | var type = _type;
25 |
26 | while (unchecked((uint)index < (uint)source.Length))
27 | {
28 | var item = source[index];
29 | index = _state++;
30 |
31 | if (item.Type == type && item.Reference is TObject reference)
32 | {
33 | _current = reference;
34 | return true;
35 | }
36 | }
37 |
38 | Dispose();
39 | return false;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/QuerySpecification/Internals/SpecLike.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics;
2 |
3 | namespace Pozitron.QuerySpecification;
4 |
5 | internal sealed class SpecLike
6 | {
7 | public Expression> KeySelector { get; }
8 | public string Pattern { get; }
9 |
10 | public SpecLike(Expression> keySelector, string pattern)
11 | {
12 | Debug.Assert(keySelector is not null);
13 | Debug.Assert(!string.IsNullOrEmpty(pattern));
14 | KeySelector = keySelector;
15 | Pattern = pattern;
16 | }
17 | }
18 |
19 | internal sealed class SpecLikeCompiled
20 | {
21 | public Func KeySelector { get; }
22 | public string Pattern { get; }
23 |
24 | public SpecLikeCompiled(Func keySelector, string pattern)
25 | {
26 | Debug.Assert(keySelector is not null);
27 | Debug.Assert(!string.IsNullOrEmpty(pattern));
28 | KeySelector = keySelector;
29 | Pattern = pattern;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/QuerySpecification/Internals/SpecPaging.cs:
--------------------------------------------------------------------------------
1 | namespace Pozitron.QuerySpecification;
2 |
3 | internal sealed class SpecPaging
4 | {
5 | public int Take = -1;
6 | public int Skip = -1;
7 | }
8 |
--------------------------------------------------------------------------------
/src/QuerySpecification/Internals/SpecSelectIterator.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics;
2 |
3 | namespace Pozitron.QuerySpecification;
4 |
5 | internal sealed class SpecSelectIterator : Iterator
6 | {
7 | private readonly SpecItem[] _source;
8 | private readonly Func _selector;
9 | private readonly int _type;
10 |
11 | public SpecSelectIterator(SpecItem[] source, int type, Func selector)
12 | {
13 | Debug.Assert(source != null && source.Length > 0);
14 | Debug.Assert(selector != null);
15 | _type = type;
16 | _source = source;
17 | _selector = selector;
18 | }
19 |
20 | public override Iterator Clone() =>
21 | new SpecSelectIterator(_source, _type, _selector);
22 |
23 | public override bool MoveNext()
24 | {
25 | var index = _state - 1;
26 | var source = _source;
27 | var type = _type;
28 |
29 | while (unchecked((uint)index < (uint)source.Length))
30 | {
31 | var item = source[index];
32 | index = _state++;
33 | if (item.Type == type && item.Reference is TObject reference)
34 | {
35 | _current = _selector(reference, item.Bag);
36 | return true;
37 | }
38 | }
39 |
40 | Dispose();
41 | return false;
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/QuerySpecification/Paging/PagedResult.cs:
--------------------------------------------------------------------------------
1 | namespace Pozitron.QuerySpecification;
2 |
3 | ///
4 | /// Represents a paged result with data and pagination information.
5 | ///
6 | /// The type of the data.
7 | public record PagedResult
8 | {
9 | ///
10 | /// Gets the pagination information.
11 | ///
12 | public Pagination Pagination { get; }
13 |
14 | ///
15 | /// Gets the data.
16 | ///
17 | public List Data { get; }
18 |
19 | ///
20 | /// Initializes a new instance of the class.
21 | ///
22 | /// The data.
23 | /// The pagination information.
24 | public PagedResult(List data, Pagination pagination)
25 | {
26 | Data = data;
27 | Pagination = pagination;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/QuerySpecification/Paging/PaginationSettings.cs:
--------------------------------------------------------------------------------
1 | namespace Pozitron.QuerySpecification;
2 |
3 | ///
4 | /// Represents pagination settings.
5 | ///
6 | public record PaginationSettings
7 | {
8 | ///
9 | /// Gets the default page number.
10 | ///
11 | public int DefaultPage { get; } = 1;
12 |
13 | ///
14 | /// Gets the default page size.
15 | ///
16 | public int DefaultPageSize { get; } = 10;
17 |
18 | ///
19 | /// Gets the default page size limit.
20 | ///
21 | public int DefaultPageSizeLimit { get; } = 50;
22 |
23 | ///
24 | /// Gets the default pagination settings.
25 | ///
26 | public static PaginationSettings Default { get; } = new();
27 |
28 | private PaginationSettings() { }
29 |
30 | ///
31 | /// Initializes a new instance of the class with the specified default page size and page size limit.
32 | ///
33 | /// The default page size.
34 | /// The default page size limit.
35 | public PaginationSettings(int defaultPageSize, int defaultPageSizeLimit)
36 | {
37 | DefaultPageSize = defaultPageSize;
38 | DefaultPageSizeLimit = defaultPageSizeLimit;
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/QuerySpecification/Paging/PagingFilter.cs:
--------------------------------------------------------------------------------
1 | namespace Pozitron.QuerySpecification;
2 |
3 | ///
4 | /// Represents a filter for paging.
5 | ///
6 | public record PagingFilter
7 | {
8 | ///
9 | /// Gets or sets the page number.
10 | ///
11 | public int? Page { get; init; }
12 |
13 | ///
14 | /// Gets or sets the page size.
15 | ///
16 | public int? PageSize { get; init; }
17 | }
18 |
--------------------------------------------------------------------------------
/src/QuerySpecification/QuerySpecification.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Pozitron.QuerySpecification
5 | Pozitron.QuerySpecification
6 | Pozitron.QuerySpecification
7 | Abstract package for building query specifications.
8 | Abstract package for building query specifications.
9 |
10 | 11.2.0
11 | fiseni pozitron query specification
12 |
13 | Refer to Releases page for details.
14 | https://github.com/fiseni/QuerySpecification/releases
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/src/QuerySpecification/SpecificationExtensions.cs:
--------------------------------------------------------------------------------
1 | namespace Pozitron.QuerySpecification;
2 |
3 | ///
4 | /// Extension methods for specifications.
5 | ///
6 | public static class SpecificationExtensions
7 | {
8 | ///
9 | /// Creates a new specification by applying a projection specification to the current specification.
10 | ///
11 | /// This method clones the source specification and applies the projection specification's select
12 | /// statements to create a new specification. The input specifications remain unchanged.
13 | /// The type of the entity.
14 | /// The type of the result.
15 | /// The source specification to which the projection will be applied. Cannot be .
16 | /// The projection specification that defines the transformation to apply to the source specification. Cannot be
17 | /// .
18 | /// A new that represents the result of applying the projection to the
19 | /// source specification.
20 | public static Specification WithProjectionOf(this Specification source, Specification projectionSpec)
21 | {
22 | var newSpec = source.Clone();
23 |
24 | foreach (var item in projectionSpec.Items)
25 | {
26 | if (item.Type == ItemType.Select && item.Reference is not null)
27 | {
28 | newSpec.AddOrUpdateInternal(item.Type, item.Reference, item.Bag);
29 | return newSpec;
30 | }
31 | }
32 |
33 | return newSpec;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/QuerySpecification/Validators/IValidator.cs:
--------------------------------------------------------------------------------
1 | namespace Pozitron.QuerySpecification;
2 |
3 | ///
4 | /// Represents a validator for specifications.
5 | ///
6 | public interface IValidator
7 | {
8 | ///
9 | /// Determines whether the specified entity is valid according to the given specification.
10 | ///
11 | /// The type of the entity.
12 | /// The entity to validate.
13 | /// The specification to evaluate.
14 | /// true if the entity is valid; otherwise, false.
15 | bool IsValid(T entity, Specification specification);
16 | }
17 |
--------------------------------------------------------------------------------
/src/QuerySpecification/Validators/LikeValidator.cs:
--------------------------------------------------------------------------------
1 | namespace Pozitron.QuerySpecification;
2 |
3 | /*
4 | public bool IsValid(T entity, Specification specification)
5 | {
6 | foreach (var likeGroup in specification.LikeExpressions.GroupBy(x => x.Group))
7 | {
8 | if (likeGroup.Any(c => c.KeySelectorFunc(entity)?.Like(c.Pattern) ?? false) == false) return false;
9 | }
10 | return true;
11 | }
12 | This was the previous implementation.We're trying to avoid allocations of LikeExpressions, GroupBy and LINQ.
13 | Instead of GroupBy, we have a single array sorted by group, and we slice it to get the groups.
14 | The new implementation preserves the behavior and reduces allocations drastically.
15 | For 1000 entities, the allocations are reduced from 651.160 bytes to ZERO bytes. Refer to LikeValidatorBenchmark results.
16 | */
17 |
18 | ///
19 | /// Represents a validator for "like" expressions.
20 | ///
21 | [ValidatorDiscovery(Order = 20)]
22 | public sealed class LikeValidator : IValidator
23 | {
24 | ///
25 | /// Gets the singleton instance of the class.
26 | ///
27 | public static LikeValidator Instance = new();
28 | private LikeValidator() { }
29 |
30 | ///
31 | public bool IsValid(T entity, Specification specification)
32 | {
33 | var compiledItems = specification.GetCompiledItems();
34 | if (compiledItems.Length == 0) return true;
35 |
36 | var startIndexLikeItems = Array.FindIndex(compiledItems, item => item.Type == ItemType.Like);
37 | if (startIndexLikeItems == -1) return true;
38 |
39 | // The like items are contiguously placed as a last segment in the array and are already sorted by group.
40 | return IsValid(entity, compiledItems.AsSpan()[startIndexLikeItems..compiledItems.Length]);
41 | }
42 |
43 | private static bool IsValid(T entity, ReadOnlySpan span)
44 | {
45 | var groupStart = 0;
46 | for (var i = 1; i <= span.Length; i++)
47 | {
48 | // If we reached the end of the span or the group has changed, we slice and process the group.
49 | if (i == span.Length || span[i].Bag != span[groupStart].Bag)
50 | {
51 | if (IsValidInOrGroup(entity, span[groupStart..i]) is false)
52 | {
53 | return false;
54 | }
55 | groupStart = i;
56 | }
57 | }
58 | return true;
59 |
60 | static bool IsValidInOrGroup(T entity, ReadOnlySpan span)
61 | {
62 | var validOrGroup = false;
63 | foreach (var specItem in span)
64 | {
65 | if (specItem.Reference is not SpecLikeCompiled specLike) continue;
66 |
67 | if (specLike.KeySelector(entity)?.Like(specLike.Pattern) ?? false)
68 | {
69 | validOrGroup = true;
70 | break;
71 | }
72 | }
73 | return validOrGroup;
74 | }
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/QuerySpecification/Validators/SpecificationValidator.cs:
--------------------------------------------------------------------------------
1 | namespace Pozitron.QuerySpecification;
2 |
3 | ///
4 | /// Validates specifications.
5 | ///
6 | public class SpecificationValidator
7 | {
8 | ///
9 | /// Gets the default instance of the class.
10 | ///
11 | public static SpecificationValidator Default = new();
12 |
13 | ///
14 | /// Gets the list of validators.
15 | ///
16 | protected List Validators { get; }
17 |
18 | ///
19 | /// Initializes a new instance of the class.
20 | ///
21 | public SpecificationValidator()
22 | {
23 | Validators = TypeDiscovery.IsAutoDiscoveryEnabled
24 | ? TypeDiscovery.GetValidators()
25 | :
26 | [
27 | WhereValidator.Instance,
28 | LikeValidator.Instance,
29 | ];
30 | }
31 |
32 | ///
33 | /// Initializes a new instance of the class with the specified validators.
34 | ///
35 | /// The validators to use.
36 | public SpecificationValidator(IEnumerable validators)
37 | {
38 | Validators = validators.ToList();
39 | }
40 |
41 | ///
42 | /// Determines whether the specified entity is valid according to the given specification.
43 | ///
44 | /// The type of the entity.
45 | /// The entity to validate.
46 | /// The specification to evaluate.
47 | /// true if the entity is valid; otherwise, false.
48 | public virtual bool IsValid(T entity, Specification specification)
49 | {
50 | if (specification.IsEmpty) return true;
51 |
52 | foreach (var validator in Validators)
53 | {
54 | if (validator.IsValid(entity, specification) == false)
55 | return false;
56 | }
57 |
58 | return true;
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/QuerySpecification/Validators/WhereValidator.cs:
--------------------------------------------------------------------------------
1 | namespace Pozitron.QuerySpecification;
2 |
3 | ///
4 | /// Represents a validator for where expressions.
5 | ///
6 | [ValidatorDiscovery(Order = 10)]
7 | public sealed class WhereValidator : IValidator
8 | {
9 | ///
10 | /// Gets the singleton instance of the class.
11 | ///
12 | public static WhereValidator Instance = new();
13 | private WhereValidator() { }
14 |
15 | ///
16 | public bool IsValid(T entity, Specification specification)
17 | {
18 | var compiledItems = specification.GetCompiledItems();
19 |
20 | foreach (var item in compiledItems)
21 | {
22 | if (item.Type == ItemType.Where && item.Reference is Func compiledExpr)
23 | {
24 | if (compiledExpr(entity) == false)
25 | return false;
26 | }
27 | }
28 |
29 | return true;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/QuerySpecification/build/Pozitron.QuerySpecification.targets:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | <_SpecAutoDiscoveryLower>$([System.String]::Copy('$(SpecAutoDiscovery)').ToLowerInvariant())
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/tests/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Tests
5 | net9.0
6 | enable
7 | enable
8 |
9 | false
10 | true
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | all
19 | runtime; build; native; contentfiles; analyzers; buildtransitive
20 |
21 |
22 | all
23 | runtime; build; native; contentfiles; analyzers; buildtransitive
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/tests/QuerySpecification.AutoDiscovery.Tests/GlobalUsings.cs:
--------------------------------------------------------------------------------
1 | global using FluentAssertions;
2 | global using Pozitron.QuerySpecification;
3 | global using System.Linq.Expressions;
4 | global using Xunit;
5 |
--------------------------------------------------------------------------------
/tests/QuerySpecification.AutoDiscovery.Tests/QuerySpecification.AutoDiscovery.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/tests/QuerySpecification.AutoDiscovery.Tests/SpecificationEvaluatorTests.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.CompilerServices;
2 |
3 | namespace Tests;
4 |
5 | public class SpecificationEvaluatorTests
6 | {
7 | [Fact]
8 | public void DefaultSingleton_ScansEvaluators_GivenAutoDiscoveryEnabled()
9 | {
10 | var evaluator = SpecificationEvaluator.Default;
11 |
12 | var result = EvaluatorsOf(evaluator);
13 |
14 | result.Should().HaveCountGreaterThan(1);
15 | result.Should().ContainSingle(x => x is TestEvaluator);
16 | }
17 |
18 | [Fact]
19 | public void Constructor_ScansEvaluators_GivenAutoDiscoveryEnabled()
20 | {
21 | var evaluator = new SpecificationEvaluator();
22 |
23 | var result = EvaluatorsOf(evaluator);
24 |
25 | result.Should().HaveCountGreaterThan(1);
26 | result.Should().ContainSingle(x => x is TestEvaluator);
27 | }
28 |
29 | [UnsafeAccessor(UnsafeAccessorKind.Field, Name = "k__BackingField")]
30 | public static extern ref List EvaluatorsOf(SpecificationEvaluator @this);
31 |
32 | public class TestEvaluator : IEvaluator
33 | {
34 | public IQueryable Evaluate(IQueryable source, Specification specification) where T : class
35 | {
36 | return source;
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/tests/QuerySpecification.AutoDiscovery.Tests/SpecificationMemoryEvaluatorTests.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.CompilerServices;
2 |
3 | namespace Tests;
4 |
5 | public class SpecificationMemoryEvaluatorTests
6 | {
7 | [Fact]
8 | public void DefaultSingleton_ScansEvaluators_GivenAutoDiscoveryEnabled()
9 | {
10 | var evaluator = SpecificationMemoryEvaluator.Default;
11 |
12 | var result = EvaluatorsOf(evaluator);
13 |
14 | result.Should().HaveCountGreaterThan(1);
15 | result.Should().ContainSingle(x => x is TestMemoryEvaluator);
16 | }
17 |
18 | [Fact]
19 | public void Constructor_ScansEvaluators_GivenAutoDiscoveryEnabled()
20 | {
21 | var evaluator = new SpecificationMemoryEvaluator();
22 |
23 | var result = EvaluatorsOf(evaluator);
24 |
25 | result.Should().HaveCountGreaterThan(1);
26 | result.Should().ContainSingle(x => x is TestMemoryEvaluator);
27 | }
28 |
29 | [UnsafeAccessor(UnsafeAccessorKind.Field, Name = "k__BackingField")]
30 | public static extern ref List EvaluatorsOf(SpecificationMemoryEvaluator @this);
31 |
32 | public class TestMemoryEvaluator : IMemoryEvaluator
33 | {
34 | public IEnumerable Evaluate(IEnumerable source, Specification specification)
35 | {
36 | return source;
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/tests/QuerySpecification.AutoDiscovery.Tests/SpecificationValidatorTests.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.CompilerServices;
2 |
3 | namespace Tests;
4 |
5 | public class SpecificationValidatorTests
6 | {
7 | [Fact]
8 | public void DefaultSingleton_ScansValidators_GivenAutoDiscoveryEnabled()
9 | {
10 | var validators = SpecificationValidator.Default;
11 |
12 | var result = ValidatorsOf(validators);
13 |
14 | result.Should().HaveCountGreaterThan(1);
15 | result.Should().ContainSingle(x => x is TestValidator);
16 | }
17 |
18 | [Fact]
19 | public void Constructor_ScansValidators_GivenAutoDiscoveryEnabled()
20 | {
21 | var validators = new SpecificationValidator();
22 |
23 | var result = ValidatorsOf(validators);
24 |
25 | result.Should().HaveCountGreaterThan(1);
26 | result.Should().ContainSingle(x => x is TestValidator);
27 | }
28 |
29 | [UnsafeAccessor(UnsafeAccessorKind.Field, Name = "k__BackingField")]
30 | public static extern ref List ValidatorsOf(SpecificationValidator @this);
31 |
32 | public class TestValidator : IValidator
33 | {
34 | public bool IsValid(T entity, Specification specification)
35 | {
36 | return true;
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/tests/QuerySpecification.AutoDiscovery.Tests/TypeDiscoveryTests.cs:
--------------------------------------------------------------------------------
1 | [assembly: SpecAutoDiscovery]
2 |
3 | namespace Tests;
4 |
5 | public class TypeDiscoveryTests
6 | {
7 | [Fact]
8 | public void GetMemoryEvaluators_IncludesCustom()
9 | {
10 | var allEvaluators = TypeDiscovery.GetMemoryEvaluators();
11 | allEvaluators.Should().ContainSingle(x => x is TestMemoryEvaluator);
12 | }
13 |
14 | [Fact]
15 | public void GetEvaluators_IncludesCustom()
16 | {
17 | var allEvaluators = TypeDiscovery.GetEvaluators();
18 | allEvaluators.Should().ContainSingle(x => x is TestEvaluator);
19 | }
20 |
21 | [Fact]
22 | public void GetValidators_IncludesCustom()
23 | {
24 | var allValidators = TypeDiscovery.GetValidators();
25 | allValidators.Should().ContainSingle(x => x is TestValidator);
26 | }
27 |
28 | // Custom user evaluators and validators
29 | public class TestEvaluator : IEvaluator
30 | {
31 | public IQueryable Evaluate(IQueryable