├── .gitattributes ├── .gitignore ├── DynamicQuery.sln ├── LICENSE.md ├── PoweredSoft.DynamicQuery.AspNetCore.NewtonsoftJson ├── MvcBuilderExtensions.cs └── PoweredSoft.DynamicQuery.AspNetCore.NewtonsoftJson.csproj ├── PoweredSoft.DynamicQuery.AspNetCore ├── MvcBuilderExtensions.cs └── PoweredSoft.DynamicQuery.AspNetCore.csproj ├── PoweredSoft.DynamicQuery.Cli ├── PoweredSoft.DynamicQuery.Cli.csproj └── Program.cs ├── PoweredSoft.DynamicQuery.Core ├── AggregateType.cs ├── FilterType.cs ├── IAfterReadInterceptor.cs ├── IAggregate.cs ├── IAggregateInterceptor.cs ├── IBeforeQueryFilterInterceptor.cs ├── ICompositeFilter.cs ├── IFilter.cs ├── IFilterInterceptor.cs ├── IGroup.cs ├── IGroupingInterceptor.cs ├── IIncludeStrategyInterceptor.cs ├── IInterceptQueryExecutionOptions.cs ├── INoSortInterceptor.cs ├── IQueryConvertInterceptor.cs ├── IQueryCriteria.cs ├── IQueryExecutionOptions.cs ├── IQueryHandler.cs ├── IQueryInterceptor.cs ├── IQueryInterceptorProvider.cs ├── IQueryResult.cs ├── ISimpleFilter.cs ├── ISort.cs ├── ISortInterceptor.cs ├── PoweredSoft.DynamicQuery.Core.csproj └── QueryExecutionOptions.cs ├── PoweredSoft.DynamicQuery.NewtonsoftJson ├── DynamicQueryJsonConverter.cs ├── Extensions.cs └── PoweredSoft.DynamicQuery.NewtonsoftJson.csproj ├── PoweredSoft.DynamicQuery.Test ├── AggregateInterceptorTests.cs ├── AggregateTests.cs ├── AsyncTests.cs ├── BeforeFilterTests.cs ├── ConvertibleInterceptorTests.cs ├── CriteriaTests.cs ├── DeserializeTests.cs ├── FilterInterceptorTests.cs ├── FilterTests.cs ├── GroupInterceptorTests.cs ├── GroupTests.cs ├── IncludeStrategyTests.cs ├── Mock │ ├── Entities.cs │ ├── MockContext.cs │ ├── MockContextFactory.cs │ ├── TestSeeders.cs │ └── Ticket.cs ├── MockQueryExecutionOptionsInterceptor.cs ├── NoSortTests.cs ├── PoweredSoft.DynamicQuery.Test.csproj ├── QueryProviderTests.cs ├── SortInterceptorTests.cs └── SortTests.cs ├── PoweredSoft.DynamicQuery ├── Aggregate.cs ├── Extensions │ ├── FilterExtensions.cs │ ├── FilterTypeExtensions.cs │ └── GroupResultExtensions.cs ├── Filter.cs ├── Group.cs ├── PoweredSoft.DynamicQuery.csproj ├── QueryCriteria.cs ├── QueryHandler.cs ├── QueryHandlerAsync.cs ├── QueryHandlerBase.cs ├── QueryInterceptorEqualityComparer.cs ├── QueryResult.cs ├── ServiceCollectionExtensions.cs └── Sort.cs └── README.md /.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 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | project.fragment.lock.json 46 | artifacts/ 47 | 48 | *_i.c 49 | *_p.c 50 | *_i.h 51 | *.ilk 52 | *.meta 53 | *.obj 54 | *.pch 55 | *.pdb 56 | *.pgc 57 | *.pgd 58 | *.rsp 59 | *.sbr 60 | *.tlb 61 | *.tli 62 | *.tlh 63 | *.tmp 64 | *.tmp_proj 65 | *.log 66 | *.vspscc 67 | *.vssscc 68 | .builds 69 | *.pidb 70 | *.svclog 71 | *.scc 72 | 73 | # Chutzpah Test files 74 | _Chutzpah* 75 | 76 | # Visual C++ cache files 77 | ipch/ 78 | *.aps 79 | *.ncb 80 | *.opendb 81 | *.opensdf 82 | *.sdf 83 | *.cachefile 84 | *.VC.db 85 | *.VC.VC.opendb 86 | 87 | # Visual Studio profiler 88 | *.psess 89 | *.vsp 90 | *.vspx 91 | *.sap 92 | 93 | # TFS 2012 Local Workspace 94 | $tf/ 95 | 96 | # Guidance Automation Toolkit 97 | *.gpState 98 | 99 | # ReSharper is a .NET coding add-in 100 | _ReSharper*/ 101 | *.[Rr]e[Ss]harper 102 | *.DotSettings.user 103 | 104 | # JustCode is a .NET coding add-in 105 | .JustCode 106 | 107 | # TeamCity is a build add-in 108 | _TeamCity* 109 | 110 | # DotCover is a Code Coverage Tool 111 | *.dotCover 112 | 113 | # NCrunch 114 | _NCrunch_* 115 | .*crunch*.local.xml 116 | nCrunchTemp_* 117 | 118 | # MightyMoose 119 | *.mm.* 120 | AutoTest.Net/ 121 | 122 | # Web workbench (sass) 123 | .sass-cache/ 124 | 125 | # Installshield output folder 126 | [Ee]xpress/ 127 | 128 | # DocProject is a documentation generator add-in 129 | DocProject/buildhelp/ 130 | DocProject/Help/*.HxT 131 | DocProject/Help/*.HxC 132 | DocProject/Help/*.hhc 133 | DocProject/Help/*.hhk 134 | DocProject/Help/*.hhp 135 | DocProject/Help/Html2 136 | DocProject/Help/html 137 | 138 | # Click-Once directory 139 | publish/ 140 | 141 | # Publish Web Output 142 | *.[Pp]ublish.xml 143 | *.azurePubxml 144 | # TODO: Comment the next line if you want to checkin your web deploy settings 145 | # but database connection strings (with potential passwords) will be unencrypted 146 | #*.pubxml 147 | *.publishproj 148 | 149 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 150 | # checkin your Azure Web App publish settings, but sensitive information contained 151 | # in these scripts will be unencrypted 152 | PublishScripts/ 153 | 154 | # NuGet Packages 155 | *.nupkg 156 | # The packages folder can be ignored because of Package Restore 157 | **/packages/* 158 | # except build/, which is used as an MSBuild target. 159 | !**/packages/build/ 160 | # Uncomment if necessary however generally it will be regenerated when needed 161 | #!**/packages/repositories.config 162 | # NuGet v3's project.json files produces more ignoreable files 163 | *.nuget.props 164 | *.nuget.targets 165 | 166 | # Microsoft Azure Build Output 167 | csx/ 168 | *.build.csdef 169 | 170 | # Microsoft Azure Emulator 171 | ecf/ 172 | rcf/ 173 | 174 | # Windows Store app package directories and files 175 | AppPackages/ 176 | BundleArtifacts/ 177 | Package.StoreAssociation.xml 178 | _pkginfo.txt 179 | 180 | # Visual Studio cache files 181 | # files ending in .cache can be ignored 182 | *.[Cc]ache 183 | # but keep track of directories ending in .cache 184 | !*.[Cc]ache/ 185 | 186 | # Others 187 | ClientBin/ 188 | ~$* 189 | *~ 190 | *.dbmdl 191 | *.dbproj.schemaview 192 | *.jfm 193 | *.pfx 194 | *.publishsettings 195 | node_modules/ 196 | orleans.codegen.cs 197 | 198 | # Since there are multiple workflows, uncomment next line to ignore bower_components 199 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 200 | #bower_components/ 201 | 202 | # RIA/Silverlight projects 203 | Generated_Code/ 204 | 205 | # Backup & report files from converting an old project file 206 | # to a newer Visual Studio version. Backup files are not needed, 207 | # because we have git ;-) 208 | _UpgradeReport_Files/ 209 | Backup*/ 210 | UpgradeLog*.XML 211 | UpgradeLog*.htm 212 | 213 | # SQL Server files 214 | *.mdf 215 | *.ldf 216 | 217 | # Business Intelligence projects 218 | *.rdl.data 219 | *.bim.layout 220 | *.bim_*.settings 221 | 222 | # Microsoft Fakes 223 | FakesAssemblies/ 224 | 225 | # GhostDoc plugin setting file 226 | *.GhostDoc.xml 227 | 228 | # Node.js Tools for Visual Studio 229 | .ntvs_analysis.dat 230 | 231 | # Visual Studio 6 build log 232 | *.plg 233 | 234 | # Visual Studio 6 workspace options file 235 | *.opt 236 | 237 | # Visual Studio LightSwitch build output 238 | **/*.HTMLClient/GeneratedArtifacts 239 | **/*.DesktopClient/GeneratedArtifacts 240 | **/*.DesktopClient/ModelManifest.xml 241 | **/*.Server/GeneratedArtifacts 242 | **/*.Server/ModelManifest.xml 243 | _Pvt_Extensions 244 | 245 | # Paket dependency manager 246 | .paket/paket.exe 247 | paket-files/ 248 | 249 | # FAKE - F# Make 250 | .fake/ 251 | 252 | # JetBrains Rider 253 | .idea/ 254 | *.sln.iml 255 | 256 | # CodeRush 257 | .cr/ 258 | 259 | # Python Tools for Visual Studio (PTVS) 260 | __pycache__/ 261 | *.pyc -------------------------------------------------------------------------------- /DynamicQuery.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29326.143 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PoweredSoft.DynamicQuery.Core", "PoweredSoft.DynamicQuery.Core\PoweredSoft.DynamicQuery.Core.csproj", "{E614658D-6852-4405-B5BE-3695C3E96B13}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PoweredSoft.DynamicQuery", "PoweredSoft.DynamicQuery\PoweredSoft.DynamicQuery.csproj", "{A9F74387-6B09-423A-96BC-F8FF345193EE}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PoweredSoft.DynamicQuery.Cli", "PoweredSoft.DynamicQuery.Cli\PoweredSoft.DynamicQuery.Cli.csproj", "{7FC0F790-A8B9-4335-8D72-09797DEB0359}" 11 | EndProject 12 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{BD742C81-D26A-466C-AB9B-840996D59FA6}" 13 | ProjectSection(SolutionItems) = preProject 14 | LICENSE.md = LICENSE.md 15 | README.md = README.md 16 | EndProjectSection 17 | EndProject 18 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PoweredSoft.DynamicQuery.Test", "PoweredSoft.DynamicQuery.Test\PoweredSoft.DynamicQuery.Test.csproj", "{3EAD8217-8E10-4261-9055-50444905922C}" 19 | EndProject 20 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PoweredSoft.DynamicQuery.AspNetCore", "PoweredSoft.DynamicQuery.AspNetCore\PoweredSoft.DynamicQuery.AspNetCore.csproj", "{DF58BD18-AB47-4018-B1EA-D1118D93B408}" 21 | EndProject 22 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PoweredSoft.DynamicQuery.NewtonsoftJson", "PoweredSoft.DynamicQuery.NewtonsoftJson\PoweredSoft.DynamicQuery.NewtonsoftJson.csproj", "{201D7ACB-E11A-47EE-80F6-3F6BFB947DCA}" 23 | EndProject 24 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PoweredSoft.DynamicQuery.AspNetCore.NewtonsoftJson", "PoweredSoft.DynamicQuery.AspNetCore.NewtonsoftJson\PoweredSoft.DynamicQuery.AspNetCore.NewtonsoftJson.csproj", "{7BBEEE93-A3DB-443B-8254-E148AC9663EA}" 25 | EndProject 26 | Global 27 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 28 | Debug|Any CPU = Debug|Any CPU 29 | Release|Any CPU = Release|Any CPU 30 | EndGlobalSection 31 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 32 | {E614658D-6852-4405-B5BE-3695C3E96B13}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {E614658D-6852-4405-B5BE-3695C3E96B13}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {E614658D-6852-4405-B5BE-3695C3E96B13}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {E614658D-6852-4405-B5BE-3695C3E96B13}.Release|Any CPU.Build.0 = Release|Any CPU 36 | {A9F74387-6B09-423A-96BC-F8FF345193EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 37 | {A9F74387-6B09-423A-96BC-F8FF345193EE}.Debug|Any CPU.Build.0 = Debug|Any CPU 38 | {A9F74387-6B09-423A-96BC-F8FF345193EE}.Release|Any CPU.ActiveCfg = Release|Any CPU 39 | {A9F74387-6B09-423A-96BC-F8FF345193EE}.Release|Any CPU.Build.0 = Release|Any CPU 40 | {7FC0F790-A8B9-4335-8D72-09797DEB0359}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 41 | {7FC0F790-A8B9-4335-8D72-09797DEB0359}.Debug|Any CPU.Build.0 = Debug|Any CPU 42 | {7FC0F790-A8B9-4335-8D72-09797DEB0359}.Release|Any CPU.ActiveCfg = Release|Any CPU 43 | {7FC0F790-A8B9-4335-8D72-09797DEB0359}.Release|Any CPU.Build.0 = Release|Any CPU 44 | {3EAD8217-8E10-4261-9055-50444905922C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 45 | {3EAD8217-8E10-4261-9055-50444905922C}.Debug|Any CPU.Build.0 = Debug|Any CPU 46 | {3EAD8217-8E10-4261-9055-50444905922C}.Release|Any CPU.ActiveCfg = Release|Any CPU 47 | {3EAD8217-8E10-4261-9055-50444905922C}.Release|Any CPU.Build.0 = Release|Any CPU 48 | {DF58BD18-AB47-4018-B1EA-D1118D93B408}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 49 | {DF58BD18-AB47-4018-B1EA-D1118D93B408}.Debug|Any CPU.Build.0 = Debug|Any CPU 50 | {DF58BD18-AB47-4018-B1EA-D1118D93B408}.Release|Any CPU.ActiveCfg = Release|Any CPU 51 | {DF58BD18-AB47-4018-B1EA-D1118D93B408}.Release|Any CPU.Build.0 = Release|Any CPU 52 | {201D7ACB-E11A-47EE-80F6-3F6BFB947DCA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 53 | {201D7ACB-E11A-47EE-80F6-3F6BFB947DCA}.Debug|Any CPU.Build.0 = Debug|Any CPU 54 | {201D7ACB-E11A-47EE-80F6-3F6BFB947DCA}.Release|Any CPU.ActiveCfg = Release|Any CPU 55 | {201D7ACB-E11A-47EE-80F6-3F6BFB947DCA}.Release|Any CPU.Build.0 = Release|Any CPU 56 | {7BBEEE93-A3DB-443B-8254-E148AC9663EA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 57 | {7BBEEE93-A3DB-443B-8254-E148AC9663EA}.Debug|Any CPU.Build.0 = Debug|Any CPU 58 | {7BBEEE93-A3DB-443B-8254-E148AC9663EA}.Release|Any CPU.ActiveCfg = Release|Any CPU 59 | {7BBEEE93-A3DB-443B-8254-E148AC9663EA}.Release|Any CPU.Build.0 = Release|Any CPU 60 | EndGlobalSection 61 | GlobalSection(SolutionProperties) = preSolution 62 | HideSolutionNode = FALSE 63 | EndGlobalSection 64 | GlobalSection(ExtensibilityGlobals) = postSolution 65 | SolutionGuid = {3770CB3C-28E4-4C15-A537-4DA38CFBA4F3} 66 | EndGlobalSection 67 | EndGlobal 68 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Powered Softwares Inc. 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 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicQuery.AspNetCore.NewtonsoftJson/MvcBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using PoweredSoft.DynamicQuery.NewtonsoftJson; 3 | 4 | namespace PoweredSoft.DynamicQuery.AspNetCore.NewtonsoftJson 5 | { 6 | public static class MvcBuilderExtensions 7 | { 8 | public static IMvcBuilder AddPoweredSoftJsonNetDynamicQuery(this IMvcBuilder mvcBuilder, bool enableStringEnumConverter = true) 9 | { 10 | mvcBuilder.AddPoweredSoftDynamicQuery(); 11 | var serviceProvider = mvcBuilder.Services.BuildServiceProvider(); 12 | 13 | mvcBuilder.AddNewtonsoftJson(o => 14 | { 15 | o.SerializerSettings.AddPoweredSoftDynamicQueryNewtonsoftJson(serviceProvider, enableStringEnumConverter: enableStringEnumConverter); 16 | }); 17 | 18 | return mvcBuilder; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicQuery.AspNetCore.NewtonsoftJson/PoweredSoft.DynamicQuery.AspNetCore.NewtonsoftJson.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.1;net5.0 5 | Powered Softwares Inc. 6 | MIT 7 | https://github.com/PoweredSoft/DynamicQuery 8 | https://github.com/PoweredSoft/DynamicQuery 9 | github 10 | powered,soft,dynamic,criteria,query,builder,asp,net,core 11 | 3.0.0$(VersionSuffix) 12 | https://secure.gravatar.com/avatar/4e32f73820c16718909a06c2927f1f8b?s=512&amp;r=g&amp;d=retro 13 | This projects makes it easier to use dynamic query in a asp.net core mvc project. 14 | PoweredSoft.DynamicQuery.AspNetCore.NewtonsoftJson 15 | False 16 | Powered Soft 17 | David Lebee 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicQuery.AspNetCore/MvcBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using PoweredSoft.Data; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | 7 | namespace PoweredSoft.DynamicQuery.AspNetCore 8 | { 9 | public static class MvcBuilderExtensions 10 | { 11 | public static IMvcBuilder AddPoweredSoftDynamicQuery(this IMvcBuilder builder) 12 | { 13 | builder.Services.AddPoweredSoftDataServices(); 14 | builder.Services.AddPoweredSoftDynamicQuery(); 15 | return builder; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicQuery.AspNetCore/PoweredSoft.DynamicQuery.AspNetCore.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.1;net5.0 5 | Powered Softwares Inc. 6 | MIT 7 | https://github.com/PoweredSoft/DynamicQuery 8 | https://github.com/PoweredSoft/DynamicQuery 9 | github 10 | powered,soft,dynamic,criteria,query,builder,asp,net,core 11 | 3.0.0$(VersionSuffix) 12 | https://secure.gravatar.com/avatar/4e32f73820c16718909a06c2927f1f8b?s=512&amp;r=g&amp;d=retro 13 | PoweredSoft.DynamicQuery.AspNetCore 14 | This projects makes it easier to use dynamic query in a asp.net core mvc project. 15 | PoweredSoft.DynamicQuery.AspNetCore 16 | False 17 | Powered Soft 18 | David Lebee 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicQuery.Cli/PoweredSoft.DynamicQuery.Cli.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net5.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicQuery.Cli/Program.cs: -------------------------------------------------------------------------------- 1 | using PoweredSoft.DynamicQuery.Core; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using Newtonsoft.Json; 6 | using Newtonsoft.Json.Converters; 7 | 8 | namespace PoweredSoft.DynamicQuery.Cli 9 | { 10 | public class PersonQueryInterceptor : IQueryInterceptor 11 | , IAggregateInterceptor 12 | , IQueryConvertInterceptor 13 | //, IBeforeQueryAlteredInterceptor 14 | //, IFilterInterceptor 15 | { 16 | public IQueryable InterceptQueryBeforeAltered(IQueryCriteria criteria, IQueryable queryable) 17 | => queryable.Where(t => t.FirstName.StartsWith("Da")); 18 | 19 | public IFilter InterceptFilter(IFilter filter) 20 | { 21 | if (filter is SimpleFilter) 22 | { 23 | var simpleFilter = filter as ISimpleFilter; 24 | if (simpleFilter.Path == "FirstName" && simpleFilter.Value is string && ((string)simpleFilter.Value).Contains(",")) 25 | { 26 | var firstNames = ((string) simpleFilter.Value).Split(','); 27 | var filters = firstNames.Select(firstName => new SimpleFilter 28 | { 29 | Path = "FirstName", 30 | Type = FilterType.Equal, 31 | Value = firstName 32 | }).Cast().ToList(); 33 | 34 | return new CompositeFilter 35 | { 36 | Type = FilterType.Composite, 37 | Filters = filters, 38 | And = true 39 | }; 40 | } 41 | } 42 | 43 | return filter; 44 | } 45 | 46 | public IAggregate InterceptAggregate(IAggregate aggregate) 47 | { 48 | if (aggregate.Path == nameof(PersonModel.AgeStr)) 49 | return new Aggregate {Type = aggregate.Type, Path = nameof(Person.Age)}; 50 | return aggregate; 51 | } 52 | 53 | public object InterceptResultTo(Person entity) 54 | { 55 | var personModel = new PersonModel(); 56 | personModel.Id = entity.Id; 57 | personModel.FirstName = entity.FirstName; 58 | personModel.LastName = entity.LastName; 59 | personModel.Age = entity.Age; 60 | personModel.Sex = entity.Sex; 61 | return personModel; 62 | } 63 | } 64 | 65 | public class Person 66 | { 67 | public int Id { get; set; } 68 | public string FirstName { get; set; } 69 | public string LastName { get; set; } 70 | public int Age { get; set; } 71 | public string Sex { get; set; } 72 | } 73 | 74 | public class PersonModel 75 | { 76 | public int Id { get; set; } 77 | public string FirstName { get; set; } 78 | public string LastName { get; set; } 79 | public int Age { get; set; } 80 | public string Sex { get; set; } 81 | public string AgeStr => $"{Age} years old"; 82 | public string FullName => $"{FirstName} {LastName}"; 83 | } 84 | 85 | class Program 86 | { 87 | static void Main(string[] args) 88 | { 89 | Play1(); 90 | 91 | 92 | } 93 | 94 | private static void Play1() 95 | { 96 | var list = new List() 97 | { 98 | new Person{ Id = 1, FirstName = "David", LastName = "Lebee", Sex = "Male", Age = 29 }, 99 | new Person{ Id = 2, FirstName = "Michaela", LastName = "Lebee", Sex = "Female", Age = 29}, 100 | new Person{ Id = 3, FirstName = "Zohra", LastName = "Lebee", Sex = "Female", Age = 20}, 101 | new Person{ Id = 4, FirstName = "Eric", LastName = "Vickar", Sex = "Male", Age = 30}, 102 | new Person{ Id = 5, FirstName = "Susan", LastName = "Vickar", Sex = "Female", Age = 30}, 103 | }; 104 | 105 | var queryable = list.AsQueryable(); 106 | var criteria = new QueryCriteria(); 107 | criteria.Page = 1; 108 | criteria.PageSize = 10; 109 | 110 | criteria.Groups = new List() 111 | { 112 | new Group { Path = "LastName" }, 113 | new Group { Path = "Sexe" } 114 | }; 115 | 116 | criteria.Aggregates = new List() 117 | { 118 | new Aggregate { Type = AggregateType.Count }, 119 | new Aggregate { Path = "AgeStr", Type = AggregateType.Avg } 120 | };; 121 | 122 | var handler = new QueryHandler(Enumerable.Empty()); 123 | handler.AddInterceptor(new PersonQueryInterceptor()); 124 | var result = handler.Execute(queryable, criteria); 125 | 126 | var jsonSettings = new JsonSerializerSettings() 127 | { 128 | ReferenceLoopHandling = ReferenceLoopHandling.Ignore 129 | }; 130 | 131 | jsonSettings.Converters.Add(new StringEnumConverter { AllowIntegerValues = false }); 132 | 133 | Console.WriteLine("Request:\n"); 134 | Console.WriteLine(JsonConvert.SerializeObject(criteria, Formatting.Indented, jsonSettings)); 135 | Console.WriteLine(""); 136 | Console.WriteLine("Response:\n"); 137 | Console.WriteLine(JsonConvert.SerializeObject(result, Formatting.Indented, jsonSettings)); 138 | Console.ReadKey(); 139 | } 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicQuery.Core/AggregateType.cs: -------------------------------------------------------------------------------- 1 | namespace PoweredSoft.DynamicQuery.Core 2 | { 3 | public enum AggregateType 4 | { 5 | Count, 6 | Sum, 7 | Avg, 8 | LongCount, 9 | Min, 10 | Max, 11 | First, 12 | FirstOrDefault, 13 | Last, 14 | LastOrDefault 15 | } 16 | } -------------------------------------------------------------------------------- /PoweredSoft.DynamicQuery.Core/FilterType.cs: -------------------------------------------------------------------------------- 1 | namespace PoweredSoft.DynamicQuery.Core 2 | { 3 | public enum FilterType 4 | { 5 | Equal, 6 | Contains, 7 | StartsWith, 8 | EndsWith, 9 | Composite, 10 | NotEqual, 11 | GreaterThan, 12 | LessThanOrEqual, 13 | GreaterThanOrEqual, 14 | LessThan, 15 | In, 16 | NotIn 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicQuery.Core/IAfterReadInterceptor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | 6 | namespace PoweredSoft.DynamicQuery.Core 7 | { 8 | public interface IAfterReadEntityInterceptor : IQueryInterceptor 9 | { 10 | void AfterReadEntity(List entities); 11 | } 12 | 13 | public interface IAfterReadEntityInterceptorAsync : IQueryInterceptor 14 | { 15 | Task AfterReadEntityAsync(List entities, CancellationToken cancellationToken = default(CancellationToken)); 16 | } 17 | 18 | public interface IAfterReadInterceptor : IQueryInterceptor 19 | { 20 | void AfterRead(List> pairs); 21 | } 22 | 23 | public interface IAfterReadInterceptorAsync : IQueryInterceptor 24 | { 25 | Task AfterReadAsync(List> pairs, CancellationToken cancellationToken = default(CancellationToken)); 26 | } 27 | 28 | public interface IAfterReadInterceptor : IQueryInterceptor 29 | { 30 | void AfterRead(List> pairs); 31 | } 32 | 33 | public interface IAfterReadInterceptorAsync : IQueryInterceptor 34 | { 35 | Task AfterReadAsync(List> pairs, CancellationToken cancellationToken = default(CancellationToken)); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicQuery.Core/IAggregate.cs: -------------------------------------------------------------------------------- 1 | namespace PoweredSoft.DynamicQuery.Core 2 | { 3 | public interface IAggregate 4 | { 5 | string Path { get; set; } 6 | AggregateType Type { get; set; } 7 | } 8 | } -------------------------------------------------------------------------------- /PoweredSoft.DynamicQuery.Core/IAggregateInterceptor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace PoweredSoft.DynamicQuery.Core 6 | { 7 | public interface IAggregateInterceptor : IQueryInterceptor 8 | { 9 | IAggregate InterceptAggregate(IAggregate aggregate); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicQuery.Core/IBeforeQueryFilterInterceptor.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | 3 | namespace PoweredSoft.DynamicQuery.Core 4 | { 5 | public interface IBeforeQueryFilterInterceptor : IQueryInterceptor 6 | { 7 | IQueryable InterceptBeforeFiltering(IQueryCriteria criteria, IQueryable queryable); 8 | } 9 | 10 | public interface IBeforeQueryFilterInterceptor : IQueryInterceptor 11 | { 12 | IQueryable InterceptBeforeFiltering(IQueryCriteria criteria, IQueryable queryable); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicQuery.Core/ICompositeFilter.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace PoweredSoft.DynamicQuery.Core 4 | { 5 | public interface ICompositeFilter : IFilter 6 | { 7 | List Filters { get; set; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicQuery.Core/IFilter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace PoweredSoft.DynamicQuery.Core 4 | { 5 | public interface IFilter 6 | { 7 | bool? And { get; set; } 8 | FilterType Type { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicQuery.Core/IFilterInterceptor.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace PoweredSoft.DynamicQuery.Core 4 | { 5 | public interface IFilterInterceptor : IQueryInterceptor 6 | { 7 | IFilter InterceptFilter(IFilter filter); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicQuery.Core/IGroup.cs: -------------------------------------------------------------------------------- 1 | namespace PoweredSoft.DynamicQuery.Core 2 | { 3 | public interface IGroup 4 | { 5 | string Path { get; set; } 6 | bool? Ascending { get; set; } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicQuery.Core/IGroupingInterceptor.cs: -------------------------------------------------------------------------------- 1 | namespace PoweredSoft.DynamicQuery.Core 2 | { 3 | public interface IGroupInterceptor : IQueryInterceptor 4 | { 5 | IGroup InterceptGroup(IGroup group); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicQuery.Core/IIncludeStrategyInterceptor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace PoweredSoft.DynamicQuery.Core 7 | { 8 | public interface IIncludeStrategyInterceptor : IQueryInterceptor 9 | { 10 | IQueryable InterceptIncludeStrategy(IQueryCriteria criteria, IQueryable queryable); 11 | } 12 | 13 | public interface IIncludeStrategyInterceptor : IQueryInterceptor 14 | { 15 | IQueryable InterceptIncludeStrategy(IQueryCriteria criteria, IQueryable queryable); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicQuery.Core/IInterceptQueryExecutionOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace PoweredSoft.DynamicQuery.Core 7 | { 8 | public interface IQueryExecutionOptionsInterceptor : IQueryInterceptor 9 | { 10 | IQueryExecutionOptions InterceptQueryExecutionOptions(IQueryable queryable, IQueryExecutionOptions current); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicQuery.Core/INoSortInterceptor.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | 3 | namespace PoweredSoft.DynamicQuery.Core 4 | { 5 | public interface INoSortInterceptor : IQueryInterceptor 6 | { 7 | IQueryable InterceptNoSort(IQueryCriteria criteria, IQueryable queryable); 8 | } 9 | 10 | public interface INoSortInterceptor : IQueryInterceptor 11 | { 12 | IQueryable InterceptNoSort(IQueryCriteria criteria, IQueryable queryable); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicQuery.Core/IQueryConvertInterceptor.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | 5 | namespace PoweredSoft.DynamicQuery.Core 6 | { 7 | public interface IQueryConvertInterceptor : IQueryInterceptor 8 | { 9 | object InterceptResultTo(object entity); 10 | } 11 | 12 | public interface IQueryConvertInterceptor : IQueryInterceptor 13 | { 14 | object InterceptResultTo(T entity); 15 | } 16 | 17 | public interface IQueryConvertInterceptor : IQueryInterceptor 18 | { 19 | T2 InterceptResultTo(T entity); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicQuery.Core/IQueryCriteria.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace PoweredSoft.DynamicQuery.Core 6 | { 7 | public interface IQueryCriteria 8 | { 9 | int? Page { get; set; } 10 | int? PageSize { get; set; } 11 | List Sorts { get; set; } 12 | List Filters { get; set; } 13 | List Groups { get; set; } 14 | List Aggregates { get; set; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicQuery.Core/IQueryExecutionOptions.cs: -------------------------------------------------------------------------------- 1 | namespace PoweredSoft.DynamicQuery.Core 2 | { 3 | public interface IQueryExecutionOptions 4 | { 5 | bool GroupByInMemory { get; set; } 6 | bool GroupByInMemoryNullCheck { get; set; } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicQuery.Core/IQueryHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | 8 | namespace PoweredSoft.DynamicQuery.Core 9 | { 10 | public interface IInterceptableQueryHandler 11 | { 12 | void AddInterceptor(IQueryInterceptor interceptor); 13 | IReadOnlyList ResolveInterceptors(IQueryCriteria criteria, IQueryable queryable); 14 | } 15 | 16 | public interface IQueryHandler : IInterceptableQueryHandler 17 | { 18 | IQueryExecutionResult Execute(IQueryable queryable, IQueryCriteria criteria); 19 | IQueryExecutionResult Execute(IQueryable queryable, IQueryCriteria criteria); 20 | IQueryExecutionResult Execute(IQueryable queryable, IQueryCriteria criteria, IQueryExecutionOptions options); 21 | IQueryExecutionResult Execute(IQueryable queryable, IQueryCriteria criteria, IQueryExecutionOptions options); 22 | } 23 | 24 | public interface IQueryHandlerAsync : IInterceptableQueryHandler 25 | { 26 | Task> ExecuteAsync(IQueryable queryable, IQueryCriteria criteria, CancellationToken cancellationToken = default); 27 | Task> ExecuteAsync(IQueryable queryable, IQueryCriteria criteria, CancellationToken cancellationToken = default); 28 | Task> ExecuteAsync(IQueryable queryable, IQueryCriteria criteria, IQueryExecutionOptions options, CancellationToken cancellationToken = default); 29 | Task> ExecuteAsync(IQueryable queryable, IQueryCriteria criteria, IQueryExecutionOptions options, CancellationToken cancellationToken = default); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicQuery.Core/IQueryInterceptor.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | 3 | namespace PoweredSoft.DynamicQuery.Core 4 | { 5 | public interface IQueryInterceptor 6 | { 7 | 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicQuery.Core/IQueryInterceptorProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | 4 | namespace PoweredSoft.DynamicQuery.Core 5 | { 6 | public interface IQueryInterceptorProvider 7 | { 8 | IEnumerable GetInterceptors(IQueryCriteria queryCriteria, IQueryable queryable); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicQuery.Core/IQueryResult.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace PoweredSoft.DynamicQuery.Core 6 | { 7 | public interface IAggregateResult 8 | { 9 | string Path { get; set; } 10 | AggregateType Type { get; set; } 11 | object Value { get; set; } 12 | } 13 | 14 | public interface IQueryResult 15 | { 16 | List Aggregates { get; } 17 | List Data { get; } 18 | } 19 | 20 | public interface IGroupQueryResult : IQueryResult 21 | { 22 | string GroupPath { get; set; } 23 | object GroupValue { get; set; } 24 | bool HasSubGroups { get; } 25 | List> SubGroups { get; set; } 26 | } 27 | 28 | public interface IQueryExecutionResultPaging 29 | { 30 | long TotalRecords { get; set; } 31 | long? NumberOfPages { get; set; } 32 | } 33 | 34 | public interface IQueryExecutionResult : IQueryResult, IQueryExecutionResultPaging 35 | { 36 | 37 | } 38 | 39 | public interface IQueryExecutionGroupResult : IQueryExecutionResult 40 | { 41 | List> Groups { get; set; } 42 | } 43 | 44 | 45 | } 46 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicQuery.Core/ISimpleFilter.cs: -------------------------------------------------------------------------------- 1 | namespace PoweredSoft.DynamicQuery.Core 2 | { 3 | public interface ISimpleFilter : IFilter 4 | { 5 | string Path { get; set; } 6 | object Value { get; set; } 7 | bool? Not { get; set; } 8 | bool? CaseInsensitive { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicQuery.Core/ISort.cs: -------------------------------------------------------------------------------- 1 | namespace PoweredSoft.DynamicQuery.Core 2 | { 3 | public interface ISort 4 | { 5 | string Path { get; set; } 6 | bool? Ascending { get; set; } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicQuery.Core/ISortInterceptor.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace PoweredSoft.DynamicQuery.Core 4 | { 5 | public interface ISortInterceptor : IQueryInterceptor 6 | { 7 | IEnumerable InterceptSort(IEnumerable sort); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicQuery.Core/PoweredSoft.DynamicQuery.Core.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | Powered Softwares Inc. 6 | MIT 7 | https://github.com/PoweredSoft/DynamicQuery 8 | https://github.com/PoweredSoft/DynamicQuery.Core/ 9 | github 10 | powered,soft,dynamic,criteria,query,builder 11 | 3.0.0$(VersionSuffix) 12 | https://secure.gravatar.com/avatar/4e32f73820c16718909a06c2927f1f8b?s=512&amp;r=g&amp;d=retro 13 | PoweredSoft.DynamicQuery.Core 14 | core abstractions 15 | PoweredSoft.DynamicQuery.Core 16 | False 17 | Poweredsoft 18 | David Lebee 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicQuery.Core/QueryExecutionOptions.cs: -------------------------------------------------------------------------------- 1 | namespace PoweredSoft.DynamicQuery.Core 2 | { 3 | public class QueryExecutionOptions : IQueryExecutionOptions 4 | { 5 | public bool GroupByInMemory { get; set; } = false; 6 | public bool GroupByInMemoryNullCheck { get; set; } = false; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicQuery.NewtonsoftJson/DynamicQueryJsonConverter.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using Newtonsoft.Json.Linq; 3 | using PoweredSoft.DynamicQuery.Core; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Threading.Tasks; 8 | 9 | namespace PoweredSoft.DynamicQuery.NewtonsoftJson 10 | { 11 | public class DynamicQueryJsonConverter : JsonConverter 12 | { 13 | public override bool CanRead => true; 14 | public override bool CanWrite => false; 15 | 16 | private Type[] DynamicQueryTypes { get; } = new Type[] 17 | { 18 | typeof(IFilter), 19 | typeof(ISimpleFilter), 20 | typeof(ICompositeFilter), 21 | typeof(IAggregate), 22 | typeof(ISort), 23 | typeof(IGroup), 24 | typeof(IQueryCriteria), 25 | typeof(IQueryHandler) 26 | }; 27 | 28 | public IServiceProvider ServiceProvider { get; } 29 | 30 | public DynamicQueryJsonConverter(IServiceProvider serviceProvider) 31 | { 32 | ServiceProvider = serviceProvider; 33 | } 34 | 35 | public override bool CanConvert(Type objectType) => objectType.IsInterface && DynamicQueryTypes.Contains(objectType); 36 | 37 | public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) 38 | { 39 | if (reader.TokenType == JsonToken.Null) 40 | return (object)null; 41 | 42 | if (objectType == typeof(IFilter)) 43 | { 44 | var jo = JObject.Load(reader); 45 | 46 | bool isComposite = false; 47 | if (jo.ContainsKey("type")) 48 | { 49 | isComposite = jo.GetValue("type").Value() 50 | .Equals("composite", StringComparison.OrdinalIgnoreCase); 51 | } 52 | else if (jo.ContainsKey("Type")) 53 | { 54 | isComposite = jo.GetValue("Type").Value() 55 | .Equals("composite", StringComparison.OrdinalIgnoreCase); 56 | } 57 | else 58 | { 59 | throw new Exception("IFilter should have a type property.."); 60 | } 61 | 62 | var filterObj = ServiceProvider.GetService(isComposite ? typeof(ICompositeFilter) : typeof(ISimpleFilter)); 63 | var filterType = filterObj.GetType(); 64 | filterObj = jo.ToObject(filterType, serializer); 65 | return filterObj; 66 | } 67 | 68 | var obj = ServiceProvider.GetService(objectType); 69 | if (obj == null) 70 | throw new JsonSerializationException("No object created."); 71 | 72 | serializer.Populate(reader, obj); 73 | return obj; 74 | } 75 | 76 | public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) 77 | { 78 | throw new NotImplementedException(); 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicQuery.NewtonsoftJson/Extensions.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using Newtonsoft.Json.Converters; 3 | using System; 4 | 5 | namespace PoweredSoft.DynamicQuery.NewtonsoftJson 6 | { 7 | public static class JsonNetSerializationSettingsExtensions 8 | { 9 | public static JsonSerializerSettings AddPoweredSoftDynamicQueryNewtonsoftJson(this JsonSerializerSettings settings, IServiceProvider serviceProvider, bool enableStringEnumConverter = true) 10 | { 11 | if (enableStringEnumConverter) 12 | settings.Converters.Add(new StringEnumConverter()); 13 | 14 | settings.Converters.Add(new DynamicQueryJsonConverter(serviceProvider)); 15 | return settings; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicQuery.NewtonsoftJson/PoweredSoft.DynamicQuery.NewtonsoftJson.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | Powered Softwares Inc. 6 | MIT 7 | https://github.com/PoweredSoft/DynamicQuery 8 | https://github.com/PoweredSoft/DynamicQuery.Core/ 9 | github 10 | powered,soft,dynamic,criteria,query,builder 11 | 3.0.0$(VersionSuffix) 12 | https://secure.gravatar.com/avatar/4e32f73820c16718909a06c2927f1f8b?s=512&amp;r=g&amp;d=retro 13 | PoweredSoft.DynamicQuery.Newtonsoft.Json 14 | False 15 | Poweredsoft 16 | David Lebee 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicQuery.Test/AggregateInterceptorTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using PoweredSoft.DynamicQuery.Core; 3 | using PoweredSoft.DynamicQuery.Test.Mock; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Text; 8 | using Xunit; 9 | 10 | namespace PoweredSoft.DynamicQuery.Test 11 | { 12 | public class AggregateInterceptorTests 13 | { 14 | private class MockAggregateInterceptor : IAggregateInterceptor 15 | { 16 | public IAggregate InterceptAggregate(IAggregate aggregate) => new Aggregate 17 | { 18 | Path = "Price", 19 | Type = AggregateType.Avg 20 | }; 21 | } 22 | 23 | [Fact] 24 | public void Simple() 25 | { 26 | MockContextFactory.SeedAndTestContextFor("AggregatorInterceptorTests_Simple", TestSeeders.SimpleSeedScenario, ctx => 27 | { 28 | var expected = ctx.Items 29 | .GroupBy(t => true) 30 | .Select(t => new 31 | { 32 | PriceAtTheTime = t.Average(t2 => t2.Price) 33 | }).First(); 34 | 35 | var criteria = new QueryCriteria(); 36 | criteria.Aggregates.Add(new Aggregate 37 | { 38 | Type = AggregateType.Avg, 39 | Path = "ItemPrice" 40 | }); 41 | var queryHandler = new QueryHandler(Enumerable.Empty()); 42 | queryHandler.AddInterceptor(new MockAggregateInterceptor()); 43 | var result = queryHandler.Execute(ctx.Items, criteria); 44 | Assert.Equal(expected.PriceAtTheTime, result.Aggregates.First().Value); 45 | }); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicQuery.Test/AggregateTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using PoweredSoft.DynamicQuery.Core; 3 | using PoweredSoft.DynamicQuery.Test.Mock; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Text; 8 | using Xunit; 9 | 10 | namespace PoweredSoft.DynamicQuery.Test 11 | { 12 | 13 | public class AggregateTests 14 | { 15 | [Fact] 16 | public void WithoutGrouping() 17 | { 18 | MockContextFactory.SeedAndTestContextFor("AggregateTests_WithoutGrouping", TestSeeders.SimpleSeedScenario, ctx => 19 | { 20 | var shouldResult = ctx.OrderItems 21 | .GroupBy(t => true) 22 | .Select(t => new 23 | { 24 | Count = t.Count(), 25 | 26 | ItemQuantityMin = t.Min(t2 => t2.Quantity), 27 | ItemQuantityMax = t.Min(t2 => t2.Quantity), 28 | ItemQuantityAverage = t.Average(t2 => t2.Quantity), 29 | ItemQuantitySum = t.Sum(t2 => t2.Quantity), 30 | AvgOfPrice = t.Average(t2 => t2.PriceAtTheTime), 31 | /* not supported by ef core 3.0 32 | First = t.First(), 33 | FirstOrDefault = t.FirstOrDefault(), 34 | Last = t.Last(), 35 | LastOrDefault = t.LastOrDefault()*/ 36 | }) 37 | .First(); 38 | 39 | // query handler that is empty should be the same as running to list. 40 | var criteria = new QueryCriteria() 41 | { 42 | Aggregates = new List 43 | { 44 | new Aggregate { Type = AggregateType.Count }, 45 | new Aggregate { Type = AggregateType.Avg, Path = "Quantity" }, 46 | new Aggregate { Type = AggregateType.Sum, Path = "Quantity" }, 47 | new Aggregate { Type = AggregateType.Avg, Path = "PriceAtTheTime"}, 48 | new Aggregate { Type = AggregateType.Min, Path = "Quantity"}, 49 | new Aggregate { Type = AggregateType.Max, Path = "Quantity" }, 50 | /*not support by ef core 3.0 51 | new Aggregate { Type = AggregateType.First }, 52 | new Aggregate { Type = AggregateType.FirstOrDefault }, 53 | new Aggregate { Type = AggregateType.Last }, 54 | new Aggregate { Type = AggregateType.LastOrDefault }, 55 | */ 56 | } 57 | }; 58 | 59 | var queryHandler = new QueryHandler(Enumerable.Empty()); 60 | var result = queryHandler.Execute(ctx.OrderItems, criteria, new QueryExecutionOptions 61 | { 62 | GroupByInMemory = true 63 | }); 64 | 65 | var aggCount = result.Aggregates.First(t => t.Type == AggregateType.Count); 66 | 67 | /* 68 | var aggFirst = result.Aggregates.First(t => t.Type == AggregateType.First); 69 | var aggFirstOrDefault = result.Aggregates.First(t => t.Type == AggregateType.FirstOrDefault); 70 | var aggLast = result.Aggregates.First(t => t.Type == AggregateType.Last); 71 | var aggLastOrDefault = result.Aggregates.First(t => t.Type == AggregateType.LastOrDefault);*/ 72 | 73 | var aggItemQuantityMin = result.Aggregates.First(t => t.Type == AggregateType.Min && t.Path == "Quantity"); 74 | var aggItemQuantityMax = result.Aggregates.First(t => t.Type == AggregateType.Max && t.Path == "Quantity"); 75 | var aggItemQuantityAverage = result.Aggregates.First(t => t.Type == AggregateType.Avg && t.Path == "Quantity"); 76 | var aggItemQuantitySum = result.Aggregates.First(t => t.Type == AggregateType.Sum && t.Path == "Quantity"); 77 | var aggAvgOfPrice = result.Aggregates.First(t => t.Type == AggregateType.Avg && t.Path == "PriceAtTheTime"); 78 | 79 | Assert.Equal(shouldResult.Count, aggCount.Value); 80 | /* 81 | Assert.Equal(shouldResult.First?.Id, (aggFirst.Value as OrderItem)?.Id); 82 | Assert.Equal(shouldResult.FirstOrDefault?.Id, (aggFirstOrDefault.Value as OrderItem)?.Id); 83 | Assert.Equal(shouldResult.Last?.Id, (aggLast.Value as OrderItem)?.Id); 84 | Assert.Equal(shouldResult.LastOrDefault?.Id, (aggLastOrDefault.Value as OrderItem)?.Id);*/ 85 | 86 | Assert.Equal(shouldResult.ItemQuantityAverage, aggItemQuantityAverage.Value); 87 | Assert.Equal(shouldResult.ItemQuantitySum, aggItemQuantitySum.Value); 88 | Assert.Equal(shouldResult.AvgOfPrice, aggAvgOfPrice.Value); 89 | }); 90 | } 91 | 92 | [Fact] 93 | public void WithGrouping() 94 | { 95 | MockContextFactory.SeedAndTestContextFor("AggregateTests_WithGrouping", TestSeeders.SimpleSeedScenario, ctx => 96 | { 97 | var shouldResults = ctx.OrderItems 98 | .GroupBy(t => t.Order.CustomerId) 99 | .Select(t => new 100 | { 101 | GroupValue = t.Key, 102 | Count = t.Count(), 103 | ItemQuantityAverage = t.Average(t2 => t2.Quantity), 104 | ItemQuantitySum = t.Sum(t2 => t2.Quantity), 105 | AvgOfPrice = t.Average(t2 => t2.PriceAtTheTime) 106 | }) 107 | .ToList(); 108 | 109 | // query handler that is empty should be the same as running to list. 110 | var criteria = new QueryCriteria() 111 | { 112 | Groups = new List 113 | { 114 | new Group { Path = "Order.CustomerId" } 115 | }, 116 | Aggregates = new List 117 | { 118 | new Aggregate { Type = AggregateType.Count }, 119 | new Aggregate { Type = AggregateType.Avg, Path = "Quantity" }, 120 | new Aggregate { Type = AggregateType.Sum, Path = "Quantity" }, 121 | new Aggregate { Type = AggregateType.Avg, Path = "PriceAtTheTime"} 122 | } 123 | }; 124 | 125 | var queryHandler = new QueryHandler(Enumerable.Empty()); 126 | var queryable = ctx.OrderItems.Include(t => t.Order); 127 | var result = queryHandler.Execute(queryable, criteria, new QueryExecutionOptions 128 | { 129 | GroupByInMemory = true 130 | }); 131 | 132 | var groupedResult = result as IQueryExecutionGroupResult; 133 | Assert.NotNull(groupedResult); 134 | 135 | var groups = groupedResult.Groups; 136 | 137 | // validate group and aggregates of groups. 138 | Assert.Equal(groups.Count, shouldResults.Count); 139 | Assert.All(groups, g => 140 | { 141 | var index = groups.IndexOf(g); 142 | var shouldResult = shouldResults[index]; 143 | 144 | // validate the group value. 145 | Assert.Equal(g.GroupValue, shouldResult.GroupValue); 146 | 147 | // validate the group aggregates. 148 | var aggCount = g.Aggregates.First(t => t.Type == AggregateType.Count); 149 | var aggItemQuantityAverage = g.Aggregates.First(t => t.Type == AggregateType.Avg && t.Path == "Quantity"); 150 | var aggItemQuantitySum = g.Aggregates.First(t => t.Type == AggregateType.Sum && t.Path == "Quantity"); 151 | var aggAvgOfPrice = g.Aggregates.First(t => t.Type == AggregateType.Avg && t.Path == "PriceAtTheTime"); 152 | Assert.Equal(shouldResult.Count, aggCount.Value); 153 | Assert.Equal(shouldResult.ItemQuantityAverage, aggItemQuantityAverage.Value); 154 | Assert.Equal(shouldResult.ItemQuantitySum, aggItemQuantitySum.Value); 155 | Assert.Equal(shouldResult.AvgOfPrice, aggAvgOfPrice.Value); 156 | }); 157 | }); 158 | } 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicQuery.Test/AsyncTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using PoweredSoft.Data; 3 | using PoweredSoft.Data.EntityFrameworkCore; 4 | using PoweredSoft.DynamicQuery.Core; 5 | using PoweredSoft.DynamicQuery.Extensions; 6 | using PoweredSoft.DynamicQuery.Test.Mock; 7 | using System; 8 | using System.Collections.Generic; 9 | using System.Linq; 10 | using System.Text; 11 | using System.Threading.Tasks; 12 | using Xunit; 13 | using static PoweredSoft.DynamicQuery.Test.GroupInterceptorTests; 14 | 15 | namespace PoweredSoft.DynamicQuery.Test 16 | { 17 | public class AsyncTests 18 | { 19 | [Fact] 20 | public void TestEmptyCriteria() 21 | { 22 | MockContextFactory.SeedAndTestContextFor("AsyncTests_TestEmptyCriteria", TestSeeders.SimpleSeedScenario, async ctx => 23 | { 24 | var resultShouldMatch = ctx.Items.ToList(); 25 | var queryable = ctx.Items.AsQueryable(); 26 | 27 | // query handler that is empty should be the same as running to list. 28 | var aqf = new AsyncQueryableService(new[] { new AsyncQueryableHandlerService() }); 29 | var criteria = new QueryCriteria(); 30 | var queryHandler = new QueryHandlerAsync(aqf, Enumerable.Empty()); 31 | var result = await queryHandler.ExecuteAsync(queryable, criteria); 32 | Assert.Equal(resultShouldMatch, result.Data); 33 | }); 34 | } 35 | 36 | [Fact] 37 | public void WithGrouping() 38 | { 39 | MockContextFactory.SeedAndTestContextFor("AsyncTests_WithGrouping", TestSeeders.SimpleSeedScenario, async ctx => 40 | { 41 | var shouldResults = ctx.OrderItems 42 | .GroupBy(t => t.Order.CustomerId) 43 | .Select(t => new 44 | { 45 | GroupValue = t.Key, 46 | Count = t.Count(), 47 | ItemQuantityAverage = t.Average(t2 => t2.Quantity), 48 | ItemQuantitySum = t.Sum(t2 => t2.Quantity), 49 | AvgOfPrice = t.Average(t2 => t2.PriceAtTheTime) 50 | }) 51 | .ToList(); 52 | 53 | // query handler that is empty should be the same as running to list. 54 | var criteria = new QueryCriteria() 55 | { 56 | Groups = new List 57 | { 58 | new Group { Path = "Order.CustomerId" } 59 | }, 60 | Aggregates = new List 61 | { 62 | new Aggregate { Type = AggregateType.Count }, 63 | new Aggregate { Type = AggregateType.Avg, Path = "Quantity" }, 64 | new Aggregate { Type = AggregateType.Sum, Path = "Quantity" }, 65 | new Aggregate { Type = AggregateType.Avg, Path = "PriceAtTheTime"} 66 | } 67 | }; 68 | var asyncService = new AsyncQueryableService(new[] { new AsyncQueryableHandlerService() }); 69 | var queryHandler = new QueryHandlerAsync(asyncService, Enumerable.Empty()); 70 | var result = await queryHandler.ExecuteAsync(ctx.OrderItems.Include(t => t.Order.Customer), criteria, new QueryExecutionOptions 71 | { 72 | GroupByInMemory = true 73 | }); 74 | 75 | var groups = result.GroupedResult().Groups; 76 | 77 | // validate group and aggregates of groups. 78 | Assert.Equal(groups.Count, shouldResults.Count); 79 | Assert.All(groups, g => 80 | { 81 | var index = groups.IndexOf(g); 82 | var shouldResult = shouldResults[index]; 83 | 84 | // validate the group value. 85 | Assert.Equal(g.GroupValue, shouldResult.GroupValue); 86 | 87 | // validate the group aggregates. 88 | var aggCount = g.Aggregates.First(t => t.Type == AggregateType.Count); 89 | var aggItemQuantityAverage = g.Aggregates.First(t => t.Type == AggregateType.Avg && t.Path == "Quantity"); 90 | var aggItemQuantitySum = g.Aggregates.First(t => t.Type == AggregateType.Sum && t.Path == "Quantity"); 91 | var aggAvgOfPrice = g.Aggregates.First(t => t.Type == AggregateType.Avg && t.Path == "PriceAtTheTime"); 92 | Assert.Equal(shouldResult.Count, aggCount.Value); 93 | Assert.Equal(shouldResult.ItemQuantityAverage, aggItemQuantityAverage.Value); 94 | Assert.Equal(shouldResult.ItemQuantitySum, aggItemQuantitySum.Value); 95 | Assert.Equal(shouldResult.AvgOfPrice, aggAvgOfPrice.Value); 96 | }); 97 | }); 98 | } 99 | 100 | [Fact] 101 | public void SimpleFilter() 102 | { 103 | MockContextFactory.SeedAndTestContextFor("AsyncTests_SimpleFilter", TestSeeders.SimpleSeedScenario, async ctx => 104 | { 105 | var resultShouldMatch = ctx.Items.Where(t => t.Name.EndsWith("Cables")).ToList(); 106 | 107 | var criteria = new QueryCriteria() 108 | { 109 | Filters = new List 110 | { 111 | new SimpleFilter 112 | { 113 | Path = "Name", 114 | Type = FilterType.EndsWith, 115 | Value = "Cables" 116 | } 117 | } 118 | }; 119 | 120 | var asyncService = new AsyncQueryableService(new[] { new AsyncQueryableHandlerService() }); 121 | var queryHandler = new QueryHandlerAsync(asyncService, Enumerable.Empty()); 122 | var result = await queryHandler.ExecuteAsync(ctx.Items, criteria); 123 | Assert.Equal(resultShouldMatch, result.Data); 124 | }); 125 | } 126 | 127 | [Fact] 128 | public void SimpleFilterWithNot() 129 | { 130 | MockContextFactory.SeedAndTestContextFor("AsyncTests_SimpleFilter2", TestSeeders.SimpleSeedScenario, async ctx => 131 | { 132 | var resultShouldMatch = ctx.Items.Where(t => !t.Name.EndsWith("Cables")).ToList(); 133 | 134 | var criteria = new QueryCriteria() 135 | { 136 | Filters = new List 137 | { 138 | new SimpleFilter 139 | { 140 | Path = "Name", 141 | Type = FilterType.EndsWith, 142 | Value = "Cables", 143 | Not = true 144 | } 145 | } 146 | }; 147 | 148 | var asyncService = new AsyncQueryableService(new[] { new AsyncQueryableHandlerService() }); 149 | var queryHandler = new QueryHandlerAsync(asyncService, Enumerable.Empty()); 150 | var result = await queryHandler.ExecuteAsync(ctx.Items, criteria); 151 | Assert.Equal(resultShouldMatch, result.Data); 152 | }); 153 | } 154 | 155 | [Fact] 156 | public void TestPaging() 157 | { 158 | MockContextFactory.SeedAndTestContextFor("AsyncTests_TestPagging", TestSeeders.SimpleSeedScenario, async ctx => 159 | { 160 | var resultShouldMatch = ctx.OrderItems.OrderBy(t => t.Id).Skip(5).Take(5).ToList(); 161 | 162 | // query handler that is empty should be the same as running to list. 163 | var criteria = new QueryCriteria(); 164 | criteria.Sorts.Add(new Sort("Id")); 165 | criteria.Page = 2; 166 | criteria.PageSize = 5; 167 | 168 | var asyncService = new AsyncQueryableService(new[] { new AsyncQueryableHandlerService() }); 169 | var queryHandler = new QueryHandlerAsync(asyncService, Enumerable.Empty()); 170 | var result = await queryHandler.ExecuteAsync(ctx.OrderItems, criteria); 171 | Assert.Equal(resultShouldMatch, result.Data); 172 | }); 173 | } 174 | 175 | [Fact] 176 | public void WithGroupingInterceptorOptions() 177 | { 178 | MockContextFactory.SeedAndTestContextFor("AsyncTests_WithGroupingInterceptorOptions", TestSeeders.SimpleSeedScenario, async ctx => 179 | { 180 | var shouldResults = ctx.OrderItems 181 | .GroupBy(t => t.Order.CustomerId) 182 | .Select(t => new 183 | { 184 | GroupValue = t.Key, 185 | Count = t.Count(), 186 | ItemQuantityAverage = t.Average(t2 => t2.Quantity), 187 | ItemQuantitySum = t.Sum(t2 => t2.Quantity), 188 | AvgOfPrice = t.Average(t2 => t2.PriceAtTheTime) 189 | }) 190 | .ToList(); 191 | 192 | // query handler that is empty should be the same as running to list. 193 | var criteria = new QueryCriteria() 194 | { 195 | Groups = new List 196 | { 197 | new Group { Path = "Order.CustomerId" } 198 | }, 199 | Aggregates = new List 200 | { 201 | new Aggregate { Type = AggregateType.Count }, 202 | new Aggregate { Type = AggregateType.Avg, Path = "Quantity" }, 203 | new Aggregate { Type = AggregateType.Sum, Path = "Quantity" }, 204 | new Aggregate { Type = AggregateType.Avg, Path = "PriceAtTheTime"} 205 | } 206 | }; 207 | var asyncService = new AsyncQueryableService(new[] { new AsyncQueryableHandlerService() }); 208 | var queryHandler = new QueryHandlerAsync(asyncService, Enumerable.Empty()); 209 | queryHandler.AddInterceptor(new MockQueryExecutionOptionsInterceptor()); 210 | var result = await queryHandler.ExecuteAsync(ctx.OrderItems.Include(t => t.Order.Customer), criteria); 211 | 212 | var groups = result.GroupedResult().Groups; 213 | 214 | // validate group and aggregates of groups. 215 | Assert.Equal(groups.Count, shouldResults.Count); 216 | Assert.All(groups, g => 217 | { 218 | var index = groups.IndexOf(g); 219 | var shouldResult = shouldResults[index]; 220 | 221 | // validate the group value. 222 | Assert.Equal(g.GroupValue, shouldResult.GroupValue); 223 | 224 | // validate the group aggregates. 225 | var aggCount = g.Aggregates.First(t => t.Type == AggregateType.Count); 226 | var aggItemQuantityAverage = g.Aggregates.First(t => t.Type == AggregateType.Avg && t.Path == "Quantity"); 227 | var aggItemQuantitySum = g.Aggregates.First(t => t.Type == AggregateType.Sum && t.Path == "Quantity"); 228 | var aggAvgOfPrice = g.Aggregates.First(t => t.Type == AggregateType.Avg && t.Path == "PriceAtTheTime"); 229 | Assert.Equal(shouldResult.Count, aggCount.Value); 230 | Assert.Equal(shouldResult.ItemQuantityAverage, aggItemQuantityAverage.Value); 231 | Assert.Equal(shouldResult.ItemQuantitySum, aggItemQuantitySum.Value); 232 | Assert.Equal(shouldResult.AvgOfPrice, aggAvgOfPrice.Value); 233 | }); 234 | }); 235 | } 236 | } 237 | 238 | } 239 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicQuery.Test/BeforeFilterTests.cs: -------------------------------------------------------------------------------- 1 | using PoweredSoft.DynamicQuery.Core; 2 | using PoweredSoft.DynamicQuery.Test.Mock; 3 | using PoweredSoft.DynamicLinq; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Text; 8 | using Xunit; 9 | 10 | namespace PoweredSoft.DynamicQuery.Test 11 | { 12 | public class BeforeFilterTests 13 | { 14 | private class MockBeforeQueryFilterInterceptor : IBeforeQueryFilterInterceptor 15 | { 16 | public IQueryable InterceptBeforeFiltering(IQueryCriteria criteria, IQueryable queryable) 17 | { 18 | return queryable.Where(t => t.Equal("Customer.FirstName", "David")); 19 | } 20 | } 21 | 22 | private class MockBeforeQueryFilterGenericInterceptor : 23 | IBeforeQueryFilterInterceptor, 24 | IBeforeQueryFilterInterceptor 25 | { 26 | public IQueryable InterceptBeforeFiltering(IQueryCriteria criteria, IQueryable queryable) 27 | { 28 | return queryable.Where(t => t.Customer.FirstName == "David"); 29 | } 30 | 31 | public IQueryable InterceptBeforeFiltering(IQueryCriteria criteria, IQueryable queryable) 32 | { 33 | // leave throw it validates the test, if it gets in here it shoulnd't 34 | throw new NotImplementedException(); 35 | } 36 | } 37 | 38 | [Fact] 39 | public void NonGeneric() 40 | { 41 | MockContextFactory.SeedAndTestContextFor("BeforeFilterTests_NonGeneric", TestSeeders.SimpleSeedScenario, ctx => 42 | { 43 | var criteria = new QueryCriteria(); 44 | var interceptor = new MockBeforeQueryFilterInterceptor(); 45 | 46 | // queryable of orders. 47 | var queryable = ctx.Orders.AsQueryable(); 48 | 49 | // pass into the interceptor. 50 | queryable = (IQueryable)interceptor.InterceptBeforeFiltering(criteria, queryable); 51 | 52 | // query handler should pass by the same interceptor. 53 | var queryHandler = new QueryHandler(Enumerable.Empty()); 54 | queryHandler.AddInterceptor(interceptor); 55 | var result = queryHandler.Execute(ctx.Orders, criteria); 56 | 57 | // compare results. 58 | var expected = queryable.ToList(); 59 | Assert.Equal(expected, result.Data); 60 | }); 61 | } 62 | 63 | [Fact] 64 | public void Generic() 65 | { 66 | MockContextFactory.SeedAndTestContextFor("BeforeFilterTests_Generic", TestSeeders.SimpleSeedScenario, ctx => 67 | { 68 | var criteria = new QueryCriteria(); 69 | var interceptor = new MockBeforeQueryFilterGenericInterceptor(); 70 | 71 | // queryable of orders. 72 | var queryable = ctx.Orders.AsQueryable(); 73 | 74 | // pass into the interceptor. 75 | queryable = interceptor.InterceptBeforeFiltering(criteria, queryable); 76 | 77 | // query handler should pass by the same interceptor. 78 | var queryHandler = new QueryHandler(Enumerable.Empty()); 79 | queryHandler.AddInterceptor(interceptor); 80 | var result = queryHandler.Execute(ctx.Orders, criteria); 81 | 82 | // compare results. 83 | var expected = queryable.ToList(); 84 | Assert.Equal(expected, result.Data); 85 | }); 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicQuery.Test/ConvertibleInterceptorTests.cs: -------------------------------------------------------------------------------- 1 | using PoweredSoft.DynamicQuery.Core; 2 | using PoweredSoft.DynamicQuery.Test.Mock; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using Xunit; 8 | 9 | namespace PoweredSoft.DynamicQuery.Test 10 | { 11 | public class QueryConvertInterceptorTests 12 | { 13 | private class CustomerModel 14 | { 15 | public long Id { get; set; } 16 | public string FirstName { get; set; } 17 | public string LastName { get; set; } 18 | public string FullName => $"{FirstName} {LastName}"; 19 | } 20 | 21 | private class MockQueryConvertInterceptor : IQueryConvertInterceptor 22 | { 23 | public object InterceptResultTo(object entity) 24 | { 25 | var customer = entity as Customer; 26 | var personModel = new CustomerModel 27 | { 28 | Id = customer.Id, 29 | FirstName = customer.FirstName, 30 | LastName = customer.LastName 31 | }; 32 | return personModel; 33 | } 34 | } 35 | 36 | private class MockQueryConvertGenericInterceptor : 37 | IQueryConvertInterceptor, 38 | IQueryConvertInterceptor 39 | { 40 | public object InterceptResultTo(Customer entity) 41 | { 42 | var customer = entity; 43 | var personModel = new CustomerModel 44 | { 45 | Id = customer.Id, 46 | FirstName = customer.FirstName, 47 | LastName = customer.LastName 48 | }; 49 | return personModel; 50 | } 51 | 52 | public object InterceptResultTo(Order entity) 53 | { 54 | // leave the throw, its on purpose to match the type testing. 55 | throw new NotImplementedException(); 56 | } 57 | } 58 | 59 | private class MockQueryConvertGenericInterceptor2 : 60 | IQueryConvertInterceptor 61 | { 62 | public CustomerModel InterceptResultTo(Customer entity) 63 | { 64 | var customer = entity; 65 | var personModel = new CustomerModel 66 | { 67 | Id = customer.Id, 68 | FirstName = customer.FirstName, 69 | LastName = customer.LastName 70 | }; 71 | return personModel; 72 | } 73 | } 74 | 75 | [Fact] 76 | public void NonGeneric() 77 | { 78 | MockContextFactory.SeedAndTestContextFor("QueryConvertInterceptorTests_NonGeneric", TestSeeders.SimpleSeedScenario, ctx => 79 | { 80 | var criteria = new QueryCriteria(); 81 | var queryHandler = new QueryHandler(Enumerable.Empty()); 82 | queryHandler.AddInterceptor(new MockQueryConvertInterceptor()); 83 | var result = queryHandler.Execute(ctx.Customers, criteria); 84 | Assert.All(result.Data, t => Assert.IsType(t)); 85 | }); 86 | } 87 | 88 | [Fact] 89 | public void Generic() 90 | { 91 | MockContextFactory.SeedAndTestContextFor("ConvertibleIntereceptorTests_Generic", TestSeeders.SimpleSeedScenario, ctx => 92 | { 93 | var criteria = new QueryCriteria(); 94 | var queryHandler = new QueryHandler(Enumerable.Empty()); 95 | queryHandler.AddInterceptor(new MockQueryConvertGenericInterceptor()); 96 | var result = queryHandler.Execute(ctx.Customers, criteria); 97 | Assert.All(result.Data, t => Assert.IsType(t)); 98 | }); 99 | } 100 | 101 | [Fact] 102 | public void Generic2() 103 | { 104 | MockContextFactory.SeedAndTestContextFor("ConvertibleIntereceptorTests_Generic2", TestSeeders.SimpleSeedScenario, ctx => 105 | { 106 | var criteria = new QueryCriteria(); 107 | var queryHandler = new QueryHandler(Enumerable.Empty()); 108 | queryHandler.AddInterceptor(new MockQueryConvertGenericInterceptor2()); 109 | var result = queryHandler.Execute(ctx.Customers, criteria); 110 | Assert.All(result.Data, t => Assert.IsType(t)); 111 | }); 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicQuery.Test/CriteriaTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Microsoft.EntityFrameworkCore; 5 | using PoweredSoft.DynamicQuery.Core; 6 | using PoweredSoft.DynamicQuery.Test.Mock; 7 | using Xunit; 8 | 9 | namespace PoweredSoft.DynamicQuery.Test 10 | { 11 | public class CriteriaTests 12 | { 13 | [Fact] 14 | public void TestEmptyCriteria() 15 | { 16 | MockContextFactory.SeedAndTestContextFor("CriteriaTests_TestEmptyCriteria", TestSeeders.SimpleSeedScenario, ctx => 17 | { 18 | var resultShouldMatch = ctx.Items.ToList(); 19 | var queryable = ctx.Items.AsQueryable(); 20 | 21 | // query handler that is empty should be the same as running to list. 22 | var criteria = new QueryCriteria(); 23 | var queryHandler = new QueryHandler(Enumerable.Empty()); 24 | var result = queryHandler.Execute(queryable, criteria); 25 | Assert.Equal(resultShouldMatch, result.Data); 26 | }); 27 | } 28 | 29 | [Fact] 30 | public void TestPaging() 31 | { 32 | MockContextFactory.SeedAndTestContextFor("CriteriaTests_TestPagging", TestSeeders.SimpleSeedScenario, ctx => 33 | { 34 | var resultShouldMatch = ctx.OrderItems.OrderBy(t => t.Id).Skip(5).Take(5).ToList(); 35 | 36 | // query handler that is empty should be the same as running to list. 37 | var criteria = new QueryCriteria(); 38 | criteria.Sorts.Add(new Sort("Id")); 39 | criteria.Page = 2; 40 | criteria.PageSize = 5; 41 | 42 | var queryHandler = new QueryHandler(Enumerable.Empty()); 43 | var result = queryHandler.Execute(ctx.OrderItems, criteria); 44 | Assert.Equal(resultShouldMatch, result.Data); 45 | }); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicQuery.Test/DeserializeTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using Newtonsoft.Json; 3 | using Newtonsoft.Json.Converters; 4 | using PoweredSoft.DynamicQuery.Core; 5 | using PoweredSoft.DynamicQuery.NewtonsoftJson; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Text; 9 | using Xunit; 10 | 11 | namespace PoweredSoft.DynamicQuery.Test 12 | { 13 | public class SerializationTests 14 | { 15 | [Fact] 16 | public void QueryCriteria() 17 | { 18 | var json = @"{""page"":1,""pageSize"":20,""filters"":[{""type"":""composite"",""filters"":[{""path"":""title"",""value"":""Qui"",""type"":""StartsWith"",""and"":false}]}]}"; 19 | 20 | var serviceCollection = new ServiceCollection(); 21 | serviceCollection.AddPoweredSoftDynamicQuery(); 22 | var serviceProvider = serviceCollection.BuildServiceProvider(); 23 | 24 | var settings = new JsonSerializerSettings(); 25 | 26 | settings.Converters.Add(new StringEnumConverter()); 27 | settings.Converters.Add(new DynamicQueryJsonConverter(serviceProvider)); 28 | 29 | var data = JsonConvert.DeserializeObject(json, settings); 30 | Assert.NotNull(data); 31 | } 32 | 33 | 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicQuery.Test/FilterInterceptorTests.cs: -------------------------------------------------------------------------------- 1 | using PoweredSoft.DynamicQuery.Core; 2 | using PoweredSoft.DynamicQuery.Extensions; 3 | using PoweredSoft.DynamicQuery.Test.Mock; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Text; 8 | using Xunit; 9 | 10 | namespace PoweredSoft.DynamicQuery.Test 11 | { 12 | public class FilterInterceptorTests 13 | { 14 | private class MockFilterInterceptorA : IFilterInterceptor 15 | { 16 | public IFilter InterceptFilter(IFilter filter) 17 | { 18 | if (filter is ISimpleFilter && ((ISimpleFilter)filter).Path == "CustomerFirstName") 19 | return new SimpleFilter { Path = "Customer.FirstName", Type = FilterType.Contains, Value = "David" }; 20 | 21 | return filter; 22 | } 23 | } 24 | 25 | private class MockFilterInterceptorAWithExtension : IFilterInterceptor 26 | { 27 | public IFilter InterceptFilter(IFilter filter) 28 | { 29 | if (filter.IsSimpleFilterOn("CustomerFirstName")) 30 | return filter.ReplaceByOn(t => t.Customer.FirstName); 31 | else if (filter.IsSimpleFilterOn("CustomerFullName")) 32 | return filter.ReplaceByCompositeOn(t => t.Customer.FirstName, t => t.Customer.LastName); 33 | 34 | return filter; 35 | } 36 | } 37 | 38 | private class MockFilterInterceptorB : IFilterInterceptor 39 | { 40 | public IFilter InterceptFilter(IFilter filter) 41 | { 42 | return new SimpleFilter { Path = "Customer.LastName", Type = FilterType.Contains, Value = "Norris" }; 43 | } 44 | } 45 | 46 | [Fact] 47 | public void Simple() 48 | { 49 | MockContextFactory.SeedAndTestContextFor("FilterInterceptorTests_Simple", TestSeeders.SimpleSeedScenario, ctx => 50 | { 51 | var queryable = ctx.Orders.AsQueryable(); 52 | 53 | var criteria = new QueryCriteria() 54 | { 55 | Filters = new List 56 | { 57 | new SimpleFilter { Path = "CustomerFirstName", Value = "David", Type = FilterType.Contains } 58 | } 59 | }; 60 | 61 | var query = new QueryHandler(Enumerable.Empty()); 62 | query.AddInterceptor(new MockFilterInterceptorA()); 63 | var result = query.Execute(queryable, criteria); 64 | 65 | var actual = result.Data; 66 | var expected = queryable.Where(t => t.Customer.FirstName == "David").ToList(); 67 | Assert.Equal(expected, actual); 68 | }); 69 | } 70 | 71 | [Fact] 72 | public void SimpleWithExtensions() 73 | { 74 | MockContextFactory.SeedAndTestContextFor("FilterInterceptorTests_SimpleWithExtensions", TestSeeders.SimpleSeedScenario, ctx => 75 | { 76 | var queryable = ctx.Orders.AsQueryable(); 77 | 78 | var criteria = new QueryCriteria() 79 | { 80 | Filters = new List 81 | { 82 | new SimpleFilter { Path = "CustomerFirstName", Value = "David", Type = FilterType.Contains } 83 | } 84 | }; 85 | 86 | var query = new QueryHandler(Enumerable.Empty()); 87 | query.AddInterceptor(new MockFilterInterceptorAWithExtension()); 88 | var result = query.Execute(queryable, criteria); 89 | 90 | var actual = result.Data; 91 | var expected = queryable.Where(t => t.Customer.FirstName == "David").ToList(); 92 | Assert.Equal(expected, actual); 93 | }); 94 | } 95 | 96 | [Fact] 97 | public void SimpleWithExtensions2() 98 | { 99 | MockContextFactory.SeedAndTestContextFor("FilterInterceptorTests_SimpleWithExtensions2", TestSeeders.SimpleSeedScenario, ctx => 100 | { 101 | var queryable = ctx.Orders.AsQueryable(); 102 | 103 | var criteria = new QueryCriteria() 104 | { 105 | Filters = new List 106 | { 107 | new SimpleFilter { Path = "CustomerFullName", Value = "Da", Type = FilterType.Contains } 108 | } 109 | }; 110 | 111 | var query = new QueryHandler(Enumerable.Empty()); 112 | query.AddInterceptor(new MockFilterInterceptorAWithExtension()); 113 | var result = query.Execute(queryable, criteria); 114 | 115 | var actual = result.Data; 116 | var expected = queryable.Where(t => t.Customer.FirstName.Contains("Da") || t.Customer.LastName.Contains("Da")).ToList(); 117 | Assert.Equal(expected, actual); 118 | }); 119 | } 120 | 121 | [Fact] 122 | public void Multi() 123 | { 124 | MockContextFactory.SeedAndTestContextFor("FilterInterceptorTests_Multi", TestSeeders.SimpleSeedScenario, ctx => 125 | { 126 | var queryable = ctx.Orders.AsQueryable(); 127 | 128 | var criteria = new QueryCriteria() 129 | { 130 | Filters = new List 131 | { 132 | new SimpleFilter { Path = "CustomerFirstName", Value = "David", Type = FilterType.Contains } 133 | } 134 | }; 135 | 136 | var query = new QueryHandler(Enumerable.Empty()); 137 | query.AddInterceptor(new MockFilterInterceptorA()); 138 | query.AddInterceptor(new MockFilterInterceptorB()); 139 | var result = query.Execute(queryable, criteria); 140 | 141 | var actual = result.Data; 142 | var expected = queryable.Where(t => t.Customer.LastName == "Norris").ToList(); 143 | Assert.Equal(expected, actual); 144 | }); 145 | } 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicQuery.Test/FilterTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using PoweredSoft.DynamicQuery.Core; 6 | using PoweredSoft.DynamicQuery.Test.Mock; 7 | using Xunit; 8 | using Xunit.Sdk; 9 | 10 | namespace PoweredSoft.DynamicQuery.Test 11 | { 12 | public class FilterTests 13 | { 14 | private class MockIsChuckFilter : ISimpleFilter 15 | { 16 | public bool? And { get; set; } = false; 17 | public FilterType Type { get; set; } = FilterType.Equal; 18 | public string Path { get; set; } = "FirstName"; 19 | public object Value { get; set; } = "Chuck"; 20 | public bool? Not { get; set; } 21 | public bool? CaseInsensitive { get; set; } 22 | } 23 | 24 | [Fact] 25 | public void TestInversionOfControl() 26 | { 27 | MockContextFactory.SeedAndTestContextFor("FilterTests_TestInversionOfControl", TestSeeders.SimpleSeedScenario, ctx => 28 | { 29 | var resultShouldMatch = ctx.Customers.Where(t => t.FirstName == "Chuck").ToList(); 30 | 31 | var criteria = new QueryCriteria() 32 | { 33 | Filters = new List { new MockIsChuckFilter() } 34 | }; 35 | 36 | var queryHandler = new QueryHandler(Enumerable.Empty()); 37 | var result = queryHandler.Execute(ctx.Customers, criteria); 38 | Assert.Equal(resultShouldMatch, result.Data); 39 | }); 40 | } 41 | 42 | 43 | 44 | [Fact] 45 | public void SimpleFilter() 46 | { 47 | MockContextFactory.SeedAndTestContextFor("FilterTests_SimpleFilter", TestSeeders.SimpleSeedScenario, ctx => 48 | { 49 | var resultShouldMatch = ctx.Items.Where(t => t.Name.EndsWith("Cables")).ToList(); 50 | 51 | var criteria = new QueryCriteria() 52 | { 53 | Filters = new List 54 | { 55 | new SimpleFilter 56 | { 57 | Path = "Name", 58 | Type = FilterType.EndsWith, 59 | Value = "Cables" 60 | } 61 | } 62 | }; 63 | 64 | var queryHandler = new QueryHandler(Enumerable.Empty()); 65 | var result = queryHandler.Execute(ctx.Items, criteria); 66 | Assert.Equal(resultShouldMatch, result.Data); 67 | }); 68 | } 69 | 70 | [Fact] 71 | public void SimpleFilterCaseInsensitive() 72 | { 73 | MockContextFactory.SeedAndTestContextFor("FilterTests_SimpleFilterCaseInsensitive", TestSeeders.SimpleSeedScenario, ctx => 74 | { 75 | var resultShouldMatch = ctx.Items.Where(t => t.Name.ToLower().EndsWith("Cables".ToLower())).ToList(); 76 | 77 | var criteria = new QueryCriteria() 78 | { 79 | Filters = new List 80 | { 81 | new SimpleFilter 82 | { 83 | Path = "Name", 84 | Type = FilterType.EndsWith, 85 | Value = "Cables", 86 | CaseInsensitive = true 87 | } 88 | } 89 | }; 90 | 91 | var queryHandler = new QueryHandler(Enumerable.Empty()); 92 | var result = queryHandler.Execute(ctx.Items, criteria); 93 | Assert.Equal(resultShouldMatch, result.Data); 94 | }); 95 | } 96 | 97 | 98 | 99 | [Fact] 100 | public void CompositeFilter() 101 | { 102 | MockContextFactory.SeedAndTestContextFor("FilterTests_CompositeFilter", TestSeeders.SimpleSeedScenario, ctx => 103 | { 104 | var resultShouldMatch = ctx.Customers.Where(t => t.FirstName == "John" || t.LastName == "Norris").ToList(); 105 | 106 | var criteria = new QueryCriteria() 107 | { 108 | Filters = new List 109 | { 110 | new CompositeFilter() 111 | { 112 | Type = FilterType.Composite, 113 | Filters = new List 114 | { 115 | new SimpleFilter() { Path = "FirstName", Type = FilterType.Equal, Value = "John" }, 116 | new SimpleFilter() { Path = "LastName", Type = FilterType.Equal, Value = "Norris"} 117 | } 118 | } 119 | } 120 | }; 121 | 122 | var queryHandler = new QueryHandler(Enumerable.Empty()); 123 | var result = queryHandler.Execute(ctx.Customers, criteria); 124 | Assert.Equal(resultShouldMatch, result.Data); 125 | }); 126 | } 127 | 128 | [Fact] 129 | public void MoreComplexCompositeFilter() 130 | { 131 | MockContextFactory.SeedAndTestContextFor("FilterTests_MoreComplexCompositeFilter", TestSeeders.SimpleSeedScenario, ctx => 132 | { 133 | var resultShouldMatch = ctx.Customers.Where(t => (t.FirstName == "John" || t.LastName == "Norris") && t.FirstName.Contains("o")).ToList(); 134 | 135 | var criteria = new QueryCriteria() 136 | { 137 | Filters = new List 138 | { 139 | new CompositeFilter() 140 | { 141 | Type = FilterType.Composite, 142 | Filters = new List 143 | { 144 | new SimpleFilter() { Path = "FirstName", Type = FilterType.Equal, Value = "John" }, 145 | new SimpleFilter() { Path = "LastName", Type = FilterType.Equal, Value = "Norris"} 146 | } 147 | }, 148 | new SimpleFilter() 149 | { 150 | And = true, 151 | Path = "FirstName", 152 | Type = FilterType.Contains, 153 | Value = "o" 154 | } 155 | } 156 | }; 157 | 158 | var queryHandler = new QueryHandler(Enumerable.Empty()); 159 | var result = queryHandler.Execute(ctx.Customers, criteria); 160 | Assert.Equal(resultShouldMatch, result.Data); 161 | }); 162 | } 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicQuery.Test/GroupInterceptorTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using PoweredSoft.DynamicQuery.Core; 3 | using PoweredSoft.DynamicQuery.Extensions; 4 | using PoweredSoft.DynamicQuery.Test.Mock; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Text; 9 | using Xunit; 10 | 11 | namespace PoweredSoft.DynamicQuery.Test 12 | { 13 | public partial class GroupInterceptorTests 14 | { 15 | private class MockGroupInterceptor : IGroupInterceptor 16 | { 17 | public IGroup InterceptGroup(IGroup group) 18 | { 19 | return new Group() 20 | { 21 | Path = "Customer.FirstName" 22 | }; 23 | } 24 | } 25 | 26 | [Fact] 27 | public void Simple() 28 | { 29 | MockContextFactory.SeedAndTestContextFor("GroupInterceptorTests_Simple", TestSeeders.SimpleSeedScenario, ctx => 30 | { 31 | var expected = ctx.Orders 32 | .OrderBy(t => t.Customer.FirstName) 33 | .GroupBy(t => t.Customer.FirstName) 34 | .Select(t => t.Key) 35 | .ToList(); 36 | 37 | var criteria = new QueryCriteria(); 38 | criteria.Groups.Add(new Group { Path = "CustomerFirstName" }); 39 | var queryHandler = new QueryHandler(Enumerable.Empty()); 40 | queryHandler.AddInterceptor(new MockGroupInterceptor()); 41 | var result = queryHandler.Execute(ctx.Orders.Include(t => t.Customer), criteria, new QueryExecutionOptions 42 | { 43 | GroupByInMemory = true 44 | }); 45 | 46 | var groupedResult = result.GroupedResult(); 47 | var actual = groupedResult.Groups.Select(t => t.GroupValue).ToList(); 48 | Assert.Equal(expected, actual); 49 | }); 50 | } 51 | 52 | [Fact] 53 | public void WithInterptorSimple() 54 | { 55 | MockContextFactory.SeedAndTestContextFor("GroupInterceptorTests_WithInterptorSimple", TestSeeders.SimpleSeedScenario, ctx => 56 | { 57 | var expected = ctx.Orders 58 | .OrderBy(t => t.Customer.FirstName) 59 | .GroupBy(t => t.Customer.FirstName) 60 | .Select(t => t.Key) 61 | .ToList(); 62 | 63 | var criteria = new QueryCriteria(); 64 | criteria.Groups.Add(new Group { Path = "CustomerFirstName" }); 65 | var queryHandler = new QueryHandler(Enumerable.Empty()); 66 | queryHandler.AddInterceptor(new MockGroupInterceptor()); 67 | queryHandler.AddInterceptor(new MockQueryExecutionOptionsInterceptor()); 68 | var result = queryHandler.Execute(ctx.Orders.Include(t => t.Customer), criteria); 69 | 70 | var groupedResult = result.GroupedResult(); 71 | var actual = groupedResult.Groups.Select(t => t.GroupValue).ToList(); 72 | Assert.Equal(expected, actual); 73 | }); 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicQuery.Test/GroupTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using PoweredSoft.DynamicQuery.Core; 3 | using PoweredSoft.DynamicQuery.Extensions; 4 | using PoweredSoft.DynamicQuery.Test.Mock; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Text; 9 | using System.Threading; 10 | using System.Threading.Tasks; 11 | using Xunit; 12 | 13 | namespace PoweredSoft.DynamicQuery.Test 14 | { 15 | public class GroupTests 16 | { 17 | [Fact] 18 | public void Simple() 19 | { 20 | MockContextFactory.SeedAndTestContextFor("GroupTests_Simple", TestSeeders.SimpleSeedScenario, ctx => 21 | { 22 | var shouldResult = ctx.Orders 23 | .OrderBy(t => t.CustomerId) 24 | .ToList() 25 | .GroupBy(t => t.CustomerId) 26 | .Select(t => new 27 | { 28 | CustomerId = t.Key, 29 | Orders = t.ToList() 30 | }) 31 | .ToList(); 32 | 33 | // query handler that is empty should be the same as running to list. 34 | var criteria = new QueryCriteria() 35 | { 36 | Groups = new List 37 | { 38 | new Group { Path = "CustomerId" } 39 | } 40 | }; 41 | 42 | var queryHandler = new QueryHandler(Enumerable.Empty()); 43 | var result = queryHandler.Execute(ctx.Orders, criteria, new QueryExecutionOptions 44 | { 45 | GroupByInMemory = true, 46 | GroupByInMemoryNullCheck = false 47 | }); 48 | var groupedResult = result.GroupedResult(); 49 | 50 | // top level should have same amount of group levels. 51 | Assert.Equal(groupedResult.Groups.Count, shouldResult.Count); 52 | for (var i = 0; i < shouldResult.Count; i++) 53 | { 54 | var expected = shouldResult[0]; 55 | var actual = groupedResult.Groups[0]; 56 | Assert.Equal(expected.CustomerId, actual.GroupValue); 57 | 58 | var expectedOrderIds = expected.Orders.Select(t => t.Id).ToList(); 59 | var actualOrderIds = actual.Data.Cast().Select(t => t.Id).ToList(); 60 | Assert.Equal(expectedOrderIds, actualOrderIds); 61 | } 62 | }); 63 | } 64 | 65 | [Fact] 66 | public void GroupComplex() 67 | { 68 | MockContextFactory.SeedAndTestContextFor("GroupTests_Complex", TestSeeders.SeedTicketScenario, ctx => 69 | { 70 | var criteria = new QueryCriteria() 71 | { 72 | Groups = new List() 73 | { 74 | new Group { Path = "TicketType" }, 75 | new Group { Path = "Priority" } 76 | }, 77 | Aggregates = new List() 78 | { 79 | new Aggregate { Type = AggregateType.Count } 80 | } 81 | }; 82 | 83 | var queryHandler = new QueryHandler(Enumerable.Empty()); 84 | var result = queryHandler.Execute(ctx.Tickets, criteria, new QueryExecutionOptions 85 | { 86 | GroupByInMemory = true 87 | }); 88 | 89 | var groupedResult = result.GroupedResult(); 90 | 91 | var firstGroup = groupedResult.Groups.FirstOrDefault(); 92 | Assert.NotNull(firstGroup); 93 | var secondGroup = groupedResult.Groups.Skip(1).FirstOrDefault(); 94 | Assert.NotNull(secondGroup); 95 | 96 | var expected = ctx.Tickets.Select(t => t.TicketType).Distinct().Count(); 97 | var c = groupedResult.Groups.Select(t => t.GroupValue).Count(); 98 | Assert.Equal(expected, c); 99 | }); 100 | } 101 | 102 | [Fact] 103 | public void InterceptorsWithGrouping() 104 | { 105 | MockContextFactory.SeedAndTestContextFor("GroupTests_InterceptorsWithGrouping", TestSeeders.SeedTicketScenario, ctx => 106 | { 107 | var criteria = new QueryCriteria() 108 | { 109 | Groups = new List() 110 | { 111 | new Group { Path = "TicketType" } 112 | }, 113 | Aggregates = new List() 114 | { 115 | new Aggregate { Type = AggregateType.Count } 116 | } 117 | }; 118 | 119 | var interceptor = new InterceptorsWithGrouping(); 120 | var queryHandler = new QueryHandler(Enumerable.Empty()); 121 | queryHandler.AddInterceptor(interceptor); 122 | var result = queryHandler.Execute(ctx.Tickets, criteria, new QueryExecutionOptions 123 | { 124 | GroupByInMemory = true 125 | }); 126 | 127 | Assert.Equal(4, interceptor.Count); 128 | Assert.True(interceptor.Test); 129 | Assert.True(interceptor.Test2); 130 | Assert.True(interceptor.Test3); 131 | Assert.True(interceptor.Test4); 132 | }); 133 | } 134 | } 135 | 136 | class InterceptorWithGroupingFakeModel 137 | { 138 | 139 | } 140 | 141 | class InterceptorsWithGrouping : 142 | IAfterReadEntityInterceptor, 143 | IAfterReadEntityInterceptorAsync, 144 | IAfterReadInterceptor, 145 | IAfterReadInterceptorAsync, 146 | IQueryConvertInterceptor 147 | { 148 | public int Count { get; set; } = 0; 149 | public bool Test { get; set; } = false; 150 | public bool Test2 { get; set; } = false; 151 | public bool Test3 { get; set; } = false; 152 | public bool Test4 { get; set; } = false; 153 | 154 | public void AfterRead(List> pairs) 155 | { 156 | Test = true; 157 | Count++; 158 | } 159 | 160 | public Task AfterReadAsync(List> pairs, CancellationToken cancellationToken = default(CancellationToken)) 161 | { 162 | Test2 = true; 163 | Count++; 164 | return Task.CompletedTask; 165 | } 166 | 167 | public void AfterReadEntity(List entities) 168 | { 169 | Test3 = true; 170 | Count++; 171 | } 172 | 173 | public Task AfterReadEntityAsync(List entities, CancellationToken cancellationToken = default(CancellationToken)) 174 | { 175 | Test4 = true; 176 | Count++; 177 | return Task.CompletedTask; 178 | } 179 | 180 | public object InterceptResultTo(Ticket entity) 181 | { 182 | return new InterceptorWithGroupingFakeModel(); 183 | } 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicQuery.Test/IncludeStrategyTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using PoweredSoft.DynamicQuery.Core; 3 | using PoweredSoft.DynamicQuery.Test.Mock; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Text; 8 | using Xunit; 9 | 10 | namespace PoweredSoft.DynamicQuery.Test 11 | { 12 | public class IncludeStrategyTests 13 | { 14 | private class MockIncludeStrategyInterceptor : IIncludeStrategyInterceptor 15 | { 16 | public IQueryable InterceptIncludeStrategy(IQueryCriteria criteria, IQueryable queryable) 17 | { 18 | queryable = ((IQueryable)queryable).Include(t => t.Customer); 19 | return queryable; 20 | } 21 | } 22 | 23 | private class MockIncludeStrategyGenericInterceptor : 24 | IIncludeStrategyInterceptor, 25 | IIncludeStrategyInterceptor 26 | { 27 | public IQueryable InterceptIncludeStrategy(IQueryCriteria criteria, IQueryable queryable) 28 | { 29 | return queryable.Include(t => t.Customer); 30 | } 31 | 32 | public IQueryable InterceptIncludeStrategy(IQueryCriteria criteria, IQueryable queryable) 33 | { 34 | // should not go in here. 35 | throw new NotImplementedException(); 36 | } 37 | } 38 | 39 | [Fact] 40 | public void NonGeneric() 41 | { 42 | MockContextFactory.SeedAndTestContextFor("IncludeStrategyTests_NonGeneric", TestSeeders.SimpleSeedScenario, ctx => 43 | { 44 | var criteria = new QueryCriteria(); 45 | var interceptor = new MockIncludeStrategyInterceptor(); 46 | 47 | // queryable of orders. 48 | var queryable = ctx.Orders.AsQueryable(); 49 | 50 | // pass into the interceptor. 51 | queryable = (IQueryable)interceptor.InterceptIncludeStrategy(criteria, queryable); 52 | 53 | // query handler should pass by the same interceptor. 54 | var queryHandler = new QueryHandler(Enumerable.Empty()); 55 | queryHandler.AddInterceptor(interceptor); 56 | var result = queryHandler.Execute(ctx.Orders, criteria); 57 | 58 | // compare results. 59 | var expected = queryable.ToList(); 60 | Assert.Equal(expected, result.Data); 61 | }); 62 | } 63 | 64 | [Fact] 65 | public void Generic() 66 | { 67 | MockContextFactory.SeedAndTestContextFor("IncludeStrategyTests_Generic", TestSeeders.SimpleSeedScenario, ctx => 68 | { 69 | var criteria = new QueryCriteria(); 70 | var interceptor = new MockIncludeStrategyGenericInterceptor(); 71 | 72 | // queryable of orders. 73 | var queryable = ctx.Orders.AsQueryable(); 74 | 75 | // pass into the interceptor. 76 | queryable = interceptor.InterceptIncludeStrategy(criteria, queryable); 77 | 78 | // query handler should pass by the same interceptor. 79 | var queryHandler = new QueryHandler(Enumerable.Empty()); 80 | queryHandler.AddInterceptor(interceptor); 81 | var result = queryHandler.Execute(ctx.Orders, criteria); 82 | 83 | // compare results. 84 | var expected = queryable.ToList(); 85 | Assert.Equal(expected, result.Data); 86 | }); 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicQuery.Test/Mock/Entities.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace PoweredSoft.DynamicQuery.Test.Mock 5 | { 6 | public class Order 7 | { 8 | public long Id { get; set; } 9 | public long OrderNum { get; set; } 10 | public DateTime Date { get; set; } 11 | public long CustomerId { get; set; } 12 | 13 | public virtual Customer Customer { get; set; } 14 | public ICollection OrderItems { get; set; } = new HashSet(); 15 | } 16 | 17 | public class Customer 18 | { 19 | public long Id { get; set; } 20 | public string FirstName { get; set; } 21 | public string LastName { get; set; } 22 | 23 | 24 | public ICollection Orders { get; set; } = new HashSet(); 25 | } 26 | 27 | public class Item 28 | { 29 | public long Id { get; set; } 30 | public string Name { get; set; } 31 | public decimal Price { get; set; } 32 | 33 | public virtual ICollection OrderItems { get; set; } = new HashSet(); 34 | } 35 | 36 | public class OrderItem 37 | { 38 | public long Id { get; set; } 39 | public long Quantity { get; set; } 40 | public decimal PriceAtTheTime { get; set; } 41 | public long ItemId { get; set; } 42 | public long OrderId { get; set; } 43 | 44 | public virtual Item Item { get; set; } 45 | public virtual Order Order { get; set; } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicQuery.Test/Mock/MockContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using Microsoft.EntityFrameworkCore; 5 | 6 | namespace PoweredSoft.DynamicQuery.Test.Mock 7 | { 8 | public class MockContext : DbContext 9 | { 10 | public virtual DbSet Customers { get; set; } 11 | public virtual DbSet Items { get; set; } 12 | public virtual DbSet Orders { get; set; } 13 | public virtual DbSet OrderItems { get; set; } 14 | public virtual DbSet Tickets { get; set; } 15 | 16 | public MockContext() 17 | { 18 | 19 | } 20 | 21 | public MockContext(DbContextOptions options) 22 | : base(options) 23 | { 24 | 25 | } 26 | 27 | protected override void OnModelCreating(ModelBuilder modelBuilder) 28 | { 29 | 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicQuery.Test/Mock/MockContextFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Threading.Tasks; 5 | using Microsoft.EntityFrameworkCore; 6 | using Microsoft.EntityFrameworkCore.Diagnostics; 7 | using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine.ClientProtocol; 8 | using Xunit.Sdk; 9 | 10 | namespace PoweredSoft.DynamicQuery.Test.Mock 11 | { 12 | public static class MockContextFactory 13 | { 14 | public static void TestContextFor(string testName, Action action) 15 | { 16 | var options = new DbContextOptionsBuilder() 17 | .UseInMemoryDatabase(databaseName: testName).Options; 18 | 19 | using var ctx = new MockContext(options); 20 | action(ctx); 21 | } 22 | 23 | public static void SeedAndTestContextFor(string testName, Action seedAction, Action action) 24 | { 25 | seedAction(testName); 26 | TestContextFor(testName, action); 27 | } 28 | 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicQuery.Test/Mock/TestSeeders.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace PoweredSoft.DynamicQuery.Test.Mock 5 | { 6 | public static class TestSeeders 7 | { 8 | public static void SimpleSeedScenario(string testName) 9 | { 10 | MockContextFactory.TestContextFor(testName, ctx => 11 | { 12 | ctx.AddRange(new Customer[] 13 | { 14 | new Customer() { Id = 1, FirstName = "David", LastName = "Lebee" }, 15 | new Customer() { Id = 2, FirstName = "John", LastName = "Doe" }, 16 | new Customer() { Id = 3, FirstName = "Chuck", LastName = "Norris" }, 17 | new Customer() { Id = 4, FirstName = "Nelson", LastName = "Mendela" }, 18 | new Customer() { Id = 5, FirstName = "Jimi", LastName = "Hendrix" }, 19 | new Customer() { Id = 6, FirstName = "Axel", LastName = "Rose" }, 20 | new Customer() { Id = 7, FirstName = "John", LastName = "Frusciante" }, 21 | new Customer() { Id = 8, FirstName = "Michael", LastName = "Jackson" }, 22 | new Customer() { Id = 9, FirstName = "Anita", LastName = "Franklin" }, 23 | }); 24 | 25 | ctx.AddRange(new Item[] 26 | { 27 | new Item { Id = 1, Name = "Computer", Price = 1000M }, 28 | new Item { Id = 2, Name = "Mice", Price = 25.99M }, 29 | new Item { Id = 3, Name = "Keyboard", Price = 100M }, 30 | new Item { Id = 4, Name = "Screen", Price = 499.98M }, 31 | new Item { Id = 5, Name = "Printer", Price = 230.95M }, 32 | new Item { Id = 6, Name = "HDMI Cables", Price = 20M }, 33 | new Item { Id = 7, Name = "Power Cables", Price = 5.99M } 34 | }); 35 | 36 | ctx.Orders.AddRange(new Order[] 37 | { 38 | new Order() 39 | { 40 | Id = 1, 41 | OrderNum = 1000, 42 | CustomerId = 1, 43 | Date = new DateTime(2018, 1, 1), 44 | OrderItems = new List() 45 | { 46 | new OrderItem() { Id = 1, ItemId = 1, PriceAtTheTime = 1000M, Quantity = 1 }, 47 | new OrderItem() { Id = 2, ItemId = 2, PriceAtTheTime = 30M, Quantity = 1 }, 48 | new OrderItem() { Id = 3, ItemId = 4, PriceAtTheTime = 399.99M, Quantity = 2 }, 49 | new OrderItem() { Id = 4, ItemId = 6, PriceAtTheTime = 20, Quantity = 2 }, 50 | new OrderItem() { Id = 8, ItemId = 6, PriceAtTheTime = 3.99M, Quantity = 3 } 51 | } 52 | }, 53 | new Order() 54 | { 55 | Id = 2, 56 | OrderNum = 1001, 57 | CustomerId = 2, 58 | Date = new DateTime(2018, 2, 1), 59 | OrderItems = new List() 60 | { 61 | new OrderItem() { Id = 9, ItemId = 6, PriceAtTheTime = 20, Quantity = 2 }, 62 | new OrderItem() { Id = 10, ItemId = 6, PriceAtTheTime = 3.99M, Quantity = 3 } 63 | } 64 | }, 65 | new Order() 66 | { 67 | Id = 3, 68 | OrderNum = 1002, 69 | CustomerId = 3, 70 | Date = new DateTime(2018, 2, 1), 71 | OrderItems = new List() 72 | { 73 | new OrderItem() { Id = 11, ItemId = 5, PriceAtTheTime = 499.99M, Quantity = 1 }, 74 | new OrderItem() { Id = 12, ItemId = 6, PriceAtTheTime = 20, Quantity = 1 }, 75 | new OrderItem() { Id = 13, ItemId = 7, PriceAtTheTime = 3.99M, Quantity = 1 } 76 | } 77 | }, 78 | new Order() 79 | { 80 | Id = 4, 81 | OrderNum = 1003, 82 | CustomerId = 1, 83 | Date = new DateTime(2018, 3, 1), 84 | OrderItems = new List() 85 | { 86 | new OrderItem() { Id = 14, ItemId = 2, PriceAtTheTime = 50M, Quantity = 1 }, 87 | new OrderItem() { Id = 15, ItemId = 3, PriceAtTheTime = 75.50M, Quantity = 1 }, 88 | } 89 | } 90 | }); 91 | 92 | ctx.SaveChanges(); 93 | }); 94 | } 95 | 96 | internal static void SeedTicketScenario(string testName) 97 | { 98 | MockContextFactory.TestContextFor(testName, ctx => 99 | { 100 | var owners = new List(); 101 | 102 | for(var i = 0; i < 20; i++) 103 | { 104 | var f = new Bogus.Faker("en"); 105 | owners.Add(f.Person.FullName); 106 | } 107 | 108 | var faker = new Bogus.Faker() 109 | .RuleFor(t => t.TicketType, (f, u) => f.PickRandom("new", "open", "refused", "closed")) 110 | .RuleFor(t => t.Title, (f, u) => f.Lorem.Sentence()) 111 | .RuleFor(t => t.Details, (f, u) => f.Lorem.Paragraph()) 112 | .RuleFor(t => t.IsHtml, (f, u) => false) 113 | .RuleFor(t => t.TagList, (f, u) => string.Join(",", f.Commerce.Categories(3))) 114 | .RuleFor(t => t.CreatedDate, (f, u) => f.Date.Recent(100)) 115 | .RuleFor(t => t.Owner, (f, u) => f.PickRandom(owners)) 116 | .RuleFor(t => t.AssignedTo, (f, u) => f.PickRandom(owners)) 117 | .RuleFor(t => t.TicketStatus, (f, u) => f.PickRandom(1, 2, 3)) 118 | .RuleFor(t => t.LastUpdateBy, (f, u) => f.Person.FullName) 119 | .RuleFor(t => t.LastUpdateDate, (f, u) => f.Date.Soon(5)) 120 | .RuleFor(t => t.Priority, (f, u) => f.PickRandom("low", "medium", "high", "critical")) 121 | .RuleFor(t => t.AffectedCustomer, (f, u) => f.PickRandom(true, false)) 122 | .RuleFor(t => t.Version, (f, u) => f.PickRandom("1.0.0", "1.1.0", "2.0.0")) 123 | .RuleFor(t => t.ProjectId, (f, u) => f.Random.Number(100)) 124 | .RuleFor(t => t.DueDate, (f, u) => f.Date.Soon(5)) 125 | .RuleFor(t => t.EstimatedDuration, (f, u) => f.Random.Number(20)) 126 | .RuleFor(t => t.ActualDuration, (f, u) => f.Random.Number(20)) 127 | .RuleFor(t => t.TargetDate, (f, u) => f.Date.Soon(5)) 128 | .RuleFor(t => t.ResolutionDate, (f, u) => f.Date.Soon(5)) 129 | .RuleFor(t => t.Type, (f, u) => f.PickRandom(1, 2, 3)) 130 | .RuleFor(t => t.ParentId, () => 0) 131 | .RuleFor(t => t.PreferredLanguage, (f, u) => f.PickRandom("fr", "en", "es")) 132 | ; 133 | 134 | var fakeModels = new List(); 135 | for (var i = 0; i < 500; i++) 136 | { 137 | var t = faker.Generate(); 138 | t.TicketId = i + 1; 139 | fakeModels.Add(t); 140 | } 141 | 142 | ctx.AddRange(fakeModels); 143 | ctx.SaveChanges(); 144 | }); 145 | } 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicQuery.Test/Mock/Ticket.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace PoweredSoft.DynamicQuery.Test.Mock 6 | { 7 | public class Ticket 8 | { 9 | public int TicketId { get; set; } 10 | public string TicketType { get; set; } 11 | public string Title { get; set; } 12 | public string Details { get; set; } 13 | public bool IsHtml { get; set; } 14 | public string TagList { get; set; } 15 | public DateTimeOffset CreatedDate { get; set; } 16 | public string Owner { get; set; } 17 | public string AssignedTo { get; set; } 18 | public int TicketStatus { get; set; } 19 | public DateTimeOffset CurrentStatusDate { get; set; } 20 | public string CurrentStatusSetBy { get; set; } 21 | public string LastUpdateBy { get; set; } 22 | public DateTimeOffset LastUpdateDate { get; set; } 23 | public string Priority { get; set; } 24 | public bool AffectedCustomer { get; set; } 25 | public string Version { get; set; } 26 | public int ProjectId { get; set; } 27 | public DateTimeOffset DueDate { get; set; } 28 | public decimal EstimatedDuration { get; set; } 29 | public decimal ActualDuration { get; set; } 30 | public DateTimeOffset TargetDate { get; set; } 31 | public DateTimeOffset ResolutionDate { get; set; } 32 | public int Type { get; set; } 33 | public int ParentId { get; set; } 34 | public string PreferredLanguage { get; set; } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicQuery.Test/MockQueryExecutionOptionsInterceptor.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Query; 2 | using PoweredSoft.DynamicQuery.Core; 3 | using System.Linq; 4 | 5 | namespace PoweredSoft.DynamicQuery.Test 6 | { 7 | public partial class GroupInterceptorTests 8 | { 9 | public class MockQueryExecutionOptionsInterceptor : IQueryExecutionOptionsInterceptor 10 | { 11 | public IQueryExecutionOptions InterceptQueryExecutionOptions(IQueryable queryable, IQueryExecutionOptions current) 12 | { 13 | if (queryable.Provider is IAsyncQueryProvider) 14 | { 15 | current.GroupByInMemory = true; 16 | } 17 | 18 | return current; 19 | } 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicQuery.Test/NoSortTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using PoweredSoft.DynamicQuery.Core; 3 | using PoweredSoft.DynamicQuery.Test.Mock; 4 | using PoweredSoft.DynamicLinq; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Text; 9 | using Xunit; 10 | 11 | namespace PoweredSoft.DynamicQuery.Test 12 | { 13 | public class NoSortTests 14 | { 15 | private class MockNoSortInterceptor : INoSortInterceptor 16 | { 17 | public IQueryable InterceptNoSort(IQueryCriteria criteria, IQueryable queryable) 18 | { 19 | return queryable.OrderBy("Customer.LastName"); 20 | } 21 | } 22 | 23 | private class MockNoSortGenericInterceptor : 24 | INoSortInterceptor, 25 | INoSortInterceptor 26 | { 27 | public IQueryable InterceptNoSort(IQueryCriteria criteria, IQueryable queryable) 28 | { 29 | return queryable.OrderBy(t => t.Customer.LastName); 30 | } 31 | 32 | public IQueryable InterceptNoSort(IQueryCriteria criteria, IQueryable queryable) 33 | { 34 | // should not go in here. 35 | throw new NotImplementedException(); 36 | } 37 | } 38 | 39 | [Fact] 40 | public void NonGeneric() 41 | { 42 | MockContextFactory.SeedAndTestContextFor("NoSortTests_NonGeneric", TestSeeders.SimpleSeedScenario, ctx => 43 | { 44 | var criteria = new QueryCriteria(); 45 | var interceptor = new MockNoSortInterceptor(); 46 | 47 | // queryable of orders. 48 | var queryable = ctx.Orders.AsQueryable(); 49 | 50 | // pass into the interceptor. 51 | queryable = (IQueryable)interceptor.InterceptNoSort(criteria, queryable); 52 | 53 | // query handler should pass by the same interceptor. 54 | var queryHandler = new QueryHandler(Enumerable.Empty()); 55 | queryHandler.AddInterceptor(interceptor); 56 | var result = queryHandler.Execute(ctx.Orders, criteria); 57 | 58 | // compare results. 59 | var expected = queryable.ToList(); 60 | Assert.Equal(expected, result.Data); 61 | }); 62 | } 63 | 64 | [Fact] 65 | public void Generic() 66 | { 67 | MockContextFactory.SeedAndTestContextFor("NoSortTests_Generic", TestSeeders.SimpleSeedScenario, ctx => 68 | { 69 | var criteria = new QueryCriteria(); 70 | var interceptor = new MockNoSortGenericInterceptor(); 71 | 72 | // queryable of orders. 73 | var queryable = ctx.Orders.AsQueryable(); 74 | 75 | // pass into the interceptor. 76 | queryable = interceptor.InterceptNoSort(criteria, queryable); 77 | 78 | // query handler should pass by the same interceptor. 79 | var queryHandler = new QueryHandler(Enumerable.Empty()); 80 | queryHandler.AddInterceptor(interceptor); 81 | var result = queryHandler.Execute(ctx.Orders, criteria); 82 | 83 | // compare results. 84 | var expected = queryable.ToList(); 85 | Assert.Equal(expected, result.Data); 86 | }); 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicQuery.Test/PoweredSoft.DynamicQuery.Test.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net5.0 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | all 20 | runtime; build; native; contentfiles; analyzers 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicQuery.Test/QueryProviderTests.cs: -------------------------------------------------------------------------------- 1 | using PoweredSoft.DynamicQuery.Core; 2 | using PoweredSoft.DynamicQuery.Test.Mock; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using Xunit; 8 | 9 | namespace PoweredSoft.DynamicQuery.Test 10 | { 11 | public class QueryProviderTests 12 | { 13 | private class FakeInterceptor : IQueryInterceptor 14 | { 15 | 16 | } 17 | 18 | private class QueryInterceptorProvider : IQueryInterceptorProvider 19 | { 20 | public IEnumerable GetInterceptors(IQueryCriteria queryCriteria, IQueryable queryable) 21 | { 22 | yield return new FakeInterceptor(); 23 | yield return new FakeInterceptor(); 24 | } 25 | } 26 | 27 | [Fact] 28 | public void Simple() 29 | { 30 | MockContextFactory.SeedAndTestContextFor("QueryProviderTests_Simple", TestSeeders.SimpleSeedScenario, ctx => 31 | { 32 | // criteria 33 | var criteria = new QueryCriteria(); 34 | var queryHandler = new QueryHandler(new List{ 35 | new QueryInterceptorProvider() 36 | }); 37 | queryHandler.AddInterceptor(new FakeInterceptor()); 38 | var interceptors = queryHandler.ResolveInterceptors(criteria, ctx.Orders); 39 | Assert.Equal(1, interceptors.Count); 40 | Assert.True(interceptors[0].GetType() == typeof(FakeInterceptor)); 41 | }); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicQuery.Test/SortInterceptorTests.cs: -------------------------------------------------------------------------------- 1 | using PoweredSoft.DynamicQuery.Core; 2 | using PoweredSoft.DynamicQuery.Test.Mock; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using Xunit; 8 | 9 | namespace PoweredSoft.DynamicQuery.Test 10 | { 11 | public class SortInterceptorTests 12 | { 13 | private class MockSortInterceptor : ISortInterceptor 14 | { 15 | public IEnumerable InterceptSort(IEnumerable sort) 16 | { 17 | return new Sort[] 18 | { 19 | new Sort("Customer.FirstName"), 20 | new Sort("Customer.LastName") 21 | }; 22 | } 23 | } 24 | 25 | [Fact] 26 | public void Simple() 27 | { 28 | MockContextFactory.SeedAndTestContextFor("SortInterceptorTests_Simple", TestSeeders.SimpleSeedScenario, ctx => 29 | { 30 | // expected 31 | var expected = ctx.Orders.OrderBy(t => t.Customer.FirstName).ThenBy(t => t.Customer.LastName).ToList(); 32 | 33 | // criteria 34 | var criteria = new QueryCriteria(); 35 | criteria.Sorts.Add(new Sort("CustomerFullName")); 36 | var queryHandler = new QueryHandler(Enumerable.Empty()); 37 | queryHandler.AddInterceptor(new MockSortInterceptor()); 38 | var result = queryHandler.Execute(ctx.Orders, criteria); 39 | Assert.Equal(expected, result.Data); 40 | }); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicQuery.Test/SortTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using PoweredSoft.DynamicLinq; 6 | using PoweredSoft.DynamicQuery.Core; 7 | using PoweredSoft.DynamicQuery.Test.Mock; 8 | using Xunit; 9 | 10 | namespace PoweredSoft.DynamicQuery.Test 11 | { 12 | public class SortTests 13 | { 14 | [Fact] 15 | public void Simple() 16 | { 17 | MockContextFactory.SeedAndTestContextFor("SortTests_Simple", TestSeeders.SimpleSeedScenario, ctx => 18 | { 19 | var shouldResult = ctx.Orders.OrderBy(t => t.OrderNum).ToList(); 20 | 21 | // query handler that is empty should be the same as running to list. 22 | var criteria = new QueryCriteria() 23 | { 24 | Sorts = new List() 25 | { 26 | new Sort("OrderNum") 27 | } 28 | }; 29 | 30 | var queryHandler = new QueryHandler(Enumerable.Empty()); 31 | var result = queryHandler.Execute(ctx.Orders, criteria); 32 | Assert.Equal(shouldResult, result.Data); 33 | }); 34 | } 35 | 36 | [Fact] 37 | public void MultiSort() 38 | { 39 | MockContextFactory.SeedAndTestContextFor("SortTests_MultiSort", TestSeeders.SimpleSeedScenario, ctx => 40 | { 41 | var shouldResult = ctx.Orders.OrderBy(t => t.Customer.FirstName).ThenBy(t => t.OrderNum).ToList(); 42 | 43 | // query handler that is empty should be the same as running to list. 44 | var criteria = new QueryCriteria() 45 | { 46 | Sorts = new List() 47 | { 48 | new Sort("Customer.FirstName"), 49 | new Sort("OrderNum") 50 | } 51 | }; 52 | 53 | var queryHandler = new QueryHandler(Enumerable.Empty()); 54 | var result = queryHandler.Execute(ctx.Orders, criteria); 55 | Assert.Equal(shouldResult, result.Data); 56 | }); 57 | } 58 | 59 | private class MockSortInterceptor : ISortInterceptor 60 | { 61 | public IEnumerable InterceptSort(IEnumerable sort) 62 | { 63 | if (sort.Count() == 1 && sort.First().Path == "OrderNumStr") 64 | return new ISort[] { new Sort("OrderNum", false) }; 65 | 66 | return sort; 67 | } 68 | } 69 | 70 | [Fact] 71 | public void SortInterceptor() 72 | { 73 | MockContextFactory.SeedAndTestContextFor("SortTests_SortInterceptor", TestSeeders.SimpleSeedScenario, ctx => 74 | { 75 | var shouldResult = ctx.Orders.OrderByDescending(t => t.OrderNum).ToList(); 76 | 77 | // query handler that is empty should be the same as running to list. 78 | var criteria = new QueryCriteria() 79 | { 80 | Sorts = new List() 81 | { 82 | new Sort("OrderNumStr", false) 83 | } 84 | }; 85 | 86 | var queryHandler = new QueryHandler(Enumerable.Empty()); 87 | queryHandler.AddInterceptor(new MockSortInterceptor()); 88 | var result = queryHandler.Execute(ctx.Orders, criteria); 89 | Assert.Equal(shouldResult, result.Data); 90 | }); 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicQuery/Aggregate.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using PoweredSoft.DynamicQuery.Core; 5 | 6 | namespace PoweredSoft.DynamicQuery 7 | { 8 | public class Aggregate : IAggregate 9 | { 10 | public string Path { get; set; } 11 | public AggregateType Type { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicQuery/Extensions/FilterExtensions.cs: -------------------------------------------------------------------------------- 1 | using PoweredSoft.DynamicQuery.Core; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Linq.Expressions; 6 | using System.Reflection; 7 | using System.Text; 8 | 9 | namespace PoweredSoft.DynamicQuery.Extensions 10 | { 11 | public static class FilterExtensions 12 | { 13 | public static bool IsSimpleFilter(this IFilter filter) => filter is ISimpleFilter; 14 | public static bool IsCompositeFilter(this IFilter filter) => filter is ICompositeFilter; 15 | 16 | public static bool IsSimpleFilterOn(this IFilter filter, string path) 17 | { 18 | var simpleFilter = filter as ISimpleFilter; 19 | if (simpleFilter == null) 20 | return false; 21 | 22 | var result = simpleFilter.Path?.Equals(path, StringComparison.InvariantCultureIgnoreCase) == true; 23 | return result; 24 | } 25 | 26 | public static bool IsSimpleFilterOn(this IFilter filter, Expression> expr) 27 | { 28 | var resolved = GetPropertySymbol(expr); 29 | return filter.IsSimpleFilterOn(resolved); 30 | } 31 | 32 | public static ISimpleFilter ReplaceByOn(this IFilter filter, string path) 33 | { 34 | var simpleFilter = filter as ISimpleFilter; 35 | if (simpleFilter == null) 36 | throw new Exception("Must be a simple filter"); 37 | 38 | var ret = new SimpleFilter(); 39 | ret.And = filter.And; 40 | ret.Type = filter.Type; 41 | ret.Value = simpleFilter.Value; 42 | ret.Path = path; 43 | return ret; 44 | } 45 | 46 | public static ISimpleFilter ReplaceByOn(this IFilter filter, Expression> expr) 47 | { 48 | var resolved = GetPropertySymbol(expr); 49 | return filter.ReplaceByOn(resolved); 50 | } 51 | 52 | public static ICompositeFilter ReplaceByCompositeOn(this IFilter filter, params string[] paths) 53 | { 54 | var simpleFilter = filter as ISimpleFilter; 55 | if (simpleFilter == null) 56 | throw new Exception("Must be a simple filter"); 57 | 58 | var compositeFilter = new CompositeFilter(); 59 | compositeFilter.And = filter.And; 60 | compositeFilter.Type = FilterType.Composite; 61 | compositeFilter.Filters = paths 62 | .Select(t => new SimpleFilter 63 | { 64 | Type = filter.Type, 65 | Path = t, 66 | And = false, 67 | Value = simpleFilter.Value 68 | }) 69 | .AsEnumerable() 70 | .ToList(); 71 | return compositeFilter; 72 | } 73 | 74 | public static ICompositeFilter ReplaceByCompositeOn(this IFilter filter, params Expression>[] exprs) 75 | { 76 | var paths = exprs.Select(expr => GetPropertySymbol(expr)).ToArray(); 77 | return ReplaceByCompositeOn(filter, paths); 78 | } 79 | 80 | internal static string GetPropertySymbol(Expression> expression) 81 | { 82 | return string.Join(".", 83 | GetMembersOnPath(expression.Body as MemberExpression) 84 | .Select(m => m.Member.Name) 85 | .Reverse()); 86 | } 87 | 88 | internal static IEnumerable GetMembersOnPath(MemberExpression expression) 89 | { 90 | while (expression != null) 91 | { 92 | yield return expression; 93 | expression = expression.Expression as MemberExpression; 94 | } 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicQuery/Extensions/FilterTypeExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using PoweredSoft.DynamicLinq; 5 | using PoweredSoft.DynamicQuery.Core; 6 | 7 | namespace PoweredSoft.DynamicQuery.Extensions 8 | { 9 | public static class FilterTypeExtensions 10 | { 11 | public static ConditionOperators? ConditionOperator(this FilterType filterType) 12 | { 13 | if (filterType == FilterType.Equal) 14 | return ConditionOperators.Equal; 15 | if (filterType == FilterType.NotEqual) 16 | return ConditionOperators.NotEqual; 17 | if (filterType == FilterType.GreaterThan) 18 | return ConditionOperators.GreaterThan; 19 | if (filterType == FilterType.GreaterThanOrEqual) 20 | return ConditionOperators.GreaterThanOrEqual; 21 | if (filterType == FilterType.LessThan) 22 | return ConditionOperators.LessThan; 23 | if (filterType == FilterType.LessThanOrEqual) 24 | return ConditionOperators.LessThanOrEqual; 25 | if (filterType == FilterType.StartsWith) 26 | return ConditionOperators.StartsWith; 27 | if (filterType == FilterType.EndsWith) 28 | return ConditionOperators.EndsWith; 29 | if (filterType == FilterType.Contains) 30 | return ConditionOperators.Contains; 31 | if (filterType == FilterType.In) 32 | return ConditionOperators.In; 33 | if (filterType == FilterType.NotIn) 34 | return ConditionOperators.NotIn; 35 | 36 | return null; 37 | } 38 | 39 | public static SelectTypes? SelectType(this AggregateType aggregateType) 40 | { 41 | if (aggregateType == AggregateType.Avg) 42 | return SelectTypes.Average; 43 | if (aggregateType == AggregateType.Count) 44 | return SelectTypes.Count; 45 | if (aggregateType == AggregateType.LongCount) 46 | return SelectTypes.LongCount; 47 | if (aggregateType == AggregateType.Sum) 48 | return SelectTypes.Sum; 49 | else if (aggregateType == AggregateType.Min) 50 | return SelectTypes.Min; 51 | else if (aggregateType == AggregateType.Max) 52 | return SelectTypes.Max; 53 | else if (aggregateType == AggregateType.First) 54 | return SelectTypes.First; 55 | else if (aggregateType == AggregateType.FirstOrDefault) 56 | return SelectTypes.FirstOrDefault; 57 | else if (aggregateType == AggregateType.Last) 58 | return SelectTypes.Last; 59 | else if (aggregateType == AggregateType.LastOrDefault) 60 | return SelectTypes.LastOrDefault; 61 | 62 | 63 | return null; 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicQuery/Extensions/GroupResultExtensions.cs: -------------------------------------------------------------------------------- 1 | using PoweredSoft.DynamicQuery.Core; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | namespace PoweredSoft.DynamicQuery.Extensions 7 | { 8 | public static class GroupResultExtensions 9 | { 10 | public static IQueryExecutionGroupResult GroupedResult(this IQueryExecutionResult source) 11 | { 12 | if (source is IQueryExecutionGroupResult ret) 13 | return ret; 14 | 15 | throw new Exception("this result is not a grouped result"); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicQuery/Filter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using PoweredSoft.DynamicQuery.Core; 5 | 6 | namespace PoweredSoft.DynamicQuery 7 | { 8 | public abstract class Filter : IFilter 9 | { 10 | public bool? And { get; set; } 11 | public FilterType Type { get; set; } 12 | } 13 | 14 | public class SimpleFilter : ISimpleFilter 15 | { 16 | public bool? And { get; set; } 17 | public bool? Not { get; set; } 18 | public FilterType Type { get; set; } 19 | public string Path { get; set; } 20 | public object Value { get; set; } 21 | public bool? CaseInsensitive { get; set; } 22 | } 23 | 24 | public class CompositeFilter : ICompositeFilter 25 | { 26 | public bool? And { get; set; } 27 | public FilterType Type { get; set; } = FilterType.Composite; 28 | public List Filters { get; set; } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicQuery/Group.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using PoweredSoft.DynamicQuery.Core; 5 | 6 | namespace PoweredSoft.DynamicQuery 7 | { 8 | public class Group : IGroup 9 | { 10 | public string Path { get; set; } 11 | public bool? Ascending { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicQuery/PoweredSoft.DynamicQuery.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | Powered Softwares Inc. 6 | MIT 7 | https://github.com/PoweredSoft/DynamicQuery 8 | https://github.com/PoweredSoft/DynamicQuery 9 | github 10 | powered,soft,dynamic,criteria,query,builder 11 | 3.0.0$(VersionSuffix) 12 | https://secure.gravatar.com/avatar/4e32f73820c16718909a06c2927f1f8b?s=512&amp;r=g&amp;d=retro 13 | PoweredSoft.DynamicQuery 14 | dynamic query based on string path very usefull for network requests. 15 | PoweredSoft.DynamicQuery 16 | False 17 | Powered Soft 18 | David Lebee 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicQuery/QueryCriteria.cs: -------------------------------------------------------------------------------- 1 | using PoweredSoft.DynamicQuery.Core; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | namespace PoweredSoft.DynamicQuery 7 | { 8 | public class QueryCriteria : IQueryCriteria 9 | { 10 | public int? Page { get; set; } 11 | public int? PageSize { get; set; } 12 | public List Sorts { get; set; } = new List(); 13 | public List Filters { get; set; } = new List(); 14 | public List Groups { get; set; } = new List(); 15 | public List Aggregates { get; set; } = new List(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicQuery/QueryHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Reflection; 6 | using System.Text; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | using PoweredSoft.DynamicLinq; 10 | using PoweredSoft.DynamicLinq.Fluent; 11 | using PoweredSoft.DynamicQuery.Core; 12 | 13 | namespace PoweredSoft.DynamicQuery 14 | { 15 | public class QueryHandler : QueryHandlerBase, IQueryHandler 16 | { 17 | public QueryHandler(IEnumerable queryableInterceptorProviders) : base(queryableInterceptorProviders) 18 | { 19 | } 20 | 21 | protected virtual IQueryExecutionResult FinalExecute() 22 | { 23 | CommonBeforeExecute(); 24 | return HasGrouping ? ExecuteGrouping() : ExecuteNoGrouping(); 25 | } 26 | 27 | protected virtual IQueryExecutionResult ExecuteGrouping() 28 | { 29 | var result = new QueryExecutionGroupResult(); 30 | 31 | // preserve queryable. 32 | var queryableAfterFilters = CurrentQueryable; 33 | 34 | result.TotalRecords = queryableAfterFilters.LongCount(); 35 | CalculatePageCount(result); 36 | 37 | // intercept groups in advance to avoid doing it more than once :) 38 | var finalGroups = Criteria.Groups.Select(g => InterceptGroup(g)).ToList(); 39 | 40 | // get the aggregates. 41 | var aggregateResults = FetchAggregates(finalGroups); 42 | 43 | // sorting. 44 | finalGroups.ReversedForEach(fg => Criteria.Sorts.Insert(0, new Sort(fg.Path, fg.Ascending))); 45 | 46 | // apply sorting and paging. 47 | ApplySorting(); 48 | ApplyPaging(); 49 | 50 | if (Options.GroupByInMemory) 51 | CurrentQueryable = CurrentQueryable.ToObjectList().Cast().AsQueryable(); 52 | 53 | CurrentQueryable = CurrentQueryable.GroupBy(QueryableUnderlyingType, gb => 54 | { 55 | gb.NullChecking(Options.GroupByInMemory ? Options.GroupByInMemoryNullCheck : false); 56 | finalGroups.ForEach((fg, index) => gb.Path(fg.Path, $"Key_{index}")); 57 | }); 58 | 59 | CurrentQueryable = CurrentQueryable.Select(sb => 60 | { 61 | sb.NullChecking(Options.GroupByInMemory ? Options.GroupByInMemoryNullCheck : false); 62 | finalGroups.ForEach((fg, index) => sb.Key($"Key_{index}", $"Key_{index}")); 63 | sb.ToList("Records"); 64 | }); 65 | 66 | 67 | // loop through the grouped records. 68 | var groupRecords = CurrentQueryable.ToDynamicClassList(); 69 | 70 | // now join them into logical collections 71 | var lastLists = new List<(List source, IGroupQueryResult group)>(); 72 | result.Groups = RecursiveRegroup(groupRecords, aggregateResults, Criteria.Groups.First(), lastLists); 73 | 74 | // intercept grouped by. 75 | QueryInterceptToGrouped(lastLists).Wait(); 76 | 77 | result.Aggregates = CalculateTotalAggregate(queryableAfterFilters); 78 | return result; 79 | } 80 | protected virtual List CalculateTotalAggregate(IQueryable queryableAfterFilters) 81 | { 82 | if (!Criteria.Aggregates.Any()) 83 | return null; 84 | 85 | IQueryable selectExpression = CreateTotalAggregateSelectExpression(queryableAfterFilters); 86 | var aggregateResult = selectExpression.ToDynamicClassList().FirstOrDefault(); 87 | return MaterializeCalculateTotalAggregateResult(aggregateResult); 88 | } 89 | 90 | protected virtual List> FetchAggregates(List finalGroups) 91 | { 92 | if (!Criteria.Aggregates.Any()) 93 | return null; 94 | 95 | var previousGroups = new List(); 96 | var ret = finalGroups.Select(fg => 97 | { 98 | IQueryable selectExpression = CreateFetchAggregateSelectExpression(fg, previousGroups); 99 | var aggregateResult = selectExpression.ToDynamicClassList(); 100 | previousGroups.Add(fg); 101 | return aggregateResult; 102 | }).ToList(); 103 | return ret; 104 | } 105 | 106 | protected virtual IQueryExecutionResult ExecuteNoGrouping() 107 | { 108 | var result = new QueryExecutionResult(); 109 | 110 | // after filter queryable 111 | var afterFilterQueryable = CurrentQueryable; 112 | 113 | // total records. 114 | result.TotalRecords = afterFilterQueryable.LongCount(); 115 | CalculatePageCount(result); 116 | 117 | // sorts and paging. 118 | ApplySorting(); 119 | ApplyPaging(); 120 | 121 | // data. 122 | var entities = ((IQueryable)CurrentQueryable).ToList(); 123 | var records = InterceptConvertTo(entities).Result; 124 | result.Data = records; 125 | 126 | // aggregates. 127 | result.Aggregates = CalculateTotalAggregate(afterFilterQueryable); 128 | 129 | return result; 130 | } 131 | 132 | public IQueryExecutionResult Execute(IQueryable queryable, IQueryCriteria criteria) 133 | { 134 | Reset(queryable, criteria, new QueryExecutionOptions()); 135 | return FinalExecute(); 136 | } 137 | 138 | public IQueryExecutionResult Execute(IQueryable queryable, IQueryCriteria criteria) 139 | { 140 | Reset(queryable, criteria, new QueryExecutionOptions()); 141 | return FinalExecute(); 142 | } 143 | 144 | public IQueryExecutionResult Execute(IQueryable queryable, IQueryCriteria criteria, IQueryExecutionOptions options) 145 | { 146 | Reset(queryable, criteria, options); 147 | return FinalExecute(); 148 | } 149 | 150 | public IQueryExecutionResult Execute(IQueryable queryable, IQueryCriteria criteria, IQueryExecutionOptions options) 151 | { 152 | Reset(queryable, criteria, options); 153 | return FinalExecute(); 154 | } 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicQuery/QueryHandlerAsync.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Reflection; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using PoweredSoft.Data; 7 | using PoweredSoft.Data.Core; 8 | using PoweredSoft.DynamicLinq; 9 | using PoweredSoft.DynamicQuery.Core; 10 | 11 | namespace PoweredSoft.DynamicQuery 12 | { 13 | 14 | public class QueryHandlerAsync : QueryHandlerBase, IQueryHandlerAsync 15 | { 16 | public IAsyncQueryableService AsyncQueryableService { get; } 17 | 18 | public QueryHandlerAsync(IAsyncQueryableService asyncQueryableService, IEnumerable queryInterceptorProviders) : base(queryInterceptorProviders) 19 | { 20 | AsyncQueryableService = asyncQueryableService; 21 | } 22 | 23 | protected virtual Task> FinalExecuteAsync(CancellationToken cancellationToken = default) 24 | { 25 | CommonBeforeExecute(); 26 | return HasGrouping ? ExecuteAsyncGrouping(cancellationToken) : ExecuteAsyncNoGrouping(cancellationToken); 27 | } 28 | 29 | protected virtual async Task> ExecuteAsyncGrouping(CancellationToken cancellationToken) 30 | { 31 | var result = new QueryExecutionGroupResult(); 32 | 33 | // preserve queryable. 34 | var queryableAfterFilters = CurrentQueryable; 35 | 36 | // async. 37 | result.TotalRecords = await this.AsyncQueryableService.LongCountAsync((IQueryable)queryableAfterFilters, cancellationToken); 38 | CalculatePageCount(result); 39 | 40 | // intercept groups in advance to avoid doing it more than once :) 41 | var finalGroups = Criteria.Groups.Select(g => InterceptGroup(g)).ToList(); 42 | 43 | // get the aggregates. 44 | var aggregateResults = await FetchAggregatesAsync(finalGroups, cancellationToken); 45 | 46 | // sorting. 47 | finalGroups.ReversedForEach(fg => Criteria.Sorts.Insert(0, new Sort(fg.Path, fg.Ascending))); 48 | 49 | // apply sorting and paging. 50 | ApplySorting(); 51 | ApplyPaging(); 52 | 53 | List groupRecords; 54 | 55 | if (Options.GroupByInMemory) 56 | { 57 | CurrentQueryable = CurrentQueryable.ToObjectList().Cast().AsQueryable(); 58 | 59 | // create group & select expression. 60 | CurrentQueryable = CurrentQueryable.GroupBy(QueryableUnderlyingType, gb => 61 | { 62 | gb.NullChecking(Options.GroupByInMemory ? Options.GroupByInMemoryNullCheck : false); 63 | finalGroups.ForEach((fg, index) => gb.Path(fg.Path, $"Key_{index}")); 64 | }); 65 | CurrentQueryable = CurrentQueryable.Select(sb => 66 | { 67 | sb.NullChecking(Options.GroupByInMemory ? Options.GroupByInMemoryNullCheck : false); 68 | finalGroups.ForEach((fg, index) => sb.Key($"Key_{index}", $"Key_{index}")); 69 | sb.ToList("Records"); 70 | }); 71 | 72 | // loop through the grouped records. 73 | groupRecords = CurrentQueryable.Cast().ToList(); 74 | } 75 | else 76 | { 77 | // create group & select expression. 78 | CurrentQueryable = CurrentQueryable.GroupBy(QueryableUnderlyingType, gb => 79 | { 80 | finalGroups.ForEach((fg, index) => gb.Path(fg.Path, $"Key_{index}")); 81 | }); 82 | CurrentQueryable = CurrentQueryable.Select(sb => 83 | { 84 | finalGroups.ForEach((fg, index) => sb.Key($"Key_{index}", $"Key_{index}")); 85 | sb.ToList("Records"); 86 | }); 87 | 88 | // loop through the grouped records. 89 | groupRecords = await AsyncQueryableService.ToListAsync(CurrentQueryable.Cast(), cancellationToken); 90 | } 91 | 92 | // now join them into logical collections 93 | var lastLists = new List<(List entities, IGroupQueryResult group)>(); 94 | result.Groups = RecursiveRegroup(groupRecords, aggregateResults, Criteria.Groups.First(), lastLists); 95 | 96 | // converted to grouped by. 97 | await QueryInterceptToGrouped(lastLists); 98 | 99 | result.Aggregates = await CalculateTotalAggregateAsync(queryableAfterFilters, cancellationToken); 100 | return result; 101 | } 102 | 103 | protected async Task> ExecuteAsyncNoGrouping(CancellationToken cancellationToken) 104 | { 105 | var result = new QueryExecutionResult(); 106 | 107 | // after filter queryable 108 | IQueryable afterFilterQueryable = (IQueryable)CurrentQueryable; 109 | 110 | // total records. 111 | result.TotalRecords = await AsyncQueryableService.LongCountAsync(afterFilterQueryable, cancellationToken); 112 | CalculatePageCount(result); 113 | 114 | // sorts and paging. 115 | ApplySorting(); 116 | ApplyPaging(); 117 | 118 | // data. 119 | var entities = await AsyncQueryableService.ToListAsync(((IQueryable)CurrentQueryable), cancellationToken); 120 | var records = await InterceptConvertTo(entities); 121 | result.Data = records; 122 | 123 | // aggregates. 124 | result.Aggregates = await CalculateTotalAggregateAsync(afterFilterQueryable, cancellationToken); 125 | return result; 126 | } 127 | 128 | protected virtual async Task> CalculateTotalAggregateAsync(IQueryable queryableAfterFilters, CancellationToken cancellationToken) 129 | { 130 | if (!Criteria.Aggregates.Any()) 131 | return null; 132 | 133 | IQueryable selectExpression = CreateTotalAggregateSelectExpression(queryableAfterFilters); 134 | var aggregateResult = await AsyncQueryableService.FirstOrDefaultAsync(selectExpression.Cast()); 135 | return MaterializeCalculateTotalAggregateResult(aggregateResult); 136 | } 137 | 138 | protected async virtual Task>> FetchAggregatesAsync(List finalGroups, CancellationToken cancellationToken) 139 | { 140 | if (!Criteria.Aggregates.Any()) 141 | return null; 142 | 143 | var previousGroups = new List(); 144 | 145 | var whenAllResult = await Task.WhenAll(finalGroups.Select(fg => 146 | { 147 | IQueryable selectExpression = CreateFetchAggregateSelectExpression(fg, previousGroups); 148 | var selectExpressionCasted = selectExpression.Cast(); 149 | var aggregateResult = AsyncQueryableService.ToListAsync(selectExpressionCasted, cancellationToken); 150 | previousGroups.Add(fg); 151 | return aggregateResult; 152 | })); 153 | 154 | var finalResult = whenAllResult.ToList(); 155 | return finalResult; 156 | } 157 | 158 | public Task> ExecuteAsync(IQueryable queryable, IQueryCriteria criteria, CancellationToken cancellationToken = default) 159 | { 160 | Reset(queryable, criteria, new QueryExecutionOptions()); 161 | return FinalExecuteAsync(cancellationToken); 162 | } 163 | 164 | public Task> ExecuteAsync(IQueryable queryable, IQueryCriteria criteria, CancellationToken cancellationToken = default) 165 | { 166 | Reset(queryable, criteria, new QueryExecutionOptions()); 167 | return FinalExecuteAsync(cancellationToken); 168 | } 169 | 170 | public Task> ExecuteAsync(IQueryable queryable, IQueryCriteria criteria, IQueryExecutionOptions options, CancellationToken cancellationToken = default) 171 | { 172 | Reset(queryable, criteria, options); 173 | return FinalExecuteAsync(cancellationToken); 174 | } 175 | 176 | public Task> ExecuteAsync(IQueryable queryable, IQueryCriteria criteria, IQueryExecutionOptions options, CancellationToken cancellationToken = default) 177 | { 178 | Reset(queryable, criteria, options); 179 | return FinalExecuteAsync(cancellationToken); 180 | } 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicQuery/QueryHandlerBase.cs: -------------------------------------------------------------------------------- 1 | using PoweredSoft.DynamicQuery.Core; 2 | using PoweredSoft.DynamicLinq; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Diagnostics; 6 | using System.Linq; 7 | using System.Net; 8 | using System.Reflection; 9 | using System.Runtime.CompilerServices; 10 | using System.Text; 11 | using System.Threading.Tasks; 12 | using PoweredSoft.DynamicLinq.Fluent; 13 | using PoweredSoft.DynamicQuery.Extensions; 14 | 15 | namespace PoweredSoft.DynamicQuery 16 | { 17 | public abstract class QueryHandlerBase : IInterceptableQueryHandler 18 | { 19 | private readonly IEnumerable queryableInterceptorProviders; 20 | 21 | protected List AddedInterceptors { get; } = new List(); 22 | protected IQueryCriteria Criteria { get; set; } 23 | protected IQueryable QueryableAtStart { get; private set; } 24 | protected IQueryable CurrentQueryable { get; set; } 25 | protected IQueryExecutionOptions Options { get; private set; } 26 | 27 | protected Type QueryableUnderlyingType => QueryableAtStart.ElementType; 28 | protected bool HasGrouping => Criteria.Groups?.Any() == true; 29 | protected bool HasPaging => Criteria.PageSize.HasValue && Criteria.PageSize > 0; 30 | 31 | protected IReadOnlyList Interceptors { get; set; } 32 | 33 | protected virtual void ResetInterceptors(IQueryCriteria criteria, IQueryable queryable) 34 | { 35 | Interceptors = ResolveInterceptors(criteria, queryable); 36 | } 37 | 38 | public QueryHandlerBase(IEnumerable queryableInterceptorProviders) 39 | { 40 | this.queryableInterceptorProviders = queryableInterceptorProviders; 41 | } 42 | 43 | protected virtual void Reset(IQueryable queryable, IQueryCriteria criteria, IQueryExecutionOptions options) 44 | { 45 | ResetInterceptors(criteria, queryable); 46 | Criteria = criteria ?? throw new ArgumentNullException("criteria"); 47 | QueryableAtStart = queryable ?? throw new ArgumentNullException("queryable"); 48 | CurrentQueryable = QueryableAtStart; 49 | Options = options; 50 | } 51 | 52 | protected virtual void CommonBeforeExecute() 53 | { 54 | ApplyQueryExecutionOptionIncerceptors(); 55 | ApplyIncludeStrategyInterceptors(); 56 | ApplyBeforeFilterInterceptors(); 57 | ApplyFilters(); 58 | } 59 | 60 | protected virtual void ApplyQueryExecutionOptionIncerceptors() 61 | { 62 | Options = Interceptors 63 | .Where(t => t is IQueryExecutionOptionsInterceptor) 64 | .Cast() 65 | .Aggregate(Options, (prev, curr) => curr.InterceptQueryExecutionOptions(CurrentQueryable, prev)); 66 | } 67 | 68 | public virtual void AddInterceptor(IQueryInterceptor interceptor) 69 | { 70 | if (interceptor == null) throw new ArgumentNullException("interceptor"); 71 | 72 | if (!AddedInterceptors.Contains(interceptor)) 73 | AddedInterceptors.Add(interceptor); 74 | } 75 | 76 | protected virtual IGroup InterceptGroup(IGroup group) 77 | { 78 | var ret = Interceptors 79 | .Where(t => t is IGroupInterceptor) 80 | .Cast() 81 | .Aggregate(group, (prev, inter) => inter.InterceptGroup(prev)); 82 | 83 | return ret; 84 | } 85 | 86 | 87 | protected virtual void ApplyPaging() 88 | { 89 | if (!HasPaging) 90 | return; 91 | 92 | var q = (IQueryable) CurrentQueryable; 93 | var skip = ((Criteria.Page ?? 1) - 1) * Criteria.PageSize.Value; 94 | CurrentQueryable = q.Skip(skip).Take(Criteria.PageSize.Value); 95 | } 96 | 97 | protected virtual void ApplySorting() 98 | { 99 | if (Criteria.Sorts?.Any() != true) 100 | { 101 | ApplyNoSortInterceptor(); 102 | return; 103 | } 104 | 105 | bool isAppending = false; 106 | Criteria.Sorts.ForEach(sort => 107 | { 108 | var transformedSort = InterceptSort(sort); 109 | if (transformedSort.Count == 0) 110 | return; 111 | 112 | transformedSort.ForEach(ts => 113 | { 114 | CurrentQueryable = CurrentQueryable.OrderBy(ts.Path, ts.Ascending == false ? QueryOrderByDirection.Descending : QueryOrderByDirection.Ascending, isAppending); 115 | isAppending = true; 116 | }); 117 | }); 118 | } 119 | 120 | protected DynamicClass FindMatchingAggregateResult(List> aggregateResults, List groups, List> groupResults) 121 | { 122 | var groupIndex = groupResults.Count - 1; 123 | var aggregateLevel = aggregateResults[groupIndex]; 124 | 125 | var ret = aggregateLevel.FirstOrDefault(al => 126 | { 127 | for (var i = 0; i < groups.Count; i++) 128 | { 129 | if (!al.GetDynamicPropertyValue($"Key_{i}").Equals(groupResults[i].GroupValue)) 130 | return false; 131 | } 132 | 133 | return true; 134 | }); 135 | return ret; 136 | } 137 | 138 | protected virtual IQueryable CreateFetchAggregateSelectExpression(IGroup finalGroup, List previousGroups) 139 | { 140 | var groupExpression = CurrentQueryable.GroupBy(QueryableUnderlyingType, gb => 141 | { 142 | var groupKeyIndex = -1; 143 | previousGroups.ForEach(pg => gb.Path(pg.Path, $"Key_{++groupKeyIndex}")); 144 | gb.Path(finalGroup.Path, $"Key_{++groupKeyIndex}"); 145 | }); 146 | 147 | var selectExpression = groupExpression.Select(sb => 148 | { 149 | var groupKeyIndex = -1; 150 | previousGroups.ForEach(pg => sb.Key($"Key_{++groupKeyIndex}", $"Key_{groupKeyIndex}")); 151 | sb.Key($"Key_{++groupKeyIndex}", $"Key_{groupKeyIndex}"); 152 | Criteria.Aggregates.ForEach((a, ai) => 153 | { 154 | var fa = InterceptAggregate(a); 155 | var selectType = ResolveSelectFrom(fa.Type); 156 | sb.Aggregate(fa.Path, selectType, $"Agg_{ai}"); 157 | }); 158 | }); 159 | return selectExpression; 160 | } 161 | 162 | protected virtual List MaterializeCalculateTotalAggregateResult(DynamicClass aggregateResult) 163 | { 164 | var ret = new List(); 165 | Criteria.Aggregates.ForEach((a, index) => 166 | { 167 | ret.Add(new AggregateResult() 168 | { 169 | Path = a.Path, 170 | Type = a.Type, 171 | Value = aggregateResult?.GetDynamicPropertyValue($"Agg_{index}") 172 | }); 173 | }); 174 | return ret; 175 | } 176 | 177 | protected virtual IQueryable CreateTotalAggregateSelectExpression(IQueryable queryableAfterFilters) 178 | { 179 | var groupExpression = queryableAfterFilters.EmptyGroupBy(QueryableUnderlyingType); 180 | var selectExpression = groupExpression.Select(sb => 181 | { 182 | Criteria.Aggregates.ForEach((a, index) => 183 | { 184 | var fa = InterceptAggregate(a); 185 | var selectType = ResolveSelectFrom(fa.Type); 186 | sb.Aggregate(fa.Path, selectType, $"Agg_{index}"); 187 | }); 188 | }); 189 | return selectExpression; 190 | } 191 | 192 | protected virtual void CalculatePageCount(IQueryExecutionResultPaging result) 193 | { 194 | if (!HasPaging) 195 | return; 196 | 197 | if (result.TotalRecords < Criteria.PageSize) 198 | result.NumberOfPages = 1; 199 | else 200 | result.NumberOfPages = result.TotalRecords / Criteria.PageSize + (result.TotalRecords % Criteria.PageSize != 0 ? 1 : 0); 201 | } 202 | 203 | protected virtual IAggregate InterceptAggregate(IAggregate aggregate) 204 | { 205 | var ret = Interceptors 206 | .Where(t => t is IAggregateInterceptor) 207 | .Cast() 208 | .Aggregate(aggregate, (prev, inter) => inter.InterceptAggregate(prev)); 209 | return ret; 210 | } 211 | 212 | protected virtual async Task> InterceptConvertTo(List entities) 213 | { 214 | await AfterEntityReadInterceptors(entities); 215 | 216 | var ret = new List(); 217 | for (var i = 0; i < entities.Count; i++) 218 | ret.Add(InterceptConvertToObject(entities[i])); 219 | 220 | var pairs = entities.Select((t, index) => Tuple.Create(t, ret[index])).ToList(); 221 | await AfterReadInterceptors(pairs); 222 | 223 | return ret; 224 | } 225 | 226 | protected virtual async Task AfterEntityReadInterceptors(List entities) 227 | { 228 | Interceptors 229 | .Where(t => t is IAfterReadEntityInterceptor) 230 | .Cast>() 231 | .ToList() 232 | .ForEach(t => t.AfterReadEntity(entities)); 233 | 234 | var asyncInterceptors = Interceptors.Where(t => t is IAfterReadEntityInterceptorAsync).Cast>(); 235 | foreach (var interceptor in asyncInterceptors) 236 | await interceptor.AfterReadEntityAsync(entities); 237 | } 238 | 239 | protected virtual async Task AfterReadInterceptors(List> pairs) 240 | { 241 | var objPair = pairs.Select(t => Tuple.Create(t.Item1, (object)t.Item2)).ToList(); 242 | 243 | Interceptors 244 | .Where(t => t is IAfterReadInterceptor) 245 | .Cast>() 246 | .ToList() 247 | .ForEach(t => t.AfterRead(objPair)); 248 | 249 | var asyncInterceptors = Interceptors.Where(t => t is IAfterReadInterceptorAsync).Cast>(); 250 | foreach (var interceptor in asyncInterceptors) 251 | await interceptor.AfterReadAsync(objPair); 252 | 253 | var asyncInterceptors2 = Interceptors.Where(t => t is IAfterReadInterceptorAsync).Cast>(); 254 | foreach (var interceptor in asyncInterceptors2) 255 | await interceptor.AfterReadAsync(pairs); 256 | } 257 | 258 | protected virtual TRecord InterceptConvertToObject(object o) 259 | { 260 | o = Interceptors 261 | .Where(t => t is IQueryConvertInterceptor) 262 | .Cast() 263 | .Aggregate(o, (prev, interceptor) => interceptor.InterceptResultTo(prev)); 264 | 265 | o = Interceptors 266 | .Where(t => t is IQueryConvertInterceptor) 267 | .Cast>() 268 | .Aggregate(o, (prev, interceptor) => 269 | { 270 | if (prev is TSource) 271 | return interceptor.InterceptResultTo((TSource)prev); 272 | 273 | return o; 274 | }); 275 | 276 | o = Interceptors 277 | .Where(t => t is IQueryConvertInterceptor) 278 | .Cast>() 279 | .Aggregate(o, (prev, interceptor) => 280 | { 281 | if (prev is TSource) 282 | return interceptor.InterceptResultTo((TSource)prev); 283 | 284 | return o; 285 | }); 286 | 287 | return (TRecord)o; 288 | } 289 | 290 | protected virtual List InterceptSort(ISort sort) 291 | { 292 | var original = new List() 293 | { 294 | sort 295 | }; 296 | 297 | var ret = Interceptors 298 | .Where(t => t is ISortInterceptor) 299 | .Cast() 300 | .Aggregate(original as IEnumerable, (prev, inter) => inter.InterceptSort(prev)) 301 | .Distinct(); 302 | 303 | return ret.ToList(); 304 | } 305 | 306 | protected virtual void ApplyNoSortInterceptor() 307 | { 308 | CurrentQueryable = Interceptors.Where(t => t is INoSortInterceptor) 309 | .Cast() 310 | .Aggregate(CurrentQueryable, (prev, interceptor) => interceptor.InterceptNoSort(Criteria, prev)); 311 | 312 | CurrentQueryable = Interceptors.Where(t => t is INoSortInterceptor) 313 | .Cast>() 314 | .Aggregate((IQueryable)CurrentQueryable, (prev, interceptor) => interceptor.InterceptNoSort(Criteria, prev)); 315 | } 316 | 317 | 318 | protected virtual SelectTypes? ResolveSelectFromOrDefault(AggregateType aggregateType) => aggregateType.SelectType(); 319 | protected virtual ConditionOperators? ResolveConditionOperatorFromOrDefault(FilterType filterType) => filterType.ConditionOperator(); 320 | 321 | protected virtual ConditionOperators ResolveConditionOperatorFrom(FilterType filterType) 322 | { 323 | var ret = ResolveConditionOperatorFromOrDefault(filterType); 324 | if (ret == null) 325 | throw new NotSupportedException($"{filterType} is not supported"); 326 | 327 | return ret.Value; 328 | } 329 | 330 | protected virtual SelectTypes ResolveSelectFrom(AggregateType aggregateType) 331 | { 332 | var ret = ResolveSelectFromOrDefault(aggregateType); 333 | if (ret == null) 334 | throw new NotSupportedException($"{aggregateType} is not supported"); 335 | 336 | return ret.Value; 337 | } 338 | 339 | protected virtual void ApplyFilters() 340 | { 341 | if (true != Criteria.Filters?.Any()) 342 | return; 343 | 344 | CurrentQueryable = CurrentQueryable.Query(whereBuilder => 345 | { 346 | Criteria.Filters.ForEach(filter => ApplyFilter(whereBuilder, filter)); 347 | }); 348 | } 349 | 350 | protected virtual void ApplyFilter(WhereBuilder whereBuilder, IFilter filter) 351 | { 352 | var transformedFilter = InterceptFilter(filter); 353 | if (transformedFilter is ISimpleFilter) 354 | ApplySimpleFilter(whereBuilder, transformedFilter as ISimpleFilter); 355 | else if (transformedFilter is ICompositeFilter) 356 | AppleCompositeFilter(whereBuilder, transformedFilter as ICompositeFilter); 357 | else 358 | throw new NotSupportedException(); 359 | } 360 | 361 | protected virtual void AppleCompositeFilter(WhereBuilder whereBuilder, ICompositeFilter filter) 362 | { 363 | whereBuilder.SubQuery(subWhereBuilder => filter.Filters.ForEach(subFilter => ApplyFilter(subWhereBuilder, subFilter)), filter.And == true); 364 | } 365 | 366 | protected virtual void ApplySimpleFilter(WhereBuilder whereBuilder, ISimpleFilter filter) 367 | { 368 | var resolvedConditionOperator = ResolveConditionOperatorFrom(filter.Type); 369 | 370 | if (filter.CaseInsensitive == true) 371 | { 372 | filter.Path += ".ToLower()"; 373 | filter.Value = $"{filter.Value}"?.ToLower(); 374 | } 375 | 376 | whereBuilder.Compare(filter.Path, resolvedConditionOperator, filter.Value, and: filter.And == true, negate: filter.Not == true); 377 | } 378 | 379 | protected virtual IFilter InterceptFilter(IFilter filter) 380 | { 381 | var ret = Interceptors.Where(t => t is IFilterInterceptor) 382 | .Cast() 383 | .Aggregate(filter, (previousFilter, interceptor) => interceptor.InterceptFilter(previousFilter)); 384 | 385 | return ret; 386 | } 387 | 388 | protected virtual void ApplyIncludeStrategyInterceptors() 389 | { 390 | CurrentQueryable = Interceptors 391 | .Where(t => t is IIncludeStrategyInterceptor) 392 | .Cast() 393 | .Aggregate(CurrentQueryable, (prev, interceptor) => interceptor.InterceptIncludeStrategy(Criteria, prev)); 394 | 395 | CurrentQueryable = Interceptors 396 | .Where(t => t is IIncludeStrategyInterceptor) 397 | .Cast>() 398 | .Aggregate((IQueryable)CurrentQueryable, (prev, interceptor) => interceptor.InterceptIncludeStrategy(Criteria, prev)); 399 | } 400 | 401 | protected virtual void ApplyBeforeFilterInterceptors() 402 | { 403 | CurrentQueryable = Interceptors 404 | .Where(t => t is IBeforeQueryFilterInterceptor) 405 | .Cast() 406 | .Aggregate(CurrentQueryable, (prev, interceptor) => interceptor.InterceptBeforeFiltering(Criteria, prev)); 407 | 408 | CurrentQueryable = Interceptors 409 | .Where(t => t is IBeforeQueryFilterInterceptor) 410 | .Cast>() 411 | .Aggregate((IQueryable)CurrentQueryable, (prev, interceptor) => interceptor.InterceptBeforeFiltering(Criteria, prev)); 412 | } 413 | 414 | protected virtual List> RecursiveRegroup(List groupRecords, List> aggregateResults, IGroup group, List<(List entities, IGroupQueryResult group)> lastLists, List> parentGroupResults = null) 415 | { 416 | var groupIndex = Criteria.Groups.IndexOf(group); 417 | var isLast = Criteria.Groups.Last() == group; 418 | var groups = Criteria.Groups.Take(groupIndex + 1).ToList(); 419 | var hasAggregates = Criteria.Aggregates.Any(); 420 | 421 | var ret = groupRecords 422 | .GroupBy(gk => gk.GetDynamicPropertyValue($"Key_{groupIndex}")) 423 | .Select(t => 424 | { 425 | var groupResult = new GroupQueryResult(); 426 | 427 | // group results. 428 | 429 | List> groupResults; 430 | if (parentGroupResults == null) 431 | groupResults = new List> { groupResult }; 432 | else 433 | groupResults = parentGroupResults.Union(new[] { groupResult }).ToList(); 434 | 435 | groupResult.GroupPath = group.Path; 436 | groupResult.GroupValue = t.Key; 437 | 438 | if (hasAggregates) 439 | { 440 | var matchingAggregate = FindMatchingAggregateResult(aggregateResults, groups, groupResults); 441 | if (matchingAggregate == null) 442 | Debugger.Break(); 443 | 444 | groupResult.Aggregates = new List(); 445 | Criteria.Aggregates.ForEach((a, ai) => 446 | { 447 | var key = $"Agg_{ai}"; 448 | var aggregateResult = new AggregateResult 449 | { 450 | Path = a.Path, 451 | Type = a.Type, 452 | Value = matchingAggregate.GetDynamicPropertyValue(key) 453 | }; 454 | groupResult.Aggregates.Add(aggregateResult); 455 | }); 456 | } 457 | 458 | if (isLast) 459 | { 460 | var entities = t.SelectMany(t2 => t2.GetDynamicPropertyValue>("Records")).ToList(); 461 | var tuple = (entities, groupResult); 462 | groupResult.Data = new List(); 463 | lastLists.Add(tuple); 464 | } 465 | else 466 | { 467 | groupResult.SubGroups = RecursiveRegroup(t.ToList(), aggregateResults, Criteria.Groups[groupIndex + 1], lastLists, groupResults); 468 | } 469 | 470 | return groupResult; 471 | }) 472 | .AsEnumerable>() 473 | .ToList(); 474 | 475 | return ret; 476 | } 477 | 478 | protected virtual async Task QueryInterceptToGrouped(List<(List entities, IGroupQueryResult group)> lists) 479 | { 480 | var entities = lists.SelectMany(t => t.entities).ToList(); 481 | await AfterEntityReadInterceptors(entities); 482 | 483 | var pairs = new List>(); 484 | 485 | lists.ForEach(innerList => 486 | { 487 | for (var i = 0; i < innerList.entities.Count; i++) 488 | { 489 | var entity = innerList.entities[i]; 490 | var convertedObject = InterceptConvertToObject(entity); 491 | innerList.group.Data.Add(convertedObject); 492 | pairs.Add(Tuple.Create(entity, convertedObject)); 493 | } 494 | }); 495 | 496 | await AfterReadInterceptors(pairs); 497 | } 498 | 499 | public IReadOnlyList ResolveInterceptors(IQueryCriteria criteria, IQueryable queryable) 500 | { 501 | var providedInterceptors = queryableInterceptorProviders.SelectMany(t => t.GetInterceptors(criteria, queryable)).ToList(); 502 | var final = providedInterceptors 503 | .Concat(AddedInterceptors) 504 | .Distinct(new QueryInterceptorEqualityComparer()) 505 | .ToList(); 506 | 507 | return final; 508 | } 509 | } 510 | } 511 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicQuery/QueryInterceptorEqualityComparer.cs: -------------------------------------------------------------------------------- 1 | using PoweredSoft.DynamicQuery.Core; 2 | using System.Collections.Generic; 3 | 4 | namespace PoweredSoft.DynamicQuery 5 | { 6 | public class QueryInterceptorEqualityComparer : IEqualityComparer 7 | { 8 | public bool Equals(IQueryInterceptor x, IQueryInterceptor y) 9 | { 10 | return x.GetType() == y.GetType(); 11 | } 12 | 13 | public int GetHashCode(IQueryInterceptor obj) 14 | { 15 | return obj.GetType().GetHashCode(); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicQuery/QueryResult.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using PoweredSoft.DynamicQuery.Core; 6 | 7 | namespace PoweredSoft.DynamicQuery 8 | { 9 | /// 10 | /// Represents an aggregate result. 11 | /// 12 | public class AggregateResult : IAggregateResult 13 | { 14 | public string Path { get; set; } 15 | public AggregateType Type { get; set; } 16 | public object Value { get; set; } 17 | 18 | public bool ShouldSerializePath() => !string.IsNullOrWhiteSpace(Path); 19 | } 20 | 21 | // part of a result. 22 | public abstract class QueryResult : IQueryResult 23 | { 24 | public List Aggregates { get; set; } 25 | public List Data { get; set; } 26 | 27 | public bool ShouldSerializeAggregates() => Aggregates != null; 28 | 29 | public bool ShouldSerializeData() => Data != null; 30 | } 31 | 32 | // just result 33 | public class QueryExecutionResult : QueryResult, IQueryExecutionResult 34 | { 35 | public long TotalRecords { get; set; } 36 | public long? NumberOfPages { get; set; } 37 | 38 | public bool ShouldSerializeNumberOfPages() => NumberOfPages.HasValue; 39 | } 40 | 41 | public class QueryExecutionGroupResult : QueryExecutionResult, IQueryExecutionGroupResult 42 | { 43 | public List> Groups { get; set; } 44 | } 45 | 46 | // group result. 47 | public class GroupQueryResult : QueryResult, IGroupQueryResult 48 | { 49 | public string GroupPath { get; set; } 50 | public object GroupValue { get; set; } 51 | public bool HasSubGroups => SubGroups != null && SubGroups.Count >= 1; 52 | public List> SubGroups { get; set; } 53 | 54 | public bool ShouldSerializeSubGroups() => HasSubGroups; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicQuery/ServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using Microsoft.Extensions.DependencyInjection.Extensions; 3 | using PoweredSoft.DynamicQuery.Core; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Text; 7 | 8 | namespace PoweredSoft.DynamicQuery 9 | { 10 | public static class ServiceCollectionExtensions 11 | { 12 | public static IServiceCollection AddPoweredSoftDynamicQuery(this IServiceCollection services) 13 | { 14 | services.TryAddTransient(); 15 | services.TryAddTransient(); 16 | services.TryAddTransient(); 17 | services.TryAddTransient(); 18 | services.TryAddTransient(); 19 | services.TryAddTransient(); 20 | services.TryAddTransient(); 21 | services.TryAddTransient(); 22 | return services; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicQuery/Sort.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using PoweredSoft.DynamicQuery.Core; 5 | 6 | namespace PoweredSoft.DynamicQuery 7 | { 8 | public class Sort : ISort 9 | { 10 | public string Path { get; set; } 11 | public bool? Ascending { get; set; } 12 | 13 | public Sort() 14 | { 15 | 16 | } 17 | 18 | public Sort(string path) 19 | { 20 | Path = path; 21 | } 22 | 23 | public Sort(string path, bool? ascending) 24 | { 25 | Path = path; 26 | Ascending = ascending; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Dynamic Query 2 | 3 | It's a library that allows you to easily query a queryable using a criteria object. 4 | 5 | It also offers, to intercept the query using **IQueryInterceptor** implementations. 6 | 7 | ## Breaking Changes 8 | 9 | If you are moving up from v1, the breaking changes details are lower. 10 | 11 | 12 | ## Getting Started 13 | 14 | > Install nuget package to your awesome project. 15 | 16 | Full Version | NuGet | NuGet Install 17 | ------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------: 18 | PoweredSoft.DynamicQuery | [![NuGet](https://img.shields.io/nuget/v/PoweredSoft.DynamicQuery.svg?style=flat-square&label=nuget)](https://www.nuget.org/packages/PoweredSoft.DynamicQuery/) | ```PM> Install-Package PoweredSoft.DynamicQuery``` 19 | PoweredSoft.DynamicQuery.Core | [![NuGet](https://img.shields.io/nuget/v/PoweredSoft.DynamicQuery.Core.svg?style=flat-square&label=nuget)](https://www.nuget.org/packages/PoweredSoft.DynamicQuery.Core/) | ```PM> Install-Package PoweredSoft.DynamicQuery.Core``` 20 | PoweredSoft.DynamicQuery.AspNetCore | [![NuGet](https://img.shields.io/nuget/v/PoweredSoft.DynamicQuery.AspNetCore.svg?style=flat-square&label=nuget)](https://www.nuget.org/packages/PoweredSoft.DynamicQuery.AspNetCore/) | ```PM> Install-Package PoweredSoft.DynamicQuery.AspNetCore``` 21 | PoweredSoft.DynamicQuery.AspNetCore.NewtonsoftJson | [![NuGet](https://img.shields.io/nuget/v/PoweredSoft.DynamicQuery.AspNetCore.NewtonsoftJson.svg?style=flat-square&label=nuget)](https://www.nuget.org/packages/PoweredSoft.DynamicQuery.AspNetCore.NewtonsoftJson/) | ```PM> Install-Package PoweredSoft.DynamicQuery.AspNetCore.NewtonsoftJson``` 22 | 23 | ## Using in ASP.NET Core 24 | 25 | The package Asp.net core of dynamic query will help you start to use Dynamic Query faster in your web project. 26 | 27 | *Now Supports .NET 5* 28 | 29 | .NET Version | Compatible Branch | 30 | -------------|-----------------------| 31 | .NET 5.0 | 3.0.0+ | 32 | .NET 3.1.x | 3.0.1 or 2.1 | 33 | .NET 2.1.x | 2.0 | 34 | 35 | 36 | ### New in Since 2.1.3 37 | 38 | You may now add a ```IQueryInterceptorProvider``` to return interceptors on top of being able to use AddInterceptor. 39 | 40 | ```csharp 41 | public interface IQueryInterceptorProvider 42 | { 43 | IEnumerable GetInterceptors(IQueryCriteria queryCriteria, IQueryable queryable); 44 | } 45 | ``` 46 | 47 | ### How to configure during startup (NET Core 3) 48 | 49 | ```csharp 50 | using PoweredSoft.DynamicQuery.AspNetCore.NewtonsoftJson; 51 | 52 | public class Startup 53 | { 54 | public void ConfigureServices(IServiceCollection services) 55 | { 56 | services 57 | .AddMvc() 58 | .AddPoweredSoftJsonNetDynamicQuery(); 59 | } 60 | } 61 | 62 | ``` 63 | 64 | > How to use in a controller 65 | 66 | ```csharp 67 | 68 | [HttpGet] 69 | public IQueryExecutionResult Get( 70 | [FromServices]YourContext context, 71 | [FromServices]IQueryHandler handler, 72 | [FromServices]IQueryCriteria criteria, 73 | int? page = null, 74 | int? pageSize = null) 75 | { 76 | criteria.Page = page; 77 | criteria.PageSize = pageSize; 78 | IQueryable query = context.Somethings; 79 | var result = handler.Execute(query, criteria); 80 | return result; 81 | } 82 | 83 | [HttpPost] 84 | public IQueryExecutionResult Read( 85 | [FromServices]YourContext context, 86 | [FromServices]IQueryHandler handler, 87 | [FromBody]IQueryCriteria criteria) 88 | { 89 | IQueryable query = context.Somethings; 90 | var result = handler.Execute(query, criteria); 91 | return result; 92 | } 93 | ``` 94 | 95 | > New support for async 96 | 97 | ```csharp 98 | [HttpPost] 99 | public async Task> Read( 100 | [FromServices]YourContext context, 101 | [FromServices]IQueryHandlerAsync handler, 102 | [FromBody]IQueryCriteria criteria) 103 | { 104 | IQueryable query = context.Somethings; 105 | var result = await handler.ExecuteAsync(query, criteria); 106 | return result; 107 | } 108 | ``` 109 | 110 | ### Sample Web Project - ASP.NET CORE + EF Core 111 | 112 | Visit: https://github.com/PoweredSoft/DynamicQueryAspNetCoreSample 113 | 114 | ### Breaking Changes if you are migrating from 1.x 115 | 116 | Response interface, is now generic ```IQueryResult``` which impacts the way to execute the handler. 117 | 118 | #### Grouping results 119 | 120 | Since the results are now generic, it's no longer a List in the response so that changes the result if grouping is requested. 121 | 122 | You have now a property Groups, and HasSubGroups, and SubGroups. 123 | 124 | #### QueryConvertTo Interceptor 125 | 126 | If you are using IQueryConvertTo interceptors, it's new that you must specify the type you are converting to 127 | Ex: 128 | ```csharp 129 | IQueryable query = context.Somethings; 130 | var result = handler.Execute(query, criteria); 131 | ``` 132 | 133 | ## Criteria 134 | 135 | Criteria must implement the following interfaces 136 | 137 | Object | Interface | Implementation | Example | Description 138 | -----------------|--------------------------------------------------------------------------|-------------------------------------------------------------------------------|----------------------------------------------------------------------|-------------------------------------------- 139 | Query Criteria | [interface](../master/PoweredSoft.DynamicQuery.Core/IQueryCriteria.cs) | [default implementation](../master/PoweredSoft.DynamicQuery/QueryCriteria.cs) | [test](../master/PoweredSoft.DynamicQuery.Test/CriteriaTests.cs#L13) | Wraps the query parameters 140 | Paging | [interface](../master/PoweredSoft.DynamicQuery.Core/IQueryCriteria.cs) | [default implementation](../master/PoweredSoft.DynamicQuery/QueryCriteria.cs) | [test](../master/PoweredSoft.DynamicQuery.Test/CriteriaTests.cs#L29) | Paging support 141 | Filter | [interface](../master/PoweredSoft.DynamicQuery.Core/IFilter.cs) | [default implementation](../master/PoweredSoft.DynamicQuery/Filter.cs) | [test](../master/PoweredSoft.DynamicQuery.Test/FilterTests.cs#L22) | Represent a filter to be executed 142 | Simple Filter | [interface](../master/PoweredSoft.DynamicQuery.Core/ISimpleFilter.cs) | [default implementation](../master/PoweredSoft.DynamicQuery/Filter.cs) | [test](../master/PoweredSoft.DynamicQuery.Test/FilterTests.cs#L40) | Represent a simple filter to be executed 143 | Composite Filter | [interface](../master/PoweredSoft.DynamicQuery.Core/ICompositeFilter.cs) | [default implementation](../master/PoweredSoft.DynamicQuery/Filter.cs) | [test](../master/PoweredSoft.DynamicQuery.Test/FilterTests.cs#L68) | Represent a composite filter to be executed 144 | Sort | [interface](../master/PoweredSoft.DynamicQuery.Core/ISort.cs) | [default implementation](../master/PoweredSoft.DynamicQuery/Sort.cs) | [test](../master/PoweredSoft.DynamicQuery.Test/SortTests.cs#L15) | Represent a sort to be executed 145 | Group | [interface](../master/PoweredSoft.DynamicQuery.Core/IGroup.cs) | [default implementation](../master/PoweredSoft.DynamicQuery/Group.cs) | [test](../master/PoweredSoft.DynamicQuery.Test/GroupTests.cs) | Represent a group to be executed 146 | Aggregate | [interface](../master/PoweredSoft.DynamicQuery.Core/IAggregate.cs) | [default implementation](../master/PoweredSoft.DynamicQuery/Aggregate.cs) | [test](../master/PoweredSoft.DynamicQuery.Test/AggregateTests.cs) | Represent an aggregate to be executed 147 | 148 | ### Sample 149 | 150 | ```csharp 151 | var criteria = new QueryCriteria 152 | { 153 | Page = 1, 154 | PageSize = 12, 155 | Filters = new List 156 | { 157 | new SimpleFilter { Path = "FirstName", Type = FilterType.Equal, Value = "John" } 158 | } 159 | }; 160 | 161 | var queryHandler = new QueryHandler(); 162 | IQueryExecutionResult result = queryHandler.Execute(someQueryable, criteria); 163 | ``` 164 | 165 | ## Query Result 166 | 167 | Here is the interfaces that represent the result of query handling execution. 168 | 169 | > Changed in 2.x 170 | 171 | ```csharp 172 | public interface IAggregateResult 173 | { 174 | string Path { get; set; } 175 | AggregateType Type { get; set; } 176 | object Value { get; set; } 177 | } 178 | 179 | public interface IQueryResult 180 | { 181 | List Aggregates { get; } 182 | List Data { get; } 183 | } 184 | 185 | public interface IGroupQueryResult : IQueryResult 186 | { 187 | string GroupPath { get; set; } 188 | object GroupValue { get; set; } 189 | bool HasSubGroups { get; } 190 | List> SubGroups { get; set; } 191 | } 192 | 193 | public interface IQueryExecutionResultPaging 194 | { 195 | long TotalRecords { get; set; } 196 | long? NumberOfPages { get; set; } 197 | } 198 | 199 | public interface IQueryExecutionResult : IQueryResult, IQueryExecutionResultPaging 200 | { 201 | 202 | } 203 | 204 | public interface IQueryExecutionGroupResult : IQueryExecutionResult 205 | { 206 | List> Groups { get; set; } 207 | } 208 | ``` 209 | 210 | 211 | ## Interceptors 212 | 213 | Interceptors are meant to add hooks at certain part of the query handling to allow alteration of the criterias or the queryable it self. 214 | 215 | The following is documented in the order of what they are called by the **default** query handler implementation. 216 | 217 | > Before the expression is being built 218 | 219 | Interceptor | Interface | Example | Description 220 | ---------------------------------------|--------------------------------------------------------------------------------------------|-------------------------------------------------------------|------------------------------------------------------------------------------------------------------------ 221 | IIncludeStrategyInterceptor | [interface](../master/PoweredSoft.DynamicQuery.Core/IIncludeStrategyInterceptor.cs) | [test](../master/PoweredSoft.DynamicQuery.Test/IncludeStrategyTests.cs) | This is to allow you to specify include paths for the queryable 222 | IIncludeStrategyInterceptor<T> | [interface](../master/PoweredSoft.DynamicQuery.Core/IIncludeStrategyInterceptor.cs) | [test](../master/PoweredSoft.DynamicQuery.Test/IncludeStrategyTests.cs#L65) | This is to allow you to specify include paths for the queryable 223 | IBeforeQueryFilterInterceptor | [interface](../master/PoweredSoft.DynamicQuery.Core/IBeforeQueryFilterInterceptor.cs) | [test](../master/PoweredSoft.DynamicQuery.Test/BeforeFilterTests.cs) | Before adding the filters to the expression 224 | IBeforeQueryFilterInterceptor<T> | [interface](../master/PoweredSoft.DynamicQuery.Core/IBeforeQueryFilterInterceptor.cs) | [test](../master/PoweredSoft.DynamicQuery.Test/BeforeFilterTests.cs#L64) | Before adding the filters to the expression 225 | INoSortInterceptor | [interface](../master/PoweredSoft.DynamicQuery.Core/INoSortInterceptor.cs) | [test](../master/PoweredSoft.DynamicQuery.Test/NoSortTests.cs) | This is called to allow you to specify an OrderBy in case none is specified, to avoid paging crash with EF6 226 | INoSortInterceptor<T> | [interface](../master/PoweredSoft.DynamicQuery.Core/INoSortInterceptor.cs) | [test](../master/PoweredSoft.DynamicQuery.Test/NoSortTests.cs#L65) | This is called to allow you to specify an OrderBy in case none is specified, to avoid paging crash with EF6 227 | 228 | > After/During expression building before query execution 229 | 230 | Interceptor | Interface | Example | Description 231 | ----------------------|------------------------------------------------------------------------------------|-------------------------------------------------------------|--------------------------------------------------------------------------------------------------- 232 | IFilterInterceptor | [interface](../master/PoweredSoft.DynamicQuery.Core/IFilterInterceptor.cs) | [test](../master/PoweredSoft.DynamicQuery.Test/FilterInterceptorTests.cs) | This interceptor allows you to change the behavior of a IFilter being applied to the queryable 233 | ISortInterceptor | [interface](../master/PoweredSoft.DynamicQuery.Core/ISortInterceptor.cs) | [test](../master/PoweredSoft.DynamicQuery.Test/SortInterceptorTests.cs) | This interceptor allows you to change the behavior of a ISort being applied to the queryable 234 | IGroupInterceptor | [interface](../master/PoweredSoft.DynamicQuery.Core/IGroupInterceptor.cs) | [test](../master/PoweredSoft.DynamicQuery.Test/GroupInterceptorTests.cs) | This interceptor allows you to change the behavior of a IGroup being applied to the queryable 235 | IAggregateInterceptor | [interface](../master/PoweredSoft.DynamicQuery.Core/IAggregateInterceptor.cs) | [test](../master/PoweredSoft.DynamicQuery.Test/AggregateInterceptorTests.cs) | This interceptor allows you to change the behavior of a IAggregate being applied to the queryable 236 | 237 | > Post Query execution 238 | 239 | Interceptor | Interface | Example | Description 240 | ----------------------------------|---------------------------------------------------------------------------------------|-------------------------------------------------------------|------------------------------------------------------------------------------------------------ 241 | IQueryConvertInterceptor | [interface](../master/PoweredSoft.DynamicQuery.Core/IQueryConvertInterceptor.cs) | [test](../master/PoweredSoft.DynamicQuery.Test/ConvertibleInterceptorTests.cs) | This interceptor allows you to replace the object that is being returned by the query, by another object instance 242 | IQueryConvertInterceptor<T, T2> | [interface](../master/PoweredSoft.DynamicQuery.Core/IQueryConvertInterceptor.cs) | [test](../master/PoweredSoft.DynamicQuery.Test/ConvertibleInterceptorTests.cs#L72) | This interceptor allows you to replace the object that is being returned by the query, by another object instance **(restricts the source)** 243 | IQueryConvertInterceptor<T, T2> | [interface](../master/PoweredSoft.DynamicQuery.Core/IQueryConvertInterceptor.cs) | [test](../master/PoweredSoft.DynamicQuery.Test/ConvertibleInterceptorTests.cs#L101) | This interceptor allows you to replace the object that is being returned by the query, by another object instance **(restricts the source & output)** 244 | --------------------------------------------------------------------------------