├── .gitattributes ├── .gitignore ├── LICENSE ├── PoweredSoft.DynamicLinq.Dal ├── App.config ├── BlogContext.cs ├── BlogCoreContext.cs ├── Configurations │ └── Configurations.cs ├── Pocos │ ├── Author.cs │ ├── Comment.cs │ ├── CommentLike.cs │ ├── Post.cs │ ├── Uniqe.cs │ └── Website.cs ├── PoweredSoft.DynamicLinq.Dal.csproj └── packages.config ├── PoweredSoft.DynamicLinq.EntityFramework ├── App.config ├── Extensions │ └── DbContextExtensions.cs └── PoweredSoft.DynamicLinq.EntityFramework.csproj ├── PoweredSoft.DynamicLinq.EntityFrameworkCore ├── Extensions │ └── DbContextExtensions.cs └── PoweredSoft.DynamicLinq.EntityFrameworkCore.csproj ├── PoweredSoft.DynamicLinq.Test ├── AnonymousTypeTest.cs ├── App.config ├── ComplexQueriesTests.cs ├── ConstantTests.cs ├── CountTests.cs ├── EntityFrameworkCoreTests.cs ├── EntityFrameworkTests.cs ├── GetCoreContext.cs ├── GroupingTests.cs ├── Helpers │ └── QueryableAssert.cs ├── HelpersTests.cs ├── InTests.cs ├── PoweredSoft.DynamicLinq.Test.csproj ├── SelectTests.cs ├── ShortcutTests.cs ├── SimpleQueriesTest.cs ├── StringComparision.cs └── TestData.cs ├── PoweredSoft.DynamicLinq.sln ├── PoweredSoft.DynamicLinq ├── Constants.cs ├── DynamicType │ ├── DynamicClass.cs │ └── DynamicClassFactory.cs ├── Extensions │ ├── EnumerableExtensions.cs │ └── QueryableExtensions.cs ├── Fluent │ ├── Group │ │ └── GroupBuilder.cs │ ├── OrderBy │ │ ├── OrderByBuilder.cs │ │ └── OrderByPart.cs │ ├── Select │ │ └── SelectBuilder.cs │ └── Where │ │ ├── WhereBuilder.cs │ │ ├── WhereBuilder.shortcuts.cs │ │ └── WhereBuilderCondition.cs ├── Helpers │ ├── AnonymousTypes.cs │ ├── QueryableHelpers.cs │ └── TypeHelpers.cs ├── Interfaces │ └── IQueryBuilder.cs ├── Parser │ ├── ExpressionParser.cs │ ├── ExpressionParserPiece.cs │ ├── ExpressionParserPieceGroup.cs │ └── ParserExtensions.cs ├── PoweredSoft.DynamicLinq.csproj ├── Properties │ └── PublishProfiles │ │ └── FolderProfile.pubxml └── Resolver │ └── PathExpressionResolver.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 -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 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.DynamicLinq.Dal/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicLinq.Dal/BlogContext.cs: -------------------------------------------------------------------------------- 1 | using PoweredSoft.DynamicLinq.Dal.Configurations; 2 | using PoweredSoft.DynamicLinq.Dal.Pocos; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Data.Entity; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace PoweredSoft.DynamicLinq.Dal 11 | { 12 | 13 | public class BlogContext : DbContext 14 | { 15 | public DbSet Authors { get; set; } 16 | public DbSet Comments { get; set; } 17 | public DbSet Posts { get; set; } 18 | 19 | static BlogContext() 20 | { 21 | Database.SetInitializer(new DropCreateDatabaseAlways()); 22 | } 23 | 24 | public BlogContext() 25 | { 26 | 27 | } 28 | 29 | public BlogContext(string connectionString) : base(connectionString) 30 | { 31 | 32 | } 33 | 34 | protected override void OnModelCreating(DbModelBuilder modelBuilder) 35 | { 36 | base.OnModelCreating(modelBuilder); 37 | modelBuilder.Configurations.Add(new AuthorConfiguration()); 38 | modelBuilder.Configurations.Add(new CommentConfiguration()); 39 | modelBuilder.Configurations.Add(new PostConfiguration()); 40 | modelBuilder.Configurations.Add(new WebsiteConfiguration()); 41 | modelBuilder.Configurations.Add(new CommentLikeConfiguration()); 42 | modelBuilder.Configurations.Add(new UniqueConfiguration()); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicLinq.Dal/BlogCoreContext.cs: -------------------------------------------------------------------------------- 1 | using PoweredSoft.DynamicLinq.Dal.Pocos; 2 | using Microsoft.EntityFrameworkCore; 3 | using JetBrains.Annotations; 4 | using System.Diagnostics.CodeAnalysis; 5 | 6 | namespace PoweredSoft.DynamicLinq.Dal 7 | { 8 | public class BlogCoreContext : DbContext 9 | { 10 | public BlogCoreContext([NotNull] DbContextOptions options) : base(options) 11 | { 12 | } 13 | 14 | protected BlogCoreContext() 15 | { 16 | } 17 | 18 | public DbSet Authors { get; set; } 19 | public DbSet Comments { get; set; } 20 | public DbSet Posts { get; set; } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicLinq.Dal/Configurations/Configurations.cs: -------------------------------------------------------------------------------- 1 | using PoweredSoft.DynamicLinq.Dal.Pocos; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.ComponentModel.DataAnnotations.Schema; 5 | using System.Data.Entity.ModelConfiguration; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace PoweredSoft.DynamicLinq.Dal.Configurations 11 | { 12 | public class UniqueConfiguration : EntityTypeConfiguration 13 | { 14 | public UniqueConfiguration() : this("dbo") 15 | { 16 | 17 | } 18 | 19 | public UniqueConfiguration(string schema) 20 | { 21 | ToTable("Unique", schema); 22 | HasKey(t => t.Id); 23 | Property(t => t.Id).HasColumnName("Id").HasColumnType("bigint").IsRequired().HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity); 24 | Property(t => t.RowNumber).HasColumnType("uniqueidentifier").IsRequired(); 25 | Property(t => t.OtherNullableGuid).HasColumnType("uniqueidentifier"); 26 | } 27 | } 28 | 29 | public class AuthorConfiguration : EntityTypeConfiguration 30 | { 31 | public AuthorConfiguration() : this("dbo") 32 | { 33 | 34 | } 35 | 36 | public AuthorConfiguration(string schema) 37 | { 38 | ToTable("Author", schema); 39 | HasKey(t => t.Id); 40 | Property(t => t.Id).HasColumnName("Id").HasColumnType("bigint").IsRequired().HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity); 41 | Property(t => t.FirstName).HasColumnType("nvarchar").HasMaxLength(50).IsRequired(); 42 | Property(t => t.LastName).HasColumnType("nvarchar").HasMaxLength(50).IsRequired(); 43 | 44 | HasOptional(t => t.Website).WithMany(t => t.Authors).HasForeignKey(t => t.WebsiteId).WillCascadeOnDelete(false); 45 | } 46 | } 47 | 48 | public class PostConfiguration : EntityTypeConfiguration 49 | { 50 | public PostConfiguration() : this("dbo") 51 | { 52 | 53 | } 54 | 55 | public PostConfiguration(string schema) 56 | { 57 | ToTable("Post", schema); 58 | HasKey(t => t.Id); 59 | Property(t => t.Id).HasColumnName("Id").HasColumnType("bigint").IsRequired().HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity); 60 | Property(t => t.AuthorId).HasColumnName("AuthorId").HasColumnType("bigint").IsRequired(); 61 | Property(t => t.Title).HasColumnName("Title").HasColumnType("nvarchar").HasMaxLength(100).IsRequired(); 62 | Property(t => t.Content).HasColumnName("Content").HasColumnType("nvarchar(max)").IsRequired(); 63 | Property(t => t.CreateTime).HasColumnName("CreateTime").HasColumnType("datetimeoffset").IsRequired(); 64 | Property(t => t.PublishTime).HasColumnName("PublishTime").HasColumnType("datetimeoffset").IsOptional(); 65 | 66 | HasRequired(t => t.Author).WithMany(t => t.Posts).HasForeignKey(t => t.AuthorId).WillCascadeOnDelete(false); 67 | } 68 | } 69 | 70 | public class CommentConfiguration : EntityTypeConfiguration 71 | { 72 | public CommentConfiguration() : this("dbo") 73 | { 74 | 75 | } 76 | 77 | public CommentConfiguration(string schema) 78 | { 79 | ToTable("Comment", schema); 80 | HasKey(t => t.Id); 81 | Property(t => t.Id).HasColumnName("Id").HasColumnType("bigint").IsRequired().HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity); 82 | Property(t => t.PostId).HasColumnName("PostId").HasColumnType("bigint").IsRequired(); 83 | Property(t => t.DisplayName).HasColumnName("DisplayName").HasColumnType("nvarchar").HasMaxLength(100).IsRequired(); 84 | Property(t => t.Email).HasColumnName("Email").HasColumnType("nvarchar").IsOptional(); 85 | Property(t => t.CommentText).HasColumnName("CommentText").HasColumnType("nvarchar").HasMaxLength(255).IsOptional(); 86 | 87 | HasRequired(t => t.Post).WithMany(t => t.Comments).HasForeignKey(t => t.PostId).WillCascadeOnDelete(false); 88 | } 89 | } 90 | 91 | public class CommentLikeConfiguration : EntityTypeConfiguration 92 | { 93 | public CommentLikeConfiguration() : this("dbo") 94 | { 95 | } 96 | 97 | public CommentLikeConfiguration(string schema) 98 | { 99 | ToTable("CommentLike", schema); 100 | HasKey(t => t.Id); 101 | Property(t => t.Id).HasColumnName("Id").HasColumnType("bigint").IsRequired().HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity); 102 | Property(t => t.CommentId).HasColumnName("CommentId").HasColumnType("bigint").IsRequired(); 103 | Property(t => t.CreateTime).HasColumnName("CreateTime").HasColumnType("datetimeoffset").IsRequired(); 104 | 105 | HasRequired(t => t.Comment).WithMany(t => t.CommentLikes).HasForeignKey(t => t.CommentId).WillCascadeOnDelete(false); 106 | } 107 | } 108 | 109 | public class WebsiteConfiguration : EntityTypeConfiguration 110 | { 111 | public WebsiteConfiguration() : this("dbo") 112 | { 113 | 114 | } 115 | 116 | public WebsiteConfiguration(string schema) 117 | { 118 | ToTable("Website", schema); 119 | HasKey(t => t.Id); 120 | Property(t => t.Id).HasColumnName("Id").HasColumnType("bigint").IsRequired().HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity); 121 | Property(t => t.Title).HasColumnName("Title").HasColumnType("nvarchar").HasMaxLength(100).IsRequired(); 122 | Property(t => t.Url).HasColumnName("Url").HasColumnType("nvarchar").HasMaxLength(255).IsRequired(); 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicLinq.Dal/Pocos/Author.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace PoweredSoft.DynamicLinq.Dal.Pocos 8 | { 9 | public class Author 10 | { 11 | public long Id { get; set; } 12 | public string FirstName { get; set; } 13 | public string LastName { get; set; } 14 | public long? WebsiteId { get; set; } 15 | 16 | public virtual Website Website { get; set; } 17 | public ICollection Posts { get; set; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicLinq.Dal/Pocos/Comment.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace PoweredSoft.DynamicLinq.Dal.Pocos 8 | { 9 | public class Comment 10 | { 11 | public long Id { get; set; } 12 | public long PostId { get; set; } 13 | public string DisplayName { get; set; } 14 | public string Email { get; set; } 15 | public string CommentText { get; set; } 16 | public Post Post { get; set; } 17 | public ICollection CommentLikes { get; set; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicLinq.Dal/Pocos/CommentLike.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace PoweredSoft.DynamicLinq.Dal.Pocos 8 | { 9 | public class CommentLike 10 | { 11 | public long Id { get; set; } 12 | public long CommentId { get; set; } 13 | public DateTimeOffset CreateTime { get; set; } 14 | 15 | public virtual Comment Comment { get; set; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicLinq.Dal/Pocos/Post.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace PoweredSoft.DynamicLinq.Dal.Pocos 8 | { 9 | public class Post 10 | { 11 | public long Id { get; set; } 12 | public long AuthorId { get; set; } 13 | public string Title { get; set; } 14 | public string Content { get; set; } 15 | public DateTimeOffset CreateTime { get; set; } 16 | public DateTimeOffset? PublishTime { get; set; } 17 | 18 | public Author Author { get; set; } 19 | public virtual ICollection Comments { get; set; } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicLinq.Dal/Pocos/Uniqe.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace PoweredSoft.DynamicLinq.Dal.Pocos 8 | { 9 | public class Unique 10 | { 11 | public long Id { get; set; } 12 | public Guid RowNumber { get; set; } 13 | public Guid? OtherNullableGuid { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicLinq.Dal/Pocos/Website.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace PoweredSoft.DynamicLinq.Dal.Pocos 8 | { 9 | public class Website 10 | { 11 | public long Id { get; set; } 12 | public string Url { get; set; } 13 | public string Title { get; set; } 14 | 15 | public ICollection Authors { get; set; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicLinq.Dal/PoweredSoft.DynamicLinq.Dal.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicLinq.Dal/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicLinq.EntityFramework/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 |
6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicLinq.EntityFramework/Extensions/DbContextExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Data.Entity; 4 | using System.Linq; 5 | using System.Reflection; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using PoweredSoft.DynamicLinq.Fluent; 9 | 10 | namespace PoweredSoft.DynamicLinq.EntityFramework 11 | { 12 | public static class DbContextExtensions 13 | { 14 | public static IQueryable Query(this DbContext context, Type pocoType, Action callback) 15 | { 16 | var set = context.Set(pocoType); 17 | var queryable = set.AsQueryable(); 18 | var builder = new WhereBuilder(queryable); 19 | callback(builder); 20 | var result = builder.Build(); 21 | return result; 22 | } 23 | 24 | public static IQueryable Query(this DbContext context, Action callback) 25 | where T : class 26 | { 27 | var query = context.Set().AsQueryable(); 28 | query = query.Query(callback); 29 | return query; 30 | } 31 | 32 | public static IQueryable Where(this DbContext context, Type pocoType, Action callback) 33 | => context.Query(pocoType, callback); 34 | 35 | public static IQueryable Where(this DbContext context, Action callback) 36 | where T : class => context.Query(callback); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicLinq.EntityFramework/PoweredSoft.DynamicLinq.EntityFramework.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.1;net461 5 | True 6 | David Lebee 7 | Powered Software Inc. 8 | Entity Framework extensions for Dynamic Linq of PoweredSoft 9 | github 10 | https://github.com/PoweredSoft/DynamicLinq 11 | entity framework ef dynamic linq 12 | https://github.com/PoweredSoft/DynamicLinq 13 | https://secure.gravatar.com/avatar/4e32f73820c16718909a06c2927f1f8b?s=512&amp;r=g&amp;d=retro 14 | 1.1.0$(VersionSuffix) 15 | EF Integration of DynamicLinq 16 | PoweredSoft.DynamicLinq.EntityFramework 17 | Added Negate & NotContains 18 | False 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicLinq.EntityFrameworkCore/Extensions/DbContextExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using Microsoft.EntityFrameworkCore; 8 | using PoweredSoft.DynamicLinq.Fluent; 9 | 10 | namespace PoweredSoft.DynamicLinq.EntityFrameworkCore 11 | { 12 | public static class DbContextExtensions 13 | { 14 | private static MethodInfo SetMethod = typeof(DbContext).GetMethod(nameof(DbContext.Set), BindingFlags.Public | BindingFlags.Instance); 15 | 16 | public static IQueryable Query(this DbContext context, Type pocoType, Action callback) 17 | { 18 | var set = SetMethod.MakeGenericMethod(pocoType).Invoke(context, new object[] { }); 19 | var queryable = set as IQueryable; 20 | var builder = new WhereBuilder(queryable); 21 | callback(builder); 22 | var result = builder.Build(); 23 | return result; 24 | } 25 | 26 | public static IQueryable Query(this DbContext context, Action callback) 27 | where T : class 28 | { 29 | var query = context.Set().AsQueryable(); 30 | query = query.Query(callback); 31 | return query; 32 | } 33 | 34 | public static IQueryable Where(this DbContext context, Type pocoType, Action callback) 35 | => context.Query(pocoType, callback); 36 | 37 | public static IQueryable Where(this DbContext context, Action callback) 38 | where T : class => context.Query(callback); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicLinq.EntityFrameworkCore/PoweredSoft.DynamicLinq.EntityFrameworkCore.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.1 5 | True 6 | David Lebee 7 | Powered Software Inc. 8 | Entity Framework extensions for Dynamic Linq of PoweredSoft 9 | github 10 | https://github.com/PoweredSoft/DynamicLinq 11 | entity framework core efcore ef dynamic linq 12 | https://github.com/PoweredSoft/DynamicLinq 13 | https://secure.gravatar.com/avatar/4e32f73820c16718909a06c2927f1f8b?s=512&amp;r=g&amp;d=retro 14 | 1.1.0$(VersionSuffix) 15 | EF Integration of DynamicLinq 16 | PoweredSoft.DynamicLinq.EntityFrameworkCore 17 | First Release of efcore extensions 18 | False 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicLinq.Test/AnonymousTypeTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | using PoweredSoft.DynamicLinq.DynamicType; 5 | 6 | namespace PoweredSoft.DynamicLinq.Test 7 | { 8 | [TestClass] 9 | public class AnonymousTypeTest 10 | { 11 | [TestMethod] 12 | public void TestEqual() 13 | { 14 | var properties = new List<(Type type, string propertyName)>() 15 | { 16 | (typeof(int), "Id"), 17 | (typeof(string), "FirstName"), 18 | (typeof(string), "LastName") 19 | }; 20 | 21 | var type = DynamicClassFactory.CreateType(properties); 22 | var instanceA = Activator.CreateInstance(type) as DynamicClass; 23 | var instanceB = Activator.CreateInstance(type) as DynamicClass; 24 | 25 | instanceA.SetDynamicPropertyValue("Id", 1); 26 | instanceA.SetDynamicPropertyValue("FirstName", "David"); 27 | instanceA.SetDynamicPropertyValue("LastName", "Lebee"); 28 | 29 | instanceB.SetDynamicPropertyValue("Id", 1); 30 | instanceB.SetDynamicPropertyValue("FirstName", "David"); 31 | instanceB.SetDynamicPropertyValue("LastName", "Lebee"); 32 | 33 | Assert.IsTrue(instanceA.Equals(instanceB)); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicLinq.Test/App.config: -------------------------------------------------------------------------------- 1 |  2 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicLinq.Test/ComplexQueriesTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Data.SqlClient; 5 | using System.Linq; 6 | using System.Linq.Expressions; 7 | using System.Reflection; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | using PoweredSoft.DynamicLinq; 11 | using PoweredSoft.DynamicLinq.Dal.Pocos; 12 | using PoweredSoft.DynamicLinq.Fluent; 13 | 14 | namespace PoweredSoft.DynamicLinq.Test 15 | { 16 | [TestClass] 17 | public class ComplexQueriesTests 18 | { 19 | [TestMethod] 20 | public void ComplexQueryBuilder() 21 | { 22 | // subject. 23 | var posts = new List() 24 | { 25 | new Post { Id = 1, AuthorId = 1, Title = "Hello 1", Content = "World" }, 26 | new Post { Id = 2, AuthorId = 1, Title = "Hello 2", Content = "World" }, 27 | new Post { Id = 3, AuthorId = 2, Title = "Hello 3", Content = "World" }, 28 | }; 29 | 30 | // the query. 31 | var query = posts.AsQueryable(); 32 | 33 | query = query.Query(q => 34 | { 35 | q.Compare("AuthorId", ConditionOperators.Equal, 1); 36 | q.And(sq => 37 | { 38 | sq.Compare("Content", ConditionOperators.Equal, "World"); 39 | sq.Or("Title", ConditionOperators.Contains, 3); 40 | }); 41 | }); 42 | 43 | Assert.AreEqual(2, query.Count()); 44 | } 45 | 46 | [TestMethod] 47 | public void UsingQueryBuilder() 48 | { 49 | // subject. 50 | var posts = new List() 51 | { 52 | new Post { Id = 1, AuthorId = 1, Title = "Hello 1", Content = "World" }, 53 | new Post { Id = 2, AuthorId = 1, Title = "Hello 2", Content = "World" }, 54 | new Post { Id = 3, AuthorId = 2, Title = "Hello 3", Content = "World" }, 55 | }; 56 | 57 | // the query. 58 | var query = posts.AsQueryable(); 59 | var queryBuilder = new WhereBuilder(query); 60 | 61 | queryBuilder.Compare("AuthorId", ConditionOperators.Equal, 1); 62 | queryBuilder.And(subQuery => 63 | { 64 | subQuery.Compare("Content", ConditionOperators.Equal, "World"); 65 | subQuery.Or("Title", ConditionOperators.Contains, 3); 66 | }); 67 | 68 | query = (IQueryable)queryBuilder.Build(); 69 | Assert.AreEqual(2, query.Count()); 70 | } 71 | 72 | [TestMethod] 73 | public void TestingSort() 74 | { 75 | // subject. 76 | var posts = new List() 77 | { 78 | new Post { Id = 1, AuthorId = 1, Title = "Hello 1", Content = "World" }, 79 | new Post { Id = 2, AuthorId = 1, Title = "Hello 2", Content = "World" }, 80 | new Post { Id = 3, AuthorId = 2, Title = "Hello 3", Content = "World" }, 81 | }; 82 | 83 | // the query. 84 | var query = posts.AsQueryable(); 85 | var queryBuilder = new OrderByBuilder(query); 86 | 87 | // add some sorting. 88 | queryBuilder 89 | .OrderByDescending("AuthorId") 90 | .ThenBy("Id"); 91 | 92 | query = queryBuilder.Build().Cast(); 93 | 94 | var first = query.First(); 95 | var second = query.Skip(1).First(); 96 | 97 | Assert.IsTrue(first.Id == 3); 98 | Assert.IsTrue(second.Id == 1); 99 | } 100 | 101 | [TestMethod] 102 | public void TestAutomaticNullChecking() 103 | { 104 | var authors = TestData.Authors; 105 | 106 | // the query. 107 | var query = authors.AsQueryable(); 108 | 109 | query = query.Query(qb => 110 | { 111 | qb.NullChecking(); 112 | qb.And("Posts.Comments.Email", ConditionOperators.Equal, "john.doe@me.com", collectionHandling: QueryCollectionHandling.Any); 113 | }); 114 | 115 | var query2 = query.Where(qb => 116 | { 117 | qb.NullChecking(); 118 | qb.And("Posts.Comments.Email", ConditionOperators.Equal, "john.doe@me.com", collectionHandling: QueryCollectionHandling.Any); 119 | }); 120 | 121 | Assert.AreEqual(1, query.Count()); 122 | Assert.AreEqual(1, query2.Count()); 123 | } 124 | 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicLinq.Test/ConstantTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Microsoft.VisualStudio.TestTools.UnitTesting; 5 | using PoweredSoft.DynamicLinq.Dal.Pocos; 6 | 7 | namespace PoweredSoft.DynamicLinq.Test 8 | { 9 | internal class ConstantTestClass 10 | { 11 | public int Id { get; set; } 12 | public int? ForeignKey { get; set; } 13 | public string Text { get; set; } 14 | } 15 | 16 | [TestClass] 17 | public class ConstantTests 18 | { 19 | internal List Posts { get; set; } = new List() 20 | { 21 | new ConstantTestClass { Id = 1, ForeignKey = null, Text = "Hello" }, 22 | new ConstantTestClass { Id = 2, ForeignKey = 1, Text = "Hello 2" }, 23 | new ConstantTestClass { Id = 3, ForeignKey = 2, Text = "Hello 3" }, 24 | new ConstantTestClass { Id = 4, ForeignKey = null, Text = "Hello 4" }, 25 | }; 26 | 27 | [TestMethod] 28 | public void LeaveAsIs() 29 | { 30 | try 31 | { 32 | Posts 33 | .AsQueryable() 34 | .Query(t => t.Equal("ForeignKey", 1, QueryConvertStrategy.LeaveAsIs)); 35 | 36 | Assert.Fail("Should have thrown an exception"); 37 | } 38 | catch 39 | { 40 | } 41 | 42 | Assert.IsTrue(Posts.AsQueryable().Query(t => t.Equal("Id", 1, QueryConvertStrategy.LeaveAsIs)).Any()); 43 | } 44 | 45 | [TestMethod] 46 | public void TestGuid() 47 | { 48 | var randomGuidStr = Guid.NewGuid().ToString(); 49 | TestData.Uniques.AsQueryable().Query(t => t.Equal("RowNumber", randomGuidStr)); 50 | TestData.Uniques.AsQueryable().Query(t => t.Equal("OtherNullableGuid", randomGuidStr)); 51 | } 52 | 53 | [TestMethod] 54 | public void SpecifyType() 55 | { 56 | Assert.IsTrue(Posts.AsQueryable().Query(t => t.Equal("ForeignKey", 1, QueryConvertStrategy.SpecifyType)).Any()); 57 | Assert.IsTrue(Posts.AsQueryable().Query(t => t.Equal("Id", 1, QueryConvertStrategy.SpecifyType)).Any()); 58 | 59 | try 60 | { 61 | Posts.AsQueryable().Query(t => t.Equal("Id", "1", QueryConvertStrategy.SpecifyType)); 62 | Assert.Fail("Should have thrown an exception"); 63 | } 64 | catch 65 | { 66 | 67 | } 68 | } 69 | 70 | [TestMethod] 71 | public void ConvertConstantToComparedPropertyOrField() 72 | { 73 | Assert.IsTrue(Posts.AsQueryable().Query(t => t.Equal("ForeignKey", 1, QueryConvertStrategy.ConvertConstantToComparedPropertyOrField)).Any()); 74 | Assert.IsTrue(Posts.AsQueryable().Query(t => t.Equal("ForeignKey", "1", QueryConvertStrategy.ConvertConstantToComparedPropertyOrField)).Any()); 75 | Assert.IsTrue(Posts.AsQueryable().Query(t => t.Equal("Id", 1, QueryConvertStrategy.ConvertConstantToComparedPropertyOrField)).Any()); 76 | Assert.IsTrue(Posts.AsQueryable().Query(t => t.Equal("Id", "1", QueryConvertStrategy.ConvertConstantToComparedPropertyOrField)).Any()); 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicLinq.Test/CountTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | 5 | namespace PoweredSoft.DynamicLinq.Test 6 | { 7 | [TestClass] 8 | public class CountTests 9 | { 10 | [TestMethod] 11 | public void Count() 12 | { 13 | var normalSyntax = TestData.Sales.Count(); 14 | var nonGenericQueryable = (IQueryable)TestData.Sales.AsQueryable(); 15 | var dynamicSyntax = nonGenericQueryable.Count(); 16 | Assert.AreEqual(normalSyntax, dynamicSyntax); 17 | } 18 | 19 | [TestMethod] 20 | public void LongCount() 21 | { 22 | var normalSyntax = TestData.Sales.LongCount(); 23 | var nonGenericQueryable = (IQueryable)TestData.Sales.AsQueryable(); 24 | var dynamicSyntax = nonGenericQueryable.LongCount(); 25 | Assert.AreEqual(normalSyntax, dynamicSyntax); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicLinq.Test/EntityFrameworkCoreTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Microsoft.VisualStudio.TestTools.UnitTesting; 5 | using PoweredSoft.DynamicLinq.Dal; 6 | using PoweredSoft.DynamicLinq.Dal.Pocos; 7 | using PoweredSoft.DynamicLinq; 8 | using Microsoft.EntityFrameworkCore; 9 | using PoweredSoft.DynamicLinq.EntityFrameworkCore; 10 | using PoweredSoft.DynamicLinq.Test.Helpers; 11 | 12 | namespace PoweredSoft.DynamicLinq.Test 13 | { 14 | [TestClass] 15 | public class EntityFrameworkCoreTests 16 | { 17 | 18 | private BlogCoreContext GetCoreContext(string testName) 19 | { 20 | var options = new DbContextOptionsBuilder() 21 | .UseInMemoryDatabase(databaseName: testName).Options; 22 | return new BlogCoreContext(options); 23 | } 24 | 25 | 26 | public static void SeedForTests(BlogCoreContext context) 27 | { 28 | context.Authors.Add(new Author 29 | { 30 | FirstName = "David", 31 | LastName = "Lebee", 32 | Posts = new List() 33 | { 34 | new Post() 35 | { 36 | CreateTime = DateTimeOffset.Now, 37 | PublishTime = DateTimeOffset.Now, 38 | Title = "New project", 39 | Content = "Lots of good things coming", 40 | Comments = new List() 41 | { 42 | new Comment() 43 | { 44 | DisplayName = "John Doe", 45 | Email = "john.doe@me.com", 46 | CommentText = "Very interesting", 47 | }, 48 | new Comment() 49 | { 50 | DisplayName = "Nice Guy", 51 | Email = "nice.guy@lol.com", 52 | CommentText = "Best of luck!" 53 | } 54 | } 55 | }, 56 | new Post() 57 | { 58 | CreateTime = DateTimeOffset.Now, 59 | PublishTime = null, 60 | Title = "The future!", 61 | Content = "Is Near" 62 | } 63 | } 64 | }); 65 | 66 | context.Authors.Add(new Author 67 | { 68 | FirstName = "Some", 69 | LastName = "Dude", 70 | Posts = new List() 71 | { 72 | new Post() { 73 | CreateTime = DateTimeOffset.Now, 74 | PublishTime = DateTimeOffset.Now, 75 | Title = "The One", 76 | Content = "And Only" 77 | }, 78 | new Post() 79 | { 80 | CreateTime = DateTimeOffset.Now, 81 | PublishTime = DateTimeOffset.Now, 82 | Title = "The Two", 83 | Content = "And Second" 84 | } 85 | } 86 | }); 87 | 88 | context.SaveChanges(); 89 | } 90 | 91 | [TestMethod] 92 | public void TestSimpleWhere() 93 | { 94 | var context = GetCoreContext(nameof(TestSimpleWhere)); 95 | SeedForTests(context); 96 | 97 | var query = context.Authors.AsQueryable(); 98 | query = query.Where("FirstName", ConditionOperators.Equal, "David"); 99 | var author = query.FirstOrDefault(); 100 | Assert.IsNotNull(author); 101 | } 102 | [TestMethod] 103 | public void TestWhereAnd() 104 | { 105 | var context = GetCoreContext(nameof(TestWhereAnd)); 106 | SeedForTests(context); 107 | 108 | var query = context.Authors.AsQueryable(); 109 | query = query.Query(q => q 110 | .Compare("FirstName", ConditionOperators.Equal, "David") 111 | .And("LastName", ConditionOperators.Equal, "Lebee") 112 | ); 113 | 114 | var author = query.FirstOrDefault(); 115 | Assert.IsNotNull(author); 116 | } 117 | 118 | [TestMethod] 119 | public void GroupBy() 120 | { 121 | var context = GetCoreContext(nameof(TestWhereOr)); 122 | SeedForTests(context); 123 | 124 | var authorsWithFamilyNameCount = context.Authors 125 | .GroupBy(t => new 126 | { 127 | t.LastName, 128 | t.FirstName 129 | }).Select(t => new 130 | { 131 | t.Key, 132 | Count = t.Count() 133 | }).ToList(); 134 | 135 | var authorsWithFamilyNameCountDynamic = context.Authors 136 | .GroupBy(t => t.Path("FirstName").Path("LastName")) 137 | .Select(t => t.Key("FirstName", "FirstName").Key("LastName", "LastName").Count("Count")) 138 | .ToDynamicClassList(); 139 | 140 | Assert.AreEqual(authorsWithFamilyNameCount.Count, authorsWithFamilyNameCountDynamic.Count); 141 | for(var i = 0; i < authorsWithFamilyNameCount.Count; i++) 142 | { 143 | Assert.AreEqual(authorsWithFamilyNameCount[i].Key.FirstName, authorsWithFamilyNameCountDynamic[i].GetDynamicPropertyValue("FirstName")); 144 | Assert.AreEqual(authorsWithFamilyNameCount[i].Key.LastName, authorsWithFamilyNameCountDynamic[i].GetDynamicPropertyValue("LastName")); 145 | Assert.AreEqual(authorsWithFamilyNameCount[i].Count, authorsWithFamilyNameCountDynamic[i].GetDynamicPropertyValue("Count")); 146 | } 147 | } 148 | 149 | 150 | [TestMethod] 151 | public void TestWhereOr() 152 | { 153 | var context = GetCoreContext(nameof(TestWhereOr)); 154 | SeedForTests(context); 155 | 156 | var query = context.Authors.AsQueryable(); 157 | query = query.Query(q => q 158 | .Compare("FirstName", ConditionOperators.Equal, "David") 159 | .Or("FirstName", ConditionOperators.Equal, "Some") 160 | ); 161 | 162 | var author = query.FirstOrDefault(); 163 | Assert.IsNotNull(author); 164 | } 165 | 166 | [TestMethod] 167 | public void TestGoingThroughSimpleNav() 168 | { 169 | var context = GetCoreContext(nameof(TestGoingThroughSimpleNav)); 170 | SeedForTests(context); 171 | 172 | var query = context.Posts.AsQueryable(); 173 | query = query.Include("Author").Where("Author.FirstName", ConditionOperators.Contains, "David"); 174 | var post = query.FirstOrDefault(); 175 | Assert.AreEqual("David", post?.Author?.FirstName); 176 | } 177 | 178 | [TestMethod] 179 | public void TestGoingThroughCollectionNav() 180 | { 181 | var context = GetCoreContext(nameof(TestGoingThroughCollectionNav)); 182 | SeedForTests(context); 183 | 184 | var query = context.Authors.AsQueryable(); 185 | query = query.Where("Posts.Title", ConditionOperators.Contains, "New"); 186 | var author = query.FirstOrDefault(); 187 | 188 | Assert.AreEqual(author?.FirstName, "David"); 189 | } 190 | 191 | [TestMethod] 192 | public void TestGoingThrough2CollectionNav() 193 | { 194 | var context = GetCoreContext(nameof(TestGoingThrough2CollectionNav)); 195 | SeedForTests(context); 196 | 197 | var query = context.Authors.AsQueryable(); 198 | query = query.Where("Posts.Comments.Email", ConditionOperators.Contains, "@me.com"); 199 | var author = query.FirstOrDefault(); 200 | 201 | Assert.AreEqual(author?.FirstName, "David"); 202 | } 203 | 204 | [TestMethod] 205 | public void TestSort() 206 | { 207 | var context = GetCoreContext(nameof(TestSort)); 208 | SeedForTests(context); 209 | 210 | var query = context.Posts.AsQueryable(); 211 | var dq = query.OrderBy("Title").ThenByDescending("Content").ToList(); 212 | var sq = query.OrderBy(t => t.Title).ThenByDescending(t => t.Content).ToList(); 213 | 214 | Assert.AreEqual(dq.Count, sq.Count); 215 | for (var i = 0; i < dq.Count; i++) 216 | Assert.AreEqual(dq[i].Id, sq[i].Id); 217 | } 218 | 219 | [TestMethod] 220 | public void TestContextTypeLessHelper() 221 | { 222 | var context = GetCoreContext(nameof(TestContextTypeLessHelper)); 223 | SeedForTests(context); 224 | 225 | var queryable = context.Query(typeof(Author), q => q.Compare("FirstName", ConditionOperators.Equal, "David")); 226 | var result = queryable.ToObjectList(); 227 | var first = result.FirstOrDefault() as Author; 228 | Assert.AreEqual(first?.FirstName, "David"); 229 | } 230 | 231 | [TestMethod] 232 | public void TestLessAndGreaterThan() 233 | { 234 | var context = GetCoreContext(nameof(TestWhereAnd)); //EF Core 235 | //or new BlogContext(testConnectionString); in EF 236 | SeedForTests(context); 237 | 238 | var query = context.Authors.Query(q => q.LessThan("FirstName", "Mario")); 239 | var first = query.FirstOrDefault(); 240 | Assert.AreEqual(first?.FirstName, "David"); 241 | 242 | query = context.Authors.Query(q => q.GreaterThan("FirstName", "Mario")); 243 | first = query.FirstOrDefault(); 244 | Assert.AreEqual(first?.FirstName, "Some"); 245 | } 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicLinq.Test/EntityFrameworkTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Data.Entity; 4 | using System.Linq; 5 | using Microsoft.VisualStudio.TestTools.UnitTesting; 6 | using PoweredSoft.DynamicLinq.Dal; 7 | using PoweredSoft.DynamicLinq.Dal.Pocos; 8 | using PoweredSoft.DynamicLinq.EntityFramework; 9 | using PoweredSoft.DynamicLinq; 10 | 11 | namespace PoweredSoft.DynamicLinq.Test 12 | { 13 | [TestClass] 14 | public class EntityFrameworkTests 15 | { 16 | public static string testConnectionString => 17 | @"Server=(localdb)\mssqllocaldb;Database=EFProviders.InMemory;Trusted_Connection=True;"; 18 | //"data source=(local); initial catalog=blogtests;persist security info=True; Integrated Security=SSPI;"; 19 | 20 | public static void SeedForTests(BlogContext context) 21 | { 22 | context.Authors.Add(new Author 23 | { 24 | FirstName = "David", 25 | LastName = "Lebee", 26 | Posts = new List() 27 | { 28 | new Post() 29 | { 30 | CreateTime = DateTimeOffset.Now, 31 | PublishTime = DateTimeOffset.Now, 32 | Title = "New project", 33 | Content = "Lots of good things coming", 34 | Comments = new List() 35 | { 36 | new Comment() 37 | { 38 | DisplayName = "John Doe", 39 | Email = "john.doe@me.com", 40 | CommentText = "Very interesting", 41 | }, 42 | new Comment() 43 | { 44 | DisplayName = "Nice Guy", 45 | Email = "nice.guy@lol.com", 46 | CommentText = "Best of luck!" 47 | } 48 | } 49 | }, 50 | new Post() 51 | { 52 | CreateTime = DateTimeOffset.Now, 53 | PublishTime = null, 54 | Title = "The future!", 55 | Content = "Is Near" 56 | } 57 | } 58 | }); 59 | 60 | context.Authors.Add(new Author 61 | { 62 | FirstName = "Some", 63 | LastName = "Dude", 64 | Posts = new List() 65 | { 66 | new Post() { 67 | CreateTime = DateTimeOffset.Now, 68 | PublishTime = DateTimeOffset.Now, 69 | Title = "The One", 70 | Content = "And Only" 71 | }, 72 | new Post() 73 | { 74 | CreateTime = DateTimeOffset.Now, 75 | PublishTime = DateTimeOffset.Now, 76 | Title = "The Two", 77 | Content = "And Second" 78 | } 79 | } 80 | }); 81 | 82 | context.SaveChanges(); 83 | } 84 | 85 | 86 | 87 | [TestMethod] 88 | public void TestSimpleWhere() 89 | { 90 | var context = new BlogContext(testConnectionString); 91 | SeedForTests(context); 92 | 93 | var query = context.Authors.AsQueryable(); 94 | query = query.Where("FirstName", ConditionOperators.Equal, "David"); 95 | var author = query.FirstOrDefault(); 96 | Assert.IsNotNull(author); 97 | } 98 | 99 | [TestMethod] 100 | public void TestWhereAnd() 101 | { 102 | var context = new BlogContext(testConnectionString); 103 | SeedForTests(context); 104 | 105 | var query = context.Authors.AsQueryable(); 106 | query = query.Query(q => q 107 | .Compare("FirstName", ConditionOperators.Equal, "David") 108 | .And("LastName", ConditionOperators.Equal, "Lebee") 109 | ); 110 | 111 | var author = query.FirstOrDefault(); 112 | Assert.IsNotNull(author); 113 | } 114 | 115 | [TestMethod] 116 | public void TestWhereOr() 117 | { 118 | var context = new BlogContext(testConnectionString); 119 | SeedForTests(context); 120 | 121 | var query = context.Authors.AsQueryable(); 122 | query = query.Query(q => q 123 | .Compare("FirstName", ConditionOperators.Equal, "David") 124 | .Or("FirstName", ConditionOperators.Equal, "Some") 125 | ); 126 | 127 | var author = query.FirstOrDefault(); 128 | Assert.IsNotNull(author); 129 | } 130 | 131 | [TestMethod] 132 | public void TestGoingThroughSimpleNav() 133 | { 134 | var context = new BlogContext(testConnectionString); 135 | SeedForTests(context); 136 | 137 | var query = context.Posts.AsQueryable(); 138 | query = query.Include("Author").Where("Author.FirstName", ConditionOperators.Contains, "David"); 139 | var post = query.FirstOrDefault(); 140 | Assert.AreEqual("David", post?.Author?.FirstName); 141 | } 142 | 143 | [TestMethod] 144 | public void TestGoingThroughCollectionNav() 145 | { 146 | var context = new BlogContext(testConnectionString); 147 | SeedForTests(context); 148 | 149 | var query = context.Authors.AsQueryable(); 150 | query = query.Where("Posts.Title", ConditionOperators.Contains, "New"); 151 | var author = query.FirstOrDefault(); 152 | 153 | Assert.AreEqual(author?.FirstName, "David"); 154 | } 155 | 156 | [TestMethod] 157 | public void TestGoingThrough2CollectionNav() 158 | { 159 | var context = new BlogContext(testConnectionString); 160 | SeedForTests(context); 161 | 162 | var query = context.Authors.AsQueryable(); 163 | query = query.Where("Posts.Comments.Email", ConditionOperators.Contains, "@me.com"); 164 | var author = query.FirstOrDefault(); 165 | 166 | Assert.AreEqual(author?.FirstName, "David"); 167 | } 168 | 169 | [TestMethod] 170 | public void TestSort() 171 | { 172 | var context = new BlogContext(testConnectionString); 173 | SeedForTests(context); 174 | 175 | var query = context.Posts.AsQueryable(); 176 | var dq = query.OrderBy("Title").ThenByDescending("Content").ToList(); 177 | var sq = query.OrderBy(t => t.Title).ThenByDescending(t => t.Content).ToList(); 178 | 179 | Assert.AreEqual(dq.Count, sq.Count); 180 | for (var i = 0; i < dq.Count; i++) 181 | Assert.AreEqual(dq[i].Id, sq[i].Id); 182 | } 183 | 184 | [TestMethod] 185 | public void TestContextTypeLessHelper() 186 | { 187 | var context = new BlogContext(testConnectionString); 188 | SeedForTests(context); 189 | 190 | var queryable = context.Query(typeof(Author), q => q.Compare("FirstName", ConditionOperators.Equal, "David")); 191 | var result = queryable.ToListAsync().Result; 192 | var first = result.FirstOrDefault() as Author; 193 | Assert.AreEqual(first?.FirstName, "David"); 194 | } 195 | 196 | [TestMethod] 197 | public void TestLessAndGreaterThan() 198 | { 199 | var context = new BlogContext(testConnectionString); 200 | SeedForTests(context); 201 | 202 | var query = context.Authors.Query(q => q.LessThan("FirstName", "Mario")); 203 | var first = query.FirstOrDefault(); 204 | Assert.AreEqual(first?.FirstName, "David"); 205 | 206 | query = context.Authors.Query(q => q.GreaterThan("FirstName", "Mario")); 207 | first = query.FirstOrDefault(); 208 | Assert.AreEqual(first?.FirstName, "Some"); 209 | } 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicLinq.Test/GetCoreContext.cs: -------------------------------------------------------------------------------- 1 | namespace PoweredSoft.DynamicLinq.Test 2 | { 3 | internal class GetCoreContext 4 | { 5 | private string v; 6 | 7 | public GetCoreContext(string v) 8 | { 9 | this.v = v; 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /PoweredSoft.DynamicLinq.Test/GroupingTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using PoweredSoft.DynamicLinq; 8 | using PoweredSoft.DynamicLinq.Dal; 9 | using System.Diagnostics; 10 | using PoweredSoft.DynamicLinq.Test.Helpers; 11 | using System.Collections; 12 | using PoweredSoft.DynamicLinq.Dal.Pocos; 13 | 14 | namespace PoweredSoft.DynamicLinq.Test 15 | { 16 | internal class TestStructureCompare : IEqualityComparer 17 | { 18 | public bool Equals(TestStructure x, TestStructure y) 19 | { 20 | return x?.ClientId == y?.ClientId; 21 | } 22 | 23 | public int GetHashCode(TestStructure obj) 24 | { 25 | return obj.ClientId; 26 | } 27 | } 28 | 29 | internal class TestStructure 30 | { 31 | public int ClientId { get; set; } 32 | } 33 | 34 | [TestClass] 35 | public class GroupingTests 36 | { 37 | [TestMethod] 38 | public void TestEmptyGroup() 39 | { 40 | var subject = TestData.Sales; 41 | 42 | var normalSyntax = subject 43 | .GroupBy(t => true) 44 | .Select(t => new 45 | { 46 | NetSalesSum = t.Sum(t2 => t2.NetSales), 47 | NetSalesAvg = t.Average(t2 => t2.NetSales) 48 | }) 49 | .First(); 50 | 51 | var dynamicSyntax = subject 52 | .EmptyGroupBy(typeof(MockSale)) 53 | .Select(sb => 54 | { 55 | sb.Sum("NetSales", "NetSalesSum"); 56 | sb.Average("NetSales", "NetSalesAvg"); 57 | }) 58 | .ToDynamicClassList() 59 | .First(); 60 | 61 | Assert.AreEqual(normalSyntax.NetSalesAvg, dynamicSyntax.GetDynamicPropertyValue("NetSalesAvg")); 62 | Assert.AreEqual(normalSyntax.NetSalesSum, dynamicSyntax.GetDynamicPropertyValue("NetSalesSum")); 63 | } 64 | 65 | [TestMethod] 66 | public void WantedSyntax() 67 | { 68 | var normalSyntax = TestData.Sales 69 | .GroupBy(t => new { t.ClientId }) 70 | .Select(t => new 71 | { 72 | TheClientId = t.Key.ClientId, 73 | Count = t.Count(), 74 | LongCount = t.LongCount(), 75 | NetSales = t.Sum(t2 => t2.NetSales), 76 | TaxAverage = t.Average(t2 => t2.Tax), 77 | Sales = t.ToList(), 78 | MaxNetSales = t.Max(t2 => t2.NetSales), 79 | MinNetSales = t.Min(t2 => t2.NetSales), 80 | First = t.First(), 81 | Last = t.Last(), 82 | FirstOrDefault = t.FirstOrDefault(), 83 | LastOrDefault = t.LastOrDefault() 84 | }) 85 | .ToList(); 86 | 87 | var dynamicSyntax = TestData.Sales 88 | .AsQueryable() 89 | .GroupBy(t => t.Path("ClientId")) 90 | .Select(t => 91 | { 92 | t.Key("TheClientId", "ClientId"); 93 | t.Count("Count"); 94 | t.LongCount("LongCount"); 95 | t.Sum("NetSales"); 96 | t.Average("Tax", "TaxAverage"); 97 | t.Max("NetSales", "MaxNetSales"); 98 | t.Min("NetSales", "MinNetSales"); 99 | t.First("First"); 100 | t.Last("Last"); 101 | t.FirstOrDefault("FirstOrDefault"); 102 | t.LastOrDefault("LastOrDefault"); 103 | t.ToList("Sales"); 104 | }) 105 | .ToDynamicClassList(); 106 | 107 | Assert.AreEqual(normalSyntax.Count, dynamicSyntax.Count); 108 | for(var i = 0; i < normalSyntax.Count; i++) 109 | { 110 | var left = normalSyntax[i]; 111 | var right = dynamicSyntax[i]; 112 | 113 | Assert.AreEqual(left.TheClientId, right.GetDynamicPropertyValue("TheClientId")); 114 | Assert.AreEqual(left.Count, right.GetDynamicPropertyValue("Count")); 115 | Assert.AreEqual(left.LongCount, right.GetDynamicPropertyValue("LongCount")); 116 | Assert.AreEqual(left.TaxAverage, right.GetDynamicPropertyValue("TaxAverage")); 117 | Assert.AreEqual(left.MinNetSales, right.GetDynamicPropertyValue("MinNetSales")); 118 | Assert.AreEqual(left.MaxNetSales, right.GetDynamicPropertyValue("MaxNetSales")); 119 | 120 | Assert.AreEqual(left.First, right.GetDynamicPropertyValue("First")); 121 | Assert.AreEqual(left.FirstOrDefault, right.GetDynamicPropertyValue("FirstOrDefault")); 122 | Assert.AreEqual(left.Last, right.GetDynamicPropertyValue("Last")); 123 | Assert.AreEqual(left.LastOrDefault, right.GetDynamicPropertyValue("LastOrDefault")); 124 | 125 | QueryableAssert.AreEqual(left.Sales.AsQueryable(), right.GetDynamicPropertyValue>("Sales").AsQueryable()); 126 | } 127 | } 128 | 129 | [TestMethod] 130 | public void TestingSelectBuilderAggregateFluent() 131 | { 132 | var normalSyntax = TestData.Sales 133 | .GroupBy(t => new { t.ClientId }) 134 | .Select(t => new 135 | { 136 | TheClientId = t.Key.ClientId, 137 | Count = t.Count(), 138 | LongCount = t.LongCount(), 139 | NetSales = t.Sum(t2 => t2.NetSales), 140 | TaxAverage = t.Average(t2 => t2.Tax), 141 | Sales = t.ToList() 142 | }) 143 | .ToList(); 144 | 145 | var dynamicSyntax = TestData.Sales 146 | .AsQueryable() 147 | .GroupBy(t => t.Path("ClientId")) 148 | .Select(t => 149 | { 150 | t.Aggregate("Key.ClientId", SelectTypes.Key, "TheClientId"); 151 | // should not have to specify a path, but a property is a must 152 | t.Aggregate(null, SelectTypes.Count, "Count"); 153 | // support both ways it can use path to guess property so testing this too 154 | t.Aggregate("LongCount", SelectTypes.LongCount); 155 | t.Aggregate("NetSales", SelectTypes.Sum); 156 | t.Aggregate("Tax", SelectTypes.Average, "TaxAverage"); 157 | t.ToList("Sales"); 158 | }) 159 | .ToDynamicClassList(); 160 | 161 | Assert.AreEqual(normalSyntax.Count, dynamicSyntax.Count); 162 | for (var i = 0; i < normalSyntax.Count; i++) 163 | { 164 | var left = normalSyntax[i]; 165 | var right = dynamicSyntax[i]; 166 | 167 | Assert.AreEqual(left.TheClientId, right.GetDynamicPropertyValue("TheClientId")); 168 | Assert.AreEqual(left.Count, right.GetDynamicPropertyValue("Count")); 169 | Assert.AreEqual(left.LongCount, right.GetDynamicPropertyValue("LongCount")); 170 | Assert.AreEqual(left.TaxAverage, right.GetDynamicPropertyValue("TaxAverage")); 171 | QueryableAssert.AreEqual(left.Sales.AsQueryable(), right.GetDynamicPropertyValue>("Sales").AsQueryable()); 172 | } 173 | } 174 | 175 | [TestMethod] 176 | public void GroupWithoutNullCheckComplex() 177 | { 178 | var limitResult = TestData.Authors.Where(t => t.Posts != null).AsQueryable(); 179 | 180 | var posts = limitResult 181 | .GroupBy(t => new 182 | { 183 | Titles = t.Posts.Select(t2 => t2.Title) 184 | }) 185 | .Select(t => new 186 | { 187 | Titles = t.Key.Titles, 188 | Data = t.ToList() 189 | }) 190 | .ToList(); 191 | 192 | var posts2 = limitResult 193 | .GroupBy(gb => gb.Path("Posts.Title", "Titles")) 194 | .Select(sb => 195 | { 196 | sb.Key("Titles"); 197 | sb.ToList("Data"); 198 | }) 199 | .ToDynamicClassList(); 200 | 201 | Assert.AreEqual(posts.Count, posts2.Count); 202 | for(var i = 0; i < posts.Count; i++) 203 | { 204 | var expected = posts[0]; 205 | var actual = posts2[0]; 206 | 207 | var titles = actual.GetDynamicPropertyValue("Titles") as ICollection; 208 | 209 | CollectionAssert.AreEqual(expected.Titles as ICollection, titles); 210 | } 211 | } 212 | 213 | [TestMethod] 214 | public void GroupWithNullCheckComplex() 215 | { 216 | var limitResult = TestData.Authors.AsQueryable(); 217 | 218 | var posts = limitResult 219 | .GroupBy(t => new 220 | { 221 | Titles = t.Posts == null ? new List() : t.Posts.Select(t2 => t2.Title) 222 | }) 223 | .Select(t => new 224 | { 225 | Titles = t.Key.Titles, 226 | Data = t.ToList() 227 | }) 228 | .ToList(); 229 | 230 | var tempQueryable = limitResult 231 | .GroupBy(gb => gb.NullChecking().Path("Posts.Title", "Titles")); 232 | 233 | 234 | var posts2 = tempQueryable 235 | .Select(sb => 236 | { 237 | sb.Key("Titles"); 238 | sb.ToList("Data"); 239 | }) 240 | .ToDynamicClassList(); 241 | 242 | Assert.AreEqual(posts.Count, posts2.Count); 243 | for (var i = 0; i < posts.Count; i++) 244 | { 245 | var expected = posts[0]; 246 | var actual = posts2[0]; 247 | 248 | var titles = actual.GetDynamicPropertyValue("Titles") as ICollection; 249 | 250 | CollectionAssert.AreEqual(expected.Titles as ICollection, titles); 251 | } 252 | } 253 | 254 | [TestMethod] 255 | public void GroupByToListWithPath() 256 | { 257 | var limitResult = TestData.Posts.Where(t => t.Author != null); 258 | 259 | var expected = limitResult.GroupBy(t => new 260 | { 261 | AuthorFirstName = t.Author.FirstName 262 | }) 263 | .Select(t => new 264 | { 265 | Key = t.Key.AuthorFirstName, 266 | Contents = t.Select(t2 => t2.Content).ToList() 267 | }) 268 | .ToList(); 269 | 270 | var actualQuery = limitResult 271 | .GroupBy(t => t.Path("Author.FirstName", "AuthorFirstName")) 272 | .Select(t => 273 | { 274 | t.Key("Key", "AuthorFirstName"); 275 | t.ToList("Content", "Contents", SelectCollectionHandling.LeaveAsIs); 276 | }); 277 | 278 | var actual = actualQuery.ToDynamicClassList(); 279 | 280 | Assert.AreEqual(expected.Count, actual.Count); 281 | for(var i = 0; i < expected.Count; i++) 282 | { 283 | var itExpected = expected[i]; 284 | var itActual = actual[i]; 285 | 286 | 287 | Assert.AreEqual(itExpected.Key, itActual.GetDynamicPropertyValue("Key")); 288 | CollectionAssert.AreEqual(itExpected.Contents, itActual.GetDynamicPropertyValue("Contents") as ICollection); 289 | } 290 | } 291 | 292 | [TestMethod] 293 | public void GroupByToListWithPathWithNullCheckingWithFlattening() 294 | { 295 | var limitResult = TestData.Authors; 296 | 297 | var expected = limitResult.GroupBy(t => new 298 | { 299 | AuthorFirstName = t.FirstName 300 | }) 301 | .Select(t => new 302 | { 303 | Key = t.Key.AuthorFirstName, 304 | Contents = t.SelectMany(t2 => t2.Posts == null ? new List() : t2.Posts.Select(t3 => t3.Content)).ToList() 305 | }) 306 | .ToList(); 307 | 308 | var actualQuery = limitResult 309 | .GroupBy(t => t.NullChecking().Path("FirstName", "AuthorFirstName")) 310 | .Select(t => 311 | { 312 | t.NullChecking(); 313 | t.Key("Key", "AuthorFirstName"); 314 | t.ToList("Posts.Content", "Contents", SelectCollectionHandling.Flatten); 315 | }); 316 | 317 | var actual = actualQuery.ToDynamicClassList(); 318 | 319 | Assert.AreEqual(expected.Count, actual.Count); 320 | for (var i = 0; i < expected.Count; i++) 321 | { 322 | var itExpected = expected[i]; 323 | var itActual = actual[i]; 324 | 325 | 326 | Assert.AreEqual(itExpected.Key, itActual.GetDynamicPropertyValue("Key")); 327 | CollectionAssert.AreEqual(itExpected.Contents, itActual.GetDynamicPropertyValue("Contents") as ICollection); 328 | } 329 | } 330 | 331 | } 332 | } 333 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicLinq.Test/Helpers/QueryableAssert.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace PoweredSoft.DynamicLinq.Test.Helpers 9 | { 10 | public static class QueryableAssert 11 | { 12 | private static bool _sameList(IQueryable a, IQueryable b) 13 | where T : class 14 | { 15 | if (a.Count() != b.Count()) 16 | return false; 17 | 18 | var listA = a.ToList(); 19 | var listB = b.ToList(); 20 | for (var i = 0; i < listA.Count; i++) 21 | { 22 | if (listA.ElementAt(i) != listB.ElementAt(i)) 23 | return false; 24 | } 25 | 26 | return true; 27 | } 28 | 29 | public static void AreEqual(IQueryable a, IQueryable b) 30 | where T : class 31 | { 32 | Assert.IsTrue(_sameList(a, b)); 33 | } 34 | 35 | public static void AreNotEqual(IQueryable a, IQueryable b) 36 | where T : class 37 | { 38 | Assert.IsFalse(_sameList(a, b)); 39 | } 40 | 41 | public static void AreEqual(IQueryable a, IQueryable b, string message) 42 | where T : class 43 | { 44 | Assert.IsTrue(_sameList(a, b), message); 45 | } 46 | 47 | public static void AreNotEqual(IQueryable a, IQueryable b, string message) 48 | where T : class 49 | { 50 | Assert.IsFalse(_sameList(a, b), message); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicLinq.Test/HelpersTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Microsoft.VisualStudio.TestTools.UnitTesting; 5 | using PoweredSoft.DynamicLinq.Dal.Pocos; 6 | using PoweredSoft.DynamicLinq.Helpers; 7 | 8 | namespace PoweredSoft.DynamicLinq.Test 9 | { 10 | class Foo 11 | { 12 | 13 | } 14 | 15 | class ListOfFoo : List 16 | { 17 | 18 | } 19 | 20 | [TestClass] 21 | public class HelpersTests 22 | { 23 | 24 | [TestMethod] 25 | public void TestInheritanceOfListAsGenericEnumerableType() 26 | { 27 | var shouldBeTrue = QueryableHelpers.IsGenericEnumerable(typeof(ListOfFoo)); 28 | Assert.IsTrue(shouldBeTrue); 29 | var type = QueryableHelpers.GetTypeOfEnumerable(typeof(ListOfFoo), true); 30 | Assert.IsTrue(type == typeof(Foo)); 31 | } 32 | 33 | [TestMethod] 34 | public void TestCreateFilterExpression() 35 | { 36 | var authors = new List() 37 | { 38 | new Author 39 | { 40 | Id = 1, 41 | FirstName = "David", 42 | LastName = "Lebee", 43 | Posts = new List 44 | { 45 | new Post 46 | { 47 | Id = 1, 48 | AuthorId = 1, 49 | Title = "Match", 50 | Content = "ABC", 51 | Comments = new List() 52 | { 53 | new Comment() 54 | { 55 | Id = 1, 56 | DisplayName = "John Doe", 57 | CommentText = "!@#$!@#!@#", 58 | Email = "John.doe@me.com" 59 | } 60 | } 61 | }, 62 | new Post 63 | { 64 | Id = 2, 65 | AuthorId = 1, 66 | Title = "Match", 67 | Content = "ABC", 68 | Comments = new List() 69 | } 70 | } 71 | }, 72 | new Author 73 | { 74 | Id = 2, 75 | FirstName = "Chuck", 76 | LastName = "Norris", 77 | Posts = new List 78 | { 79 | new Post 80 | { 81 | Id = 3, 82 | AuthorId = 2, 83 | Title = "Match", 84 | Content = "ASD", 85 | Comments = new List() 86 | }, 87 | new Post 88 | { 89 | Id = 4, 90 | AuthorId = 2, 91 | Title = "DontMatch", 92 | Content = "ASD", 93 | Comments = new List() 94 | } 95 | } 96 | } 97 | }; 98 | 99 | // the query. 100 | var query = authors.AsQueryable(); 101 | 102 | var allExpression = QueryableHelpers.CreateConditionExpression("Posts.Title", ConditionOperators.Equal, "Match", QueryConvertStrategy.ConvertConstantToComparedPropertyOrField, QueryCollectionHandling.All); 103 | var anyExpression = QueryableHelpers.CreateConditionExpression("Posts.Title", ConditionOperators.Equal, "Match", QueryConvertStrategy.ConvertConstantToComparedPropertyOrField, QueryCollectionHandling.Any); 104 | var anyExpression2 = QueryableHelpers.CreateConditionExpression("Posts.Comments.Email", ConditionOperators.Equal, "John.doe@me.com", QueryConvertStrategy.ConvertConstantToComparedPropertyOrField, QueryCollectionHandling.Any); 105 | Assert.AreEqual(1, query.Count(allExpression)); 106 | Assert.AreEqual(2, query.Count(anyExpression)); 107 | Assert.AreEqual(1, query.Count(anyExpression2)); 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicLinq.Test/InTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Microsoft.VisualStudio.TestTools.UnitTesting; 5 | using PoweredSoft.DynamicLinq; 6 | using PoweredSoft.DynamicLinq.Dal.Pocos; 7 | using PoweredSoft.DynamicLinq.Test.Helpers; 8 | 9 | namespace PoweredSoft.DynamicLinq.Test 10 | { 11 | [TestClass] 12 | public class InTests 13 | { 14 | [TestMethod] 15 | public void In() 16 | { 17 | IQueryable a, b; 18 | var ageGroup = new List() { 28, 27, 50 }; 19 | a = TestData.Persons.AsQueryable().Query(t => t.In("Age", ageGroup)); 20 | b = TestData.Persons.AsQueryable().Where(t => ageGroup.Contains(t.Age)); 21 | QueryableAssert.AreEqual(a, b); 22 | } 23 | 24 | [TestMethod] 25 | public void NotIn() 26 | { 27 | IQueryable a, b; 28 | var ageGroup = new List() { 50, 58 }; 29 | a = TestData.Persons.AsQueryable().Query(t => t.NotIn("Age", ageGroup)); 30 | b = TestData.Persons.AsQueryable().Where(t => !ageGroup.Contains(t.Age)); 31 | QueryableAssert.AreEqual(a, b); 32 | } 33 | 34 | [TestMethod] 35 | public void InString() 36 | { 37 | IQueryable a, b; 38 | var group = new List() { "David", "Michaela" }; 39 | a = TestData.Persons.AsQueryable().Query(t => t.In("FirstName", group)); 40 | b = TestData.Persons.AsQueryable().Where(t => group.Contains(t.FirstName)); 41 | QueryableAssert.AreEqual(a, b); 42 | } 43 | 44 | [TestMethod] 45 | public void DiffTypeListConversion() 46 | { 47 | IQueryable a, b; 48 | var ageGroup = new List() { "28", "27", "50" }; 49 | var ageGroupInt = ageGroup.Select(t => Convert.ToInt32(t)).ToList(); 50 | 51 | a = TestData.Persons.AsQueryable().Query(t => t.In("Age", ageGroup)); 52 | b = TestData.Persons.AsQueryable().Where(t => ageGroupInt.Contains(t.Age)); 53 | QueryableAssert.AreEqual(a, b); 54 | } 55 | 56 | [TestMethod] 57 | public void MixingInWithCollectionPaths() 58 | { 59 | var titles = new List() { "Match" }; 60 | var a = TestData.Authors.AsQueryable().Query(t => t.NullChecking(true).In("Posts.Title", titles)); 61 | var b = TestData.Authors.AsQueryable().Where(t => t.Posts != null && t.Posts.Any(t2 => titles.Contains(t2.Title))); 62 | QueryableAssert.AreEqual(a, b); 63 | } 64 | 65 | [TestMethod] 66 | public void MixingInComplexPaths() 67 | { 68 | var authorsFirstNames = new List() { "David", "Pablo" }; 69 | var a = TestData.Posts.AsQueryable().Query(t => t.In("Author.FirstName", authorsFirstNames)); 70 | var b = TestData.Posts.AsQueryable().Where(t => authorsFirstNames.Contains(t.Author.FirstName)); 71 | QueryableAssert.AreEqual(a, b); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicLinq.Test/PoweredSoft.DynamicLinq.Test.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicLinq.Test/SelectTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using PoweredSoft.DynamicLinq.Dal.Pocos; 3 | using PoweredSoft.DynamicLinq.Test.Helpers; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace PoweredSoft.DynamicLinq.Test 11 | { 12 | public class Mock 13 | { 14 | public int Id { get; set; } 15 | public int ForeignId { get; set; } 16 | public decimal Total { get; set; } 17 | 18 | public List Bs { get; set; } = new List(); 19 | } 20 | 21 | public class MockB 22 | { 23 | public List FirstNames { get; set; } 24 | } 25 | 26 | public class MockPerson 27 | { 28 | public string Name { get; set; } 29 | public MockListOfPhone Phones { get; set; } 30 | } 31 | 32 | public class MockPhone 33 | { 34 | public string Number { get; set; } 35 | } 36 | 37 | public class MockListOfPhone : List 38 | { 39 | 40 | } 41 | 42 | [TestClass] 43 | public class SelectTests 44 | { 45 | [TestMethod] 46 | public void TestSelectWithInheritedList() 47 | { 48 | var list = new List() 49 | { 50 | new MockPerson 51 | { 52 | Name = "David Lebee", 53 | Phones = new MockListOfPhone 54 | { 55 | new MockPhone 56 | { 57 | Number = "0000000000" 58 | } 59 | } 60 | }, 61 | new MockPerson 62 | { 63 | Name = "Yubing Liang", 64 | Phones = new MockListOfPhone 65 | { 66 | new MockPhone 67 | { 68 | Number = "1111111111" 69 | } 70 | } 71 | } 72 | }; 73 | 74 | var names = list.AsQueryable() 75 | .Where(t => t.Equal("Phones.Number", "1111111111")) 76 | .Select(t => 77 | { 78 | t.Path("Name"); 79 | t.FirstOrDefault("Phones.Number", "Number", SelectCollectionHandling.Flatten); 80 | }) 81 | .ToDynamicClassList(); 82 | 83 | Assert.IsTrue(names.Count() == 1); 84 | var firstPerson = names.First(); 85 | Assert.AreEqual("Yubing Liang", firstPerson.GetDynamicPropertyValue("Name")); 86 | Assert.AreEqual("1111111111", firstPerson.GetDynamicPropertyValue("Number")); 87 | } 88 | 89 | [TestMethod] 90 | public void TestSelect() 91 | { 92 | var list = new List() 93 | { 94 | new Mock{ 95 | Id = 1, 96 | ForeignId = 1, 97 | Total = 100, 98 | Bs = new List() { 99 | new MockB { FirstNames = new List{"David", "John" } } 100 | } 101 | }, 102 | }; 103 | 104 | var regularSyntaxA = list 105 | .AsQueryable() 106 | .Select(t => new 107 | { 108 | Id = t.Id, 109 | FirstNames = t.Bs.SelectMany(t2 => t2.FirstNames).ToList(), 110 | FirstNamesLists = t.Bs.Select(t2 => t2.FirstNames).ToList(), 111 | FirstFirstName = t.Bs.SelectMany(t2 => t2.FirstNames).First(), 112 | FirstOrDefaultFirstName = t.Bs.SelectMany(t2 => t2.FirstNames).FirstOrDefault(), 113 | LastFirstName = t.Bs.SelectMany(t2 => t2.FirstNames).Last(), 114 | LastOrDefaultFirstName = t.Bs.SelectMany(t2 => t2.FirstNames).LastOrDefault(), 115 | 116 | FirstFirstNameList = t.Bs.Select(t2 => t2.FirstNames).First(), 117 | FirstOrDefaultFirstNameList = t.Bs.Select(t2 => t2.FirstNames).FirstOrDefault(), 118 | LastFirstNameList = t.Bs.Select(t2 => t2.FirstNames).Last(), 119 | LastOrDefaultFirstNameList = t.Bs.Select(t2 => t2.FirstNames).LastOrDefault() 120 | }); 121 | 122 | var regularSyntax = regularSyntaxA.ToList(); 123 | 124 | var dynamicSyntax = list 125 | .AsQueryable() 126 | .Select(t => 127 | { 128 | t.Path("Id"); 129 | t.ToList("Bs.FirstNames", "FirstNames", SelectCollectionHandling.Flatten); 130 | t.ToList("Bs.FirstNames", "FirstNamesLists", SelectCollectionHandling.LeaveAsIs); 131 | t.First("Bs.FirstNames", "FirstFirstName", SelectCollectionHandling.Flatten); 132 | t.FirstOrDefault("Bs.FirstNames", "FirstOrDefaultFirstName", SelectCollectionHandling.Flatten); 133 | t.Last("Bs.FirstNames", "LastFirstName", SelectCollectionHandling.Flatten); 134 | t.LastOrDefault("Bs.FirstNames", "LastOrDefaultFirstName", SelectCollectionHandling.Flatten); 135 | 136 | t.First("Bs.FirstNames", "FirstFirstNameList", SelectCollectionHandling.LeaveAsIs); 137 | t.FirstOrDefault("Bs.FirstNames", "FirstOrDefaultFirstNameList", SelectCollectionHandling.LeaveAsIs); 138 | t.Last("Bs.FirstNames", "LastFirstNameList", SelectCollectionHandling.LeaveAsIs); 139 | t.LastOrDefault("Bs.FirstNames", "LastOrDefaultFirstNameList", SelectCollectionHandling.LeaveAsIs); 140 | }) 141 | .ToDynamicClassList(); 142 | 143 | Assert.AreEqual(regularSyntax.Count, dynamicSyntax.Count); 144 | for(var i = 0; i < regularSyntax.Count; i++) 145 | { 146 | Assert.AreEqual(regularSyntax[i].Id, dynamicSyntax[i].GetDynamicPropertyValue("Id")); 147 | Assert.AreEqual(regularSyntax[i].FirstFirstName, dynamicSyntax[i].GetDynamicPropertyValue("FirstFirstName")); 148 | Assert.AreEqual(regularSyntax[i].FirstOrDefaultFirstName, dynamicSyntax[i].GetDynamicPropertyValue("FirstOrDefaultFirstName")); 149 | Assert.AreEqual(regularSyntax[i].LastFirstName, dynamicSyntax[i].GetDynamicPropertyValue("LastFirstName")); 150 | Assert.AreEqual(regularSyntax[i].LastOrDefaultFirstName, dynamicSyntax[i].GetDynamicPropertyValue("LastOrDefaultFirstName")); 151 | 152 | CollectionAssert.AreEqual(regularSyntax[i].FirstFirstNameList, dynamicSyntax[i].GetDynamicPropertyValue>("FirstFirstNameList")); 153 | CollectionAssert.AreEqual(regularSyntax[i].FirstOrDefaultFirstNameList, dynamicSyntax[i].GetDynamicPropertyValue>("FirstOrDefaultFirstNameList")); 154 | CollectionAssert.AreEqual(regularSyntax[i].LastFirstNameList, dynamicSyntax[i].GetDynamicPropertyValue>("LastFirstNameList")); 155 | CollectionAssert.AreEqual(regularSyntax[i].LastOrDefaultFirstNameList, dynamicSyntax[i].GetDynamicPropertyValue>("LastOrDefaultFirstNameList")); 156 | 157 | 158 | QueryableAssert.AreEqual(regularSyntax[i].FirstNames.AsQueryable(), dynamicSyntax[i].GetDynamicPropertyValue>("FirstNames").AsQueryable()); 159 | 160 | 161 | 162 | 163 | var left = regularSyntax[i].FirstNamesLists; 164 | var right = dynamicSyntax[i].GetDynamicPropertyValue>>("FirstNamesLists"); 165 | Assert.AreEqual(left.Count, right.Count); 166 | for(var j = 0; j < left.Count; j++) 167 | QueryableAssert.AreEqual(left[j].AsQueryable(), right[j].AsQueryable()); 168 | } 169 | } 170 | 171 | [TestMethod] 172 | public void SelectNullChecking() 173 | { 174 | var query = TestData.Authors.AsQueryable(); 175 | 176 | 177 | var qs = query.Select(t => new 178 | { 179 | CommentLikes = t.Posts == null ? 180 | new List() : 181 | t.Posts.Where(t2 => t2.Comments != null).SelectMany(t2 => t2.Comments.Where(t3 => t3.CommentLikes != null).SelectMany(t3 => t3.CommentLikes)).ToList() 182 | }); 183 | 184 | var a = qs.ToList(); 185 | 186 | var querySelect = query.Select(t => 187 | { 188 | t.NullChecking(true); 189 | t.ToList("Posts.Comments.CommentLikes", selectCollectionHandling: SelectCollectionHandling.Flatten); 190 | }); 191 | 192 | var b = querySelect.ToDynamicClassList(); 193 | 194 | Assert.AreEqual(a.Count, b.Count); 195 | for(var i = 0; i < a.Count; i++) 196 | { 197 | var left = a[i]; 198 | var right = b[i]; 199 | 200 | var leftCommentLikes = left.CommentLikes; 201 | var rightCommentLikes = right.GetDynamicPropertyValue>("CommentLikes"); 202 | QueryableAssert.AreEqual(leftCommentLikes.AsQueryable(), rightCommentLikes.AsQueryable()); 203 | } 204 | } 205 | 206 | [TestMethod] 207 | public void SelectNullChecking2() 208 | { 209 | var query = TestData.Likes.AsQueryable(); 210 | 211 | var qs = query.Select(t => new 212 | { 213 | Post = t.Comment == null || t.Comment.Post == null ? null : t.Comment.Post, 214 | Texts = (t.Comment == null || t.Comment.Post == null || t.Comment.Post.Comments == null ? new List() : t.Comment.Post.Comments.Select(t2 => t2.CommentText)).ToList() 215 | }); 216 | 217 | var a = qs.ToList(); 218 | 219 | var querySelect = query.Select(t => 220 | { 221 | t.NullChecking(true); 222 | // this needs to be fixed. 223 | t.Path("Comment.CommentText", "CommentText2"); 224 | //t.PathToList("Comment.Post.Comments.CommentText", selectCollectionHandling: SelectCollectionHandling.Flatten); 225 | }); 226 | 227 | var b = querySelect.ToDynamicClassList(); 228 | } 229 | } 230 | } 231 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicLinq.Test/ShortcutTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Microsoft.VisualStudio.TestTools.UnitTesting; 5 | using PoweredSoft.DynamicLinq; 6 | using PoweredSoft.DynamicLinq.Test.Helpers; 7 | 8 | namespace PoweredSoft.DynamicLinq.Test 9 | { 10 | [TestClass] 11 | public class ShortcutTests 12 | { 13 | internal List Persons = new List 14 | { 15 | new MockPersonObject { FirstName = "David", LastName = "Lebee", Age = 28 }, 16 | new MockPersonObject { FirstName = "Michaela", LastName = "Vickar", Age = 27 }, 17 | new MockPersonObject { FirstName = "John", LastName = "Doe", Age = 28 }, 18 | new MockPersonObject { FirstName = "Chuck", LastName = "Norris", Age = 50 }, 19 | new MockPersonObject { FirstName = "Michael", LastName = "Jackson", Age = 58 } 20 | }; 21 | 22 | 23 | [TestMethod] 24 | public void Equal() 25 | { 26 | // test simple 27 | var q1 = Persons.AsQueryable().Query(t => t.Equal("FirstName", "David")); 28 | var q1b = Persons.AsQueryable().Where(t => t.FirstName == "David"); 29 | QueryableAssert.AreEqual(q1, q1b); 30 | 31 | // test and 32 | var q2 = Persons.AsQueryable().Query(t => t.Equal("FirstName", "David").AndEqual("LastName", "Lebee")); 33 | var q2b = Persons.AsQueryable().Where(t => t.FirstName == "David" && t.LastName == "Lebee"); 34 | QueryableAssert.AreEqual(q2, q2b); 35 | 36 | 37 | // test or 38 | var q3 = Persons.AsQueryable().Query(t => t.Equal("FirstName", "David").OrEqual("FirstName", "Michaela")); 39 | var q3b = Persons.AsQueryable().Where(t => t.FirstName == "David" || t.FirstName == "Michaela"); 40 | QueryableAssert.AreEqual(q3, q3b); 41 | } 42 | 43 | [TestMethod] 44 | public void NotEqual() 45 | { 46 | // test simple 47 | var q1 = Persons.AsQueryable().Query(t => t.NotEqual("FirstName", "David")); 48 | var q1b = Persons.AsQueryable().Where(t => t.FirstName != "David"); 49 | QueryableAssert.AreEqual(q1, q1b); 50 | 51 | // test and 52 | var q2 = Persons.AsQueryable().Query(t => t.NotEqual("FirstName", "David").AndNotEqual("FirstName", "Michaela")); 53 | var q2b = Persons.AsQueryable().Where(t => t.FirstName != "David" && t.FirstName != "Michaela"); 54 | QueryableAssert.AreEqual(q2, q2b); 55 | 56 | // test or 57 | var q3 = Persons.AsQueryable().Query(t => t.NotEqual("FirstName", "David").OrNotEqual("LastName", "Lebee")); 58 | var q3b = Persons.AsQueryable().Where(t => t.FirstName != "David" || t.LastName != "Lebee"); 59 | QueryableAssert.AreEqual(q3, q3b); 60 | } 61 | 62 | [TestMethod] 63 | public void GreatThan() 64 | { 65 | var q1 = Persons.AsQueryable().Query(t => t.GreaterThan("Age", 28)); 66 | var q1b = Persons.AsQueryable().Where(t => t.Age > 28); 67 | QueryableAssert.AreEqual(q1, q1b); 68 | 69 | var q2 = Persons.AsQueryable().Query(t => t.GreaterThan("Age", 28).AndGreaterThan("Age", 50)); 70 | var q2b = Persons.AsQueryable().Where(t => t.Age > 28 && t.Age > 50); 71 | QueryableAssert.AreEqual(q2, q2b); 72 | 73 | var q3 = Persons.AsQueryable().Query(t => t.GreaterThan("Age", 28).OrGreaterThan("Age", 50)); 74 | var q3b = Persons.AsQueryable().Where(t => t.Age > 28 || t.Age > 50); 75 | QueryableAssert.AreEqual(q3, q3b); 76 | } 77 | 78 | [TestMethod] 79 | public void GreatThanOrEqual() 80 | { 81 | var q1 = Persons.AsQueryable().Query(t => t.GreaterThanOrEqual("Age", 28)); 82 | var q1b = Persons.AsQueryable().Where(t => t.Age >= 28); 83 | QueryableAssert.AreEqual(q1, q1b); 84 | 85 | var q2 = Persons.AsQueryable().Query(t => t.GreaterThanOrEqual("Age", 28).AndGreaterThanOrEqual("Age", 50)); 86 | var q2b = Persons.AsQueryable().Where(t => t.Age >= 28 && t.Age >= 50); 87 | QueryableAssert.AreEqual(q2, q2b); 88 | 89 | var q3 = Persons.AsQueryable().Query(t => t.GreaterThanOrEqual("Age", 28).OrGreaterThanOrEqual("Age", 50)); 90 | var q3b = Persons.AsQueryable().Where(t => t.Age >= 28 || t.Age >= 50); 91 | QueryableAssert.AreEqual(q3, q3b); 92 | } 93 | 94 | [TestMethod] 95 | public void LessThan() 96 | { 97 | var q1 = Persons.AsQueryable().Query(t => t.LessThan("Age", 50)); 98 | var q1b = Persons.AsQueryable().Where(t => t.Age < 50); 99 | QueryableAssert.AreEqual(q1, q1b); 100 | 101 | var q2 = Persons.AsQueryable().Query(t => t.LessThan("Age", 28).AndLessThan("Age", 50)); 102 | var q2b = Persons.AsQueryable().Where(t => t.Age < 28 && t.Age < 50); 103 | QueryableAssert.AreEqual(q2, q2b); 104 | 105 | var q3 = Persons.AsQueryable().Query(t => t.LessThan("Age", 28).OrLessThan("Age", 50)); 106 | var q3b = Persons.AsQueryable().Where(t => t.Age < 28 || t.Age < 50); 107 | QueryableAssert.AreEqual(q3, q3b); 108 | } 109 | 110 | [TestMethod] 111 | public void LessThanOrEqual() 112 | { 113 | var q1 = Persons.AsQueryable().Query(t => t.LessThanOrEqual("Age", 50)); 114 | var q1b = Persons.AsQueryable().Where(t => t.Age <= 50); 115 | QueryableAssert.AreEqual(q1, q1b); 116 | 117 | var q2 = Persons.AsQueryable().Query(t => t.LessThanOrEqual("Age", 28).AndLessThanOrEqual("Age", 50)); 118 | var q2b = Persons.AsQueryable().Where(t => t.Age <= 28 && t.Age <= 50); 119 | QueryableAssert.AreEqual(q2, q2b); 120 | 121 | var q3 = Persons.AsQueryable().Query(t => t.LessThanOrEqual("Age", 28).OrLessThanOrEqual("Age", 50)); 122 | var q3b = Persons.AsQueryable().Where(t => t.Age <= 28 || t.Age <= 50); 123 | QueryableAssert.AreEqual(q3, q3b); 124 | } 125 | 126 | [TestMethod] 127 | public void StartsWith() 128 | { 129 | var q1 = Persons.AsQueryable().Query(t => t.StartsWith("FirstName", "Mi")); 130 | var q1b = Persons.AsQueryable().Where(t => t.FirstName.StartsWith("Mi")); 131 | QueryableAssert.AreEqual(q1, q1b); 132 | 133 | var q2 = Persons.AsQueryable().Query(t => t.StartsWith("FirstName", "Mi").AndStartsWith("LastName", "Vi")); 134 | var q2b = Persons.AsQueryable().Where(t => t.FirstName.StartsWith("Mi") && t.LastName.StartsWith("Vi")); 135 | QueryableAssert.AreEqual(q2, q2b); 136 | 137 | var q3 = Persons.AsQueryable().Query(t => t.StartsWith("FirstName", "Mi").OrStartsWith("FirstName", "Da")); 138 | var q3b = Persons.AsQueryable().Where(t => t.FirstName.StartsWith("Mi") || t.FirstName.StartsWith("Da")); 139 | QueryableAssert.AreEqual(q3, q3b); 140 | } 141 | 142 | [TestMethod] 143 | public void EndsWith() 144 | { 145 | var q1 = Persons.AsQueryable().Query(t => t.EndsWith("LastName", "ee")); 146 | var q1b = Persons.AsQueryable().Where(t => t.LastName.EndsWith("ee")); 147 | QueryableAssert.AreEqual(q1, q1b); 148 | 149 | var q2 = Persons.AsQueryable().Query(t => t.EndsWith("LastName", "ee").AndEndsWith("FirstName", "vid")); 150 | var q2b = Persons.AsQueryable().Where(t => t.LastName.EndsWith("ee") && t.FirstName.EndsWith("vid")); 151 | QueryableAssert.AreEqual(q2, q2b); 152 | 153 | var q3 = Persons.AsQueryable().Query(t => t.EndsWith("LastName", "ee").OrEndsWith("LastName", "ar")); 154 | var q3b = Persons.AsQueryable().Where(t => t.LastName.EndsWith("ee") || t.LastName.EndsWith("ar")); 155 | QueryableAssert.AreEqual(q3, q3b); 156 | } 157 | 158 | [TestMethod] 159 | public void Contains() 160 | { 161 | var q1 = Persons.AsQueryable().Query(t => t.Contains("LastName", "ee")); 162 | var q1b = Persons.AsQueryable().Where(t => t.LastName.Contains("ee")); 163 | QueryableAssert.AreEqual(q1, q1b); 164 | 165 | var q2 = Persons.AsQueryable().Query(t => t.Contains("LastName", "ee").AndContains("FirstName", "vid")); 166 | var q2b = Persons.AsQueryable().Where(t => t.LastName.Contains("ee") && t.FirstName.Contains("vid")); 167 | QueryableAssert.AreEqual(q2, q2b); 168 | 169 | var q3 = Persons.AsQueryable().Query(t => t.Contains("LastName", "ee").OrContains("LastName", "ar")); 170 | var q3b = Persons.AsQueryable().Where(t => t.LastName.Contains("ee") || t.LastName.Contains("ar")); 171 | QueryableAssert.AreEqual(q3, q3b); 172 | } 173 | 174 | [TestMethod] 175 | public void NotContains() 176 | { 177 | var q1 = Persons.AsQueryable().Query(t => t.Contains("LastName", "ee", negate: true)); 178 | var q1b = Persons.AsQueryable().Where(t => !t.LastName.Contains("ee")); 179 | QueryableAssert.AreEqual(q1, q1b); 180 | } 181 | 182 | [TestMethod] 183 | public void NotContains2() 184 | { 185 | var q1 = Persons.AsQueryable().Query(t => t.NotContains("LastName", "ee")); 186 | var q1b = Persons.AsQueryable().Where(t => !t.LastName.Contains("ee")); 187 | QueryableAssert.AreEqual(q1, q1b); 188 | 189 | var q2 = Persons.AsQueryable().Query(t => t.NotContains("LastName", "ee").AndNotContains("FirstName", "vid")); 190 | var q2b = Persons.AsQueryable().Where(t => !t.LastName.Contains("ee") && !t.FirstName.Contains("vid")); 191 | QueryableAssert.AreEqual(q2, q2b); 192 | 193 | var q3 = Persons.AsQueryable().Query(t => t.NotContains("LastName", "ee").OrNotContains("LastName", "ar")); 194 | var q3b = Persons.AsQueryable().Where(t => !t.LastName.Contains("ee") || !t.LastName.Contains("ar")); 195 | QueryableAssert.AreEqual(q3, q3b); 196 | } 197 | 198 | [TestMethod] 199 | public void ContainsAndNotContains() 200 | { 201 | var q1 = Persons.AsQueryable().Query(t => t.Contains("LastName", "s").AndNotContains("LastName", "r")); 202 | var q1b = Persons.AsQueryable().Where(t => t.LastName.Contains("s") && !t.LastName.Contains("r")); 203 | QueryableAssert.AreEqual(q1, q1b); 204 | } 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicLinq.Test/SimpleQueriesTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using Microsoft.VisualStudio.TestTools.UnitTesting; 6 | using PoweredSoft.DynamicLinq.Dal.Pocos; 7 | using PoweredSoft.DynamicLinq; 8 | 9 | namespace PoweredSoft.DynamicLinq.Test 10 | { 11 | [TestClass] 12 | public class SimpleQueryTests 13 | { 14 | [TestMethod] 15 | public void Equal() 16 | { 17 | // subject. 18 | var authors = new List() 19 | { 20 | new Author { Id = long.MaxValue, FirstName = "David", LastName = "Lebee" } 21 | }; 22 | 23 | // the query. 24 | var query = authors.AsQueryable(); 25 | 26 | // simple where. 27 | var newQuery = query.Where("FirstName", ConditionOperators.Equal, "David"); 28 | 29 | // must match. 30 | Assert.IsTrue(newQuery.Any(), "Must have at least one author that matches"); 31 | } 32 | 33 | [TestMethod] 34 | public void EqualLowerCase() 35 | { 36 | // subject. 37 | var authors = new List() 38 | { 39 | new Author { Id = long.MaxValue, FirstName = "David", LastName = "Lebee" } 40 | }; 41 | 42 | // the query. 43 | var query = authors.AsQueryable(); 44 | 45 | // simple where. 46 | var newQuery = query.Where("FirstName.ToLower()", ConditionOperators.Equal, "david"); 47 | 48 | // must match. 49 | Assert.IsTrue(newQuery.Any(), "Must have at least one author that matches"); 50 | } 51 | 52 | [TestMethod] 53 | public void EqualLowerCaseNullCheck() 54 | { 55 | // subject. 56 | var authors = new List() 57 | { 58 | new Author { Id = long.MaxValue, FirstName = null, LastName = "Lebee" }, 59 | new Author { Id = long.MaxValue, FirstName = "David", LastName = "Lebee" }, 60 | }; 61 | 62 | // the query. 63 | var query = authors.AsQueryable(); 64 | 65 | // simple where. 66 | var newQuery = query.Where(wb => 67 | { 68 | wb.Equal("FirstName.ToLower()", "david").NullChecking(true); 69 | }); 70 | 71 | // must match. 72 | Assert.IsTrue(newQuery.Any(), "Must have at least one author that matches"); 73 | } 74 | 75 | [TestMethod] 76 | public void DoubleMethodCheck() 77 | { 78 | // subject. 79 | var authors = new List() 80 | { 81 | new Author { Id = long.MaxValue, FirstName = "David ", LastName = "Lebee" }, 82 | }; 83 | 84 | // the query. 85 | var query = authors.AsQueryable(); 86 | 87 | // simple where. 88 | var newQuery = query.Where(wb => 89 | { 90 | wb.Equal("FirstName.Trim().ToLower()", "david").NullChecking(true); 91 | }); 92 | 93 | // must match. 94 | Assert.IsTrue(newQuery.Any(), "Must have at least one author that matches"); 95 | } 96 | 97 | [TestMethod] 98 | public void Contains() 99 | { 100 | // subject. 101 | var authors = new List() 102 | { 103 | new Author { Id = long.MaxValue, FirstName = "David", LastName = "Lebee" } 104 | }; 105 | 106 | // the query. 107 | var query = authors.AsQueryable(); 108 | 109 | // simple where. 110 | var newQuery = query.Where("FirstName", ConditionOperators.Contains, "Da"); 111 | 112 | // must match. 113 | Assert.IsTrue(newQuery.Any(), "Must have at least one author that matches"); 114 | } 115 | 116 | [TestMethod] 117 | public void StartsWith() 118 | { 119 | // subject. 120 | var authors = new List() 121 | { 122 | new Author { Id = long.MaxValue, FirstName = "David", LastName = "Lebee" } 123 | }; 124 | 125 | // the query. 126 | var query = authors.AsQueryable(); 127 | 128 | // simple where. 129 | var newQuery = query.Where("FirstName", ConditionOperators.StartsWith, "Da"); 130 | 131 | // must match. 132 | Assert.IsTrue(newQuery.Any(), "Must have at least one author that matches"); 133 | } 134 | 135 | [TestMethod] 136 | public void EndsWith() 137 | { 138 | // subject. 139 | var authors = new List() 140 | { 141 | new Author { Id = long.MaxValue, FirstName = "David", LastName = "Lebee" } 142 | }; 143 | 144 | // the query. 145 | var query = authors.AsQueryable(); 146 | 147 | // simple where. 148 | var newQuery = query.Where("FirstName", ConditionOperators.EndsWith, "Da"); 149 | 150 | // must match. 151 | Assert.IsFalse(newQuery.Any(), "Not suppose to find any matches"); 152 | } 153 | 154 | [TestMethod] 155 | public void LessThen() 156 | { 157 | // subject. 158 | var posts = new List() 159 | { 160 | new Post { Id = 1, AuthorId = 1, Title = "Hello 1", Content = "World" }, 161 | new Post { Id = 2, AuthorId = 1, Title = "Hello 2", Content = "World" }, 162 | new Post { Id = 3, AuthorId = 2, Title = "Hello 3", Content = "World" }, 163 | }; 164 | 165 | // the query. 166 | var query = posts.AsQueryable(); 167 | 168 | // simple where. 169 | var newQuery = query.Where("AuthorId", ConditionOperators.LessThan, 2); 170 | 171 | // must match. 172 | Assert.AreEqual(2, newQuery.Count()); 173 | } 174 | 175 | [TestMethod] 176 | public void GreaterThanOrEqual() 177 | { 178 | // subject. 179 | var posts = new List() 180 | { 181 | new Post { Id = 1, AuthorId = 1, Title = "Hello 1", Content = "World" }, 182 | new Post { Id = 2, AuthorId = 1, Title = "Hello 2", Content = "World" }, 183 | new Post { Id = 3, AuthorId = 2, Title = "Hello 3", Content = "World" }, 184 | }; 185 | 186 | // the query. 187 | var query = posts.AsQueryable(); 188 | 189 | // simple where. 190 | var newQuery = query.Where("AuthorId", ConditionOperators.GreaterThanOrEqual, 2); 191 | 192 | // must match. 193 | Assert.AreEqual(1, newQuery.Count()); 194 | } 195 | 196 | [TestMethod] 197 | public void TestingSort() 198 | { 199 | // subject. 200 | var posts = new List() 201 | { 202 | new Post { Id = 1, AuthorId = 1, Title = "Hello 1", Content = "World"}, 203 | new Post { Id = 2, AuthorId = 1, Title = "Hello 2", Content = "World"}, 204 | new Post { Id = 3, AuthorId = 2, Title = "Hello 3", Content = "World"}, 205 | }; 206 | 207 | // the query. 208 | var query = posts.AsQueryable(); 209 | query = query.OrderByDescending("AuthorId"); 210 | query = query.ThenBy("Id"); 211 | 212 | var first = query.First(); 213 | var second = query.Skip(1).First(); 214 | 215 | Assert.IsTrue(first.Id == 3); 216 | Assert.IsTrue(second.Id == 1); 217 | } 218 | 219 | [TestMethod] 220 | public void TestingSort2() 221 | { 222 | // subject. 223 | var posts = new List() 224 | { 225 | new Post { Id = 1, AuthorId = 1, Title = "Hello 1", Content = "World", Comments = new List { } }, 226 | new Post { Id = 2, AuthorId = 1, Title = "Hello 2", Content = "World", Comments = new List { } }, 227 | new Post { Id = 3, AuthorId = 2, Title = "Hello 3", Content = "World", Comments = new List { } }, 228 | }; 229 | 230 | // the query. 231 | var query = posts.AsQueryable(); 232 | 233 | // just testing that the expressionm can be created, some drivers support seleting in collections. 234 | var query2 = query.OrderByDescending("Comments"); 235 | var query3 = query.OrderByDescending("Comments.PostId"); 236 | } 237 | } 238 | } 239 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicLinq.Test/StringComparision.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Microsoft.VisualStudio.TestTools.UnitTesting; 5 | using PoweredSoft.DynamicLinq; 6 | using PoweredSoft.DynamicLinq.Test.Helpers; 7 | 8 | namespace PoweredSoft.DynamicLinq.Test 9 | { 10 | [TestClass] 11 | public class StringComparisionTests 12 | { 13 | internal List Persons => TestData.Persons; 14 | 15 | [TestMethod] 16 | public void Equal() 17 | { 18 | IQueryable a, b; 19 | 20 | // case sensitive. 21 | a = Persons.AsQueryable().Query(t => t.Equal("FirstName", "David", stringComparision: null)); 22 | b = Persons.AsQueryable().Where(t => t.FirstName == "David"); 23 | QueryableAssert.AreEqual(a, b, "CaseSensitive"); 24 | 25 | // not case sensitive 26 | a = Persons.AsQueryable().Query(t => t.Equal("FirstName", "DAVID", stringComparision: StringComparison.OrdinalIgnoreCase)); 27 | b = Persons.AsQueryable().Where(t => t.FirstName.Equals("DAVID", StringComparison.OrdinalIgnoreCase)); 28 | QueryableAssert.AreEqual(a, b, "CaseInsensitive"); 29 | } 30 | 31 | [TestMethod] 32 | public void NotEqual() 33 | { 34 | IQueryable a, b; 35 | 36 | // case sensitive. 37 | a = Persons.AsQueryable().Query(t => t.NotEqual("FirstName", "David", stringComparision: null)); 38 | b = Persons.AsQueryable().Where(t => t.FirstName != "David"); 39 | QueryableAssert.AreEqual(a, b, "CaseSensitive"); 40 | 41 | // not case sensitive 42 | a = Persons.AsQueryable().Query(t => t.NotEqual("FirstName", "DAVID", stringComparision: StringComparison.OrdinalIgnoreCase)); 43 | b = Persons.AsQueryable().Where(t => !t.FirstName.Equals("DAVID", StringComparison.OrdinalIgnoreCase)); 44 | QueryableAssert.AreEqual(a, b, "CaseInsensitive"); 45 | } 46 | 47 | [TestMethod] 48 | public void Contains() 49 | { 50 | IQueryable a, b; 51 | 52 | // case sensitive. 53 | a = Persons.AsQueryable().Query(t => t.Contains("FirstName", "vi", stringComparision: null)); 54 | b = Persons.AsQueryable().Where(t => t.FirstName.Contains("vi")); 55 | QueryableAssert.AreEqual(a, b, "CaseSensitive"); 56 | 57 | // not case sensitive 58 | a = Persons.AsQueryable().Query(t => t.Contains("FirstName", "VI", stringComparision: StringComparison.OrdinalIgnoreCase)); 59 | b = Persons.AsQueryable().Where(t => t.FirstName.IndexOf("VI", StringComparison.OrdinalIgnoreCase) > -1); 60 | QueryableAssert.AreEqual(a, b, "CaseInsensitive"); 61 | } 62 | 63 | [TestMethod] 64 | public void NotContains() 65 | { 66 | IQueryable a, b; 67 | 68 | // case sensitive. 69 | a = Persons.AsQueryable().Query(t => t.NotContains("FirstName", "vi", stringComparision: null)); 70 | b = Persons.AsQueryable().Where(t => !t.FirstName.Contains("vi")); 71 | QueryableAssert.AreEqual(a, b, "CaseSensitive"); 72 | 73 | // not case sensitive 74 | a = Persons.AsQueryable().Query(t => t.NotContains("FirstName", "VI", stringComparision: StringComparison.OrdinalIgnoreCase)); 75 | b = Persons.AsQueryable().Where(t => t.FirstName.IndexOf("VI", StringComparison.OrdinalIgnoreCase) == -1); 76 | QueryableAssert.AreEqual(a, b, "CaseInsensitive"); 77 | } 78 | 79 | [TestMethod] 80 | public void NegateNotContains() 81 | { 82 | IQueryable a, b; 83 | 84 | // case sensitive. 85 | a = Persons.AsQueryable().Query(t => t.NotContains("FirstName", "vi", stringComparision: null, negate: true)); 86 | b = Persons.AsQueryable().Where(t => !!t.FirstName.Contains("vi")); 87 | QueryableAssert.AreEqual(a, b, "CaseSensitive"); 88 | 89 | // not case sensitive 90 | a = Persons.AsQueryable().Query(t => t.NotContains("FirstName", "VI", stringComparision: StringComparison.OrdinalIgnoreCase, negate: true)); 91 | b = Persons.AsQueryable().Where(t => !(t.FirstName.IndexOf("VI", StringComparison.OrdinalIgnoreCase) == -1)); 92 | QueryableAssert.AreEqual(a, b, "CaseInsensitive"); 93 | } 94 | 95 | [TestMethod] 96 | public void StartsWith() 97 | { 98 | IQueryable a, b; 99 | 100 | // case sensitive. 101 | a = Persons.AsQueryable().Query(t => t.StartsWith("FirstName", "Da", stringComparision: null)); 102 | b = Persons.AsQueryable().Where(t => t.FirstName.StartsWith("Da")); 103 | QueryableAssert.AreEqual(a, b, "CaseSensitive"); 104 | 105 | // not case sensitive 106 | a = Persons.AsQueryable().Query(t => t.StartsWith("FirstName", "DA", stringComparision: StringComparison.OrdinalIgnoreCase)); 107 | b = Persons.AsQueryable().Where(t => t.FirstName.StartsWith("DA", StringComparison.OrdinalIgnoreCase)); 108 | QueryableAssert.AreEqual(a, b, "CaseInsensitive"); 109 | } 110 | 111 | [TestMethod] 112 | public void EndsWith() 113 | { 114 | IQueryable a, b; 115 | 116 | // case sensitive. 117 | a = Persons.AsQueryable().Query(t => t.EndsWith("FirstName", "vid", stringComparision: null)); 118 | b = Persons.AsQueryable().Where(t => t.FirstName.EndsWith("vid")); 119 | QueryableAssert.AreEqual(a, b, "CaseSensitive"); 120 | 121 | // not case sensitive 122 | a = Persons.AsQueryable().Query(t => t.EndsWith("FirstName", "VID", stringComparision: StringComparison.OrdinalIgnoreCase)); 123 | b = Persons.AsQueryable().Where(t => t.FirstName.EndsWith("VID", StringComparison.OrdinalIgnoreCase)); 124 | QueryableAssert.AreEqual(a, b, "CaseInsensitive"); 125 | } 126 | 127 | [DataTestMethod] 128 | [DataRow("Denis")] 129 | [DataRow("Ann")] 130 | [DataRow("Tony")] 131 | public void LessThan(string firstName) 132 | { 133 | IQueryable a, b; 134 | 135 | a = Persons.AsQueryable().Query(t => t.LessThan("FirstName", firstName)); 136 | b = Persons.AsQueryable().Where(t => t.FirstName.CompareTo(firstName) < 0); 137 | QueryableAssert.AreEqual(a, b); 138 | } 139 | 140 | [DataTestMethod] 141 | [DataRow("Denis")] 142 | [DataRow("Ann")] 143 | [DataRow("Tony")] 144 | public void LessThanOrEqual(string firstName) 145 | { 146 | IQueryable a, b; 147 | 148 | a = Persons.AsQueryable().Query(t => t.LessThanOrEqual("FirstName", firstName)); 149 | b = Persons.AsQueryable().Where(t => t.FirstName.CompareTo(firstName) <= 0); 150 | QueryableAssert.AreEqual(a, b); 151 | } 152 | 153 | [DataTestMethod] 154 | [DataRow("Denis")] 155 | [DataRow("Ann")] 156 | [DataRow("Tony")] 157 | public void GreaterThan(string firstName) 158 | { 159 | IQueryable a, b; 160 | 161 | a = Persons.AsQueryable().Query(t => t.GreaterThan("FirstName", firstName)); 162 | b = Persons.AsQueryable().Where(t => t.FirstName.CompareTo(firstName) > 0); 163 | QueryableAssert.AreEqual(a, b); 164 | } 165 | 166 | [DataTestMethod] 167 | [DataRow("Denis")] 168 | [DataRow("Ann")] 169 | [DataRow("Tony")] 170 | public void GreaterThanOrEqual(string firstName) 171 | { 172 | IQueryable a, b; 173 | 174 | a = Persons.AsQueryable().Query(t => t.GreaterThanOrEqual("FirstName", firstName)); 175 | b = Persons.AsQueryable().Where(t => t.FirstName.CompareTo(firstName) >= 0); 176 | QueryableAssert.AreEqual(a, b); 177 | } 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicLinq.Test/TestData.cs: -------------------------------------------------------------------------------- 1 | using PoweredSoft.DynamicLinq.Dal.Pocos; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace PoweredSoft.DynamicLinq.Test 9 | { 10 | internal class MockPersonObject 11 | { 12 | public string FirstName { get; set; } 13 | 14 | public string LastName { get; set; } 15 | 16 | public int Age { get; set; } 17 | } 18 | 19 | internal class MockSale 20 | { 21 | public long Id { get; set; } 22 | public int ClientId { get; set; } 23 | public MockClient Client { get; set; } 24 | public decimal GrossSales { get; set; } 25 | public decimal NetSales { get; set; } 26 | public decimal Tax { get; set; } 27 | } 28 | 29 | internal class MockClient 30 | { 31 | public long Id { get; set; } 32 | public string Name { get; set; } 33 | } 34 | 35 | internal static class TestData 36 | { 37 | static readonly internal List Uniques = new List 38 | { 39 | new Unique { Id = 1, RowNumber = Guid.NewGuid(), OtherNullableGuid = null } , 40 | new Unique { Id = 2, RowNumber = Guid.NewGuid(), OtherNullableGuid = Guid.NewGuid() } 41 | }; 42 | 43 | static readonly internal List Persons = new List 44 | { 45 | new MockPersonObject { FirstName = "David", LastName = "Lebee", Age = 28 }, 46 | new MockPersonObject { FirstName = "Michaela", LastName = "Vickar", Age = 27 }, 47 | new MockPersonObject { FirstName = "John", LastName = "Doe", Age = 28 }, 48 | new MockPersonObject { FirstName = "Chuck", LastName = "Norris", Age = 50 }, 49 | new MockPersonObject { FirstName = "Michael", LastName = "Jackson", Age = 58 } 50 | }; 51 | 52 | static readonly internal List Clients = new List 53 | { 54 | new MockClient { Id = 1, Name = "ACME INC."}, 55 | new MockClient { Id = 2, Name = "MSLINK" }, 56 | new MockClient { Id = 3, Name = "COOL GUYS TBD"}, 57 | new MockClient { Id = 4, Name = "SOME LLC YEAH!" } 58 | }; 59 | 60 | static readonly internal List Sales = new List 61 | { 62 | new MockSale { Id = 1, ClientId = 1, Client = Clients.First(t => t.Id == 1), GrossSales = 1000M, NetSales = 890.0M, Tax = 20M }, 63 | new MockSale { Id = 2, ClientId = 1, Client = Clients.First(t => t.Id == 1), GrossSales = 1100M, NetSales = 180.0M, Tax = 0M }, 64 | new MockSale { Id = 3, ClientId = 2, Client = Clients.First(t => t.Id == 2), GrossSales = 1200M, NetSales = 920.0M, Tax = 3M }, 65 | new MockSale { Id = 4, ClientId = 2, Client = Clients.First(t => t.Id == 2), GrossSales = 1330M, NetSales = 800.0M, Tax = 120M }, 66 | new MockSale { Id = 5, ClientId = 1, Client = Clients.First(t => t.Id == 1), GrossSales = 1400M, NetSales = 990.0M, Tax = 20M }, 67 | new MockSale { Id = 6, ClientId = 3, Client = Clients.First(t => t.Id == 3), GrossSales = 1500M, NetSales = 290.0M, Tax = 200M }, 68 | new MockSale { Id = 7, ClientId = 3, Client = Clients.First(t => t.Id == 3), GrossSales = 1600M, NetSales = 230.0M, Tax = 240M }, 69 | new MockSale { Id = 8, ClientId = 3, Client = Clients.First(t => t.Id == 3), GrossSales = 1700M, NetSales = 330.0M, Tax = 210M }, 70 | new MockSale { Id = 9, ClientId = 1, Client = Clients.First(t => t.Id == 1), GrossSales = 1800M, NetSales = 890.0M, Tax = 290M }, 71 | new MockSale { Id = 10, ClientId = 4, Client = Clients.First(t => t.Id == 4), GrossSales = 1900M, NetSales = 490.0M, Tax = 270M } 72 | }; 73 | 74 | static readonly internal List Posts = new List() 75 | { 76 | new Post 77 | { 78 | Id = 1, 79 | Author = new Author() 80 | { 81 | Id = 1, 82 | FirstName = "David", 83 | LastName = "Lebee" 84 | }, 85 | AuthorId = 1, 86 | CreateTime = DateTime.Now, 87 | Title = "Match", 88 | Content = "ABC", 89 | }, 90 | new Post 91 | { 92 | Id = 2, 93 | Author = new Author() 94 | { 95 | Id = 1, 96 | FirstName = "David", 97 | LastName = "Lebee" 98 | }, 99 | AuthorId = 1, 100 | CreateTime = DateTime.Now, 101 | Title = "Match 2", 102 | Content = "ABC 2", 103 | }, 104 | new Post 105 | { 106 | Id = 3, 107 | Author = new Author() 108 | { 109 | Id = 2, 110 | FirstName = "John", 111 | LastName = "Doe" 112 | }, 113 | AuthorId = 3, 114 | CreateTime = DateTime.Now, 115 | Title = "Match 3", 116 | Content = "ABC 3", 117 | }, 118 | }; 119 | 120 | static readonly internal List Likes = new List() 121 | { 122 | new CommentLike 123 | { 124 | Id = 1, 125 | CommentId = 1, 126 | Comment = new Comment 127 | { 128 | Email = "john@doe.ca", 129 | CommentText = "bla bla", 130 | Post = new Post 131 | { 132 | Content = "ASDFSADF" 133 | } 134 | } 135 | }, 136 | new CommentLike 137 | { 138 | Id = 2, 139 | CommentId = 2 140 | } 141 | }; 142 | 143 | static readonly internal List Authors = new List() 144 | { 145 | new Author 146 | { 147 | Id = 1, 148 | FirstName = "David", 149 | LastName = "Lebee", 150 | Posts = new List 151 | { 152 | new Post 153 | { 154 | Id = 1, 155 | AuthorId = 1, 156 | Title = "Match", 157 | Content = "ABC", 158 | Comments = new List() 159 | { 160 | new Comment() 161 | { 162 | Id = 1, 163 | DisplayName = "John Doe", 164 | CommentText = "!@#$!@#!@#", 165 | Email = "john.doe@me.com", 166 | CommentLikes = new List() 167 | { 168 | new CommentLike() 169 | { 170 | Id = 1, 171 | CommentId = 1, 172 | CreateTime = DateTimeOffset.Now 173 | }, 174 | new CommentLike() 175 | { 176 | Id = 2, 177 | CommentId = 1, 178 | CreateTime = DateTimeOffset.Now 179 | }, 180 | } 181 | } 182 | } 183 | }, 184 | new Post 185 | { 186 | Id = 2, 187 | AuthorId = 1, 188 | Title = "Match", 189 | Content = "ABC" 190 | } 191 | } 192 | }, 193 | new Author 194 | { 195 | Id = 2, 196 | FirstName = "Chuck", 197 | LastName = "Norris", 198 | Posts = new List 199 | { 200 | new Post 201 | { 202 | Id = 3, 203 | AuthorId = 2, 204 | Title = "Match", 205 | Content = "ASD" 206 | }, 207 | new Post 208 | { 209 | Id = 4, 210 | AuthorId = 2, 211 | Title = "DontMatch", 212 | Content = "ASD" 213 | } 214 | } 215 | }, 216 | new Author 217 | { 218 | Id = 3, 219 | FirstName = "Mark", 220 | LastName = "Ronson" 221 | } 222 | }; 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicLinq.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29503.13 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PoweredSoft.DynamicLinq", "PoweredSoft.DynamicLinq\PoweredSoft.DynamicLinq.csproj", "{5BB7E50F-8B40-4512-88DC-4B3BD89C9A5E}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PoweredSoft.DynamicLinq.Test", "PoweredSoft.DynamicLinq.Test\PoweredSoft.DynamicLinq.Test.csproj", "{6F5C80F0-9045-4098-913F-7BDAD135E6DD}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PoweredSoft.DynamicLinq.Dal", "PoweredSoft.DynamicLinq.Dal\PoweredSoft.DynamicLinq.Dal.csproj", "{C16927E7-1358-4B9D-BDD7-149E505DE6CC}" 11 | EndProject 12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PoweredSoft.DynamicLinq.EntityFramework", "PoweredSoft.DynamicLinq.EntityFramework\PoweredSoft.DynamicLinq.EntityFramework.csproj", "{82DADDB0-4A69-4E19-82AD-E73ABC8F1B4A}" 13 | EndProject 14 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{77B4027B-ECB0-4ED1-8646-025AC4146CE2}" 15 | ProjectSection(SolutionItems) = preProject 16 | LICENSE = LICENSE 17 | README.md = README.md 18 | EndProjectSection 19 | EndProject 20 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PoweredSoft.DynamicLinq.EntityFrameworkCore", "PoweredSoft.DynamicLinq.EntityFrameworkCore\PoweredSoft.DynamicLinq.EntityFrameworkCore.csproj", "{BBF5805B-560C-474B-885B-9202281B42F7}" 21 | EndProject 22 | Global 23 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 24 | Debug|Any CPU = Debug|Any CPU 25 | Release|Any CPU = Release|Any CPU 26 | EndGlobalSection 27 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 28 | {5BB7E50F-8B40-4512-88DC-4B3BD89C9A5E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 29 | {5BB7E50F-8B40-4512-88DC-4B3BD89C9A5E}.Debug|Any CPU.Build.0 = Debug|Any CPU 30 | {5BB7E50F-8B40-4512-88DC-4B3BD89C9A5E}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {5BB7E50F-8B40-4512-88DC-4B3BD89C9A5E}.Release|Any CPU.Build.0 = Release|Any CPU 32 | {6F5C80F0-9045-4098-913F-7BDAD135E6DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {6F5C80F0-9045-4098-913F-7BDAD135E6DD}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {6F5C80F0-9045-4098-913F-7BDAD135E6DD}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {6F5C80F0-9045-4098-913F-7BDAD135E6DD}.Release|Any CPU.Build.0 = Release|Any CPU 36 | {C16927E7-1358-4B9D-BDD7-149E505DE6CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 37 | {C16927E7-1358-4B9D-BDD7-149E505DE6CC}.Debug|Any CPU.Build.0 = Debug|Any CPU 38 | {C16927E7-1358-4B9D-BDD7-149E505DE6CC}.Release|Any CPU.ActiveCfg = Release|Any CPU 39 | {C16927E7-1358-4B9D-BDD7-149E505DE6CC}.Release|Any CPU.Build.0 = Release|Any CPU 40 | {82DADDB0-4A69-4E19-82AD-E73ABC8F1B4A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 41 | {82DADDB0-4A69-4E19-82AD-E73ABC8F1B4A}.Debug|Any CPU.Build.0 = Debug|Any CPU 42 | {82DADDB0-4A69-4E19-82AD-E73ABC8F1B4A}.Release|Any CPU.ActiveCfg = Release|Any CPU 43 | {82DADDB0-4A69-4E19-82AD-E73ABC8F1B4A}.Release|Any CPU.Build.0 = Release|Any CPU 44 | {BBF5805B-560C-474B-885B-9202281B42F7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 45 | {BBF5805B-560C-474B-885B-9202281B42F7}.Debug|Any CPU.Build.0 = Debug|Any CPU 46 | {BBF5805B-560C-474B-885B-9202281B42F7}.Release|Any CPU.ActiveCfg = Release|Any CPU 47 | {BBF5805B-560C-474B-885B-9202281B42F7}.Release|Any CPU.Build.0 = Release|Any CPU 48 | EndGlobalSection 49 | GlobalSection(SolutionProperties) = preSolution 50 | HideSolutionNode = FALSE 51 | EndGlobalSection 52 | GlobalSection(ExtensibilityGlobals) = postSolution 53 | SolutionGuid = {EBCBC23C-D682-4818-A7AA-4142BF6989C2} 54 | EndGlobalSection 55 | EndGlobal 56 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicLinq/Constants.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace PoweredSoft.DynamicLinq 9 | { 10 | public enum ConditionOperators 11 | { 12 | Equal, 13 | NotEqual, 14 | GreaterThan, 15 | GreaterThanOrEqual, 16 | LessThan, 17 | LessThanOrEqual, 18 | Contains, 19 | NotContains, 20 | StartsWith, 21 | EndsWith, 22 | In, 23 | NotIn 24 | } 25 | 26 | public enum QueryConvertStrategy 27 | { 28 | LeaveAsIs, 29 | ConvertConstantToComparedPropertyOrField, 30 | SpecifyType 31 | } 32 | 33 | public enum QueryCollectionHandling 34 | { 35 | Any, 36 | All 37 | } 38 | 39 | public enum QueryOrderByDirection 40 | { 41 | Ascending, 42 | Descending 43 | } 44 | 45 | public enum SelectTypes 46 | { 47 | Key, 48 | Count, 49 | LongCount, 50 | Sum, 51 | Average, 52 | ToList, 53 | Path, 54 | Min, 55 | Max, 56 | LastOrDefault, 57 | FirstOrDefault, 58 | Last, 59 | First, 60 | } 61 | 62 | public enum SelectCollectionHandling 63 | { 64 | LeaveAsIs, 65 | Flatten 66 | } 67 | 68 | internal static class Constants 69 | { 70 | internal static readonly MethodInfo GroupByMethod = typeof(Queryable).GetMethods().First(t => t.Name == "GroupBy"); 71 | internal static readonly MethodInfo GroupByMethodWithEqualityComparer = typeof(Queryable).GetMethods().First(t => t.Name == "GroupBy" && t.GetParameters().Any(t2 => t2.Name == "comparer")); 72 | internal static readonly MethodInfo StringEqualWithComparisation = typeof(string).GetMethod("Equals", new Type[] { typeof(string), typeof(StringComparison) }); 73 | internal static readonly MethodInfo ContainsMethod = typeof(string).GetMethod("Contains", new Type[] { typeof(string) }); 74 | internal static readonly MethodInfo StartsWithMethod = typeof(string).GetMethod("StartsWith", new Type[] { typeof(string) }); 75 | internal static readonly MethodInfo StartsWithMethodWithComparisation = typeof(string).GetMethod("StartsWith", new Type[] { typeof(string), typeof(StringComparison) }); 76 | internal static readonly MethodInfo EndsWithMethod = typeof(string).GetMethod("EndsWith", new Type[] { typeof(string) }); 77 | internal static readonly MethodInfo EndsWithMethodWithComparisation = typeof(string).GetMethod("EndsWith", new Type[] { typeof(string), typeof(StringComparison) }); 78 | internal static readonly MethodInfo IndexOfMethod = typeof(string).GetMethod("IndexOf", new Type[] { typeof(string), typeof(StringComparison) }); 79 | internal static readonly MethodInfo AnyMethod = typeof(Enumerable).GetMethods(BindingFlags.Static | BindingFlags.Public).First(t => t.Name == "Any" && t.GetParameters().Count() == 2); 80 | internal static readonly MethodInfo AllMethod = typeof(Enumerable).GetMethods(BindingFlags.Static | BindingFlags.Public).First(t => t.Name == "All" && t.GetParameters().Count() == 2); 81 | internal static readonly MethodInfo CompareToMethod = typeof(string).GetMethod("CompareTo", new Type[] { typeof(string) }); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicLinq/DynamicType/DynamicClass.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Dynamic; 3 | using System.Reflection; 4 | 5 | namespace PoweredSoft.DynamicLinq 6 | { 7 | /// 8 | /// https://github.com/StefH/System.Linq.Dynamic.Core/blob/master/src/System.Linq.Dynamic.Core/DynamicClass.cs 9 | /// 10 | public abstract class DynamicClass : DynamicObject 11 | { 12 | private Dictionary _propertiesDictionary; 13 | 14 | private Dictionary Properties 15 | { 16 | get 17 | { 18 | if (_propertiesDictionary == null) 19 | { 20 | _propertiesDictionary = new Dictionary(); 21 | 22 | foreach (PropertyInfo pi in GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance)) 23 | { 24 | int parameters = pi.GetIndexParameters().Length; 25 | if (parameters > 0) 26 | { 27 | // The property is an indexer, skip this. 28 | continue; 29 | } 30 | 31 | _propertiesDictionary.Add(pi.Name, pi.GetValue(this, null)); 32 | } 33 | } 34 | 35 | return _propertiesDictionary; 36 | } 37 | } 38 | 39 | /// 40 | /// Gets the dynamic property by name. 41 | /// 42 | /// The type. 43 | /// Name of the property. 44 | /// T 45 | public T GetDynamicPropertyValue(string propertyName) 46 | { 47 | var type = GetType(); 48 | var propInfo = type.GetProperty(propertyName); 49 | 50 | return (T)propInfo.GetValue(this, null); 51 | } 52 | 53 | /// 54 | /// Gets the dynamic property value by name. 55 | /// 56 | /// Name of the property. 57 | /// value 58 | public object GetDynamicPropertyValue(string propertyName) 59 | { 60 | return GetDynamicPropertyValue(propertyName); 61 | } 62 | 63 | /// 64 | /// Sets the dynamic property value by name. 65 | /// 66 | /// The type. 67 | /// Name of the property. 68 | /// The value. 69 | public void SetDynamicPropertyValue(string propertyName, T value) 70 | { 71 | var type = GetType(); 72 | var propInfo = type.GetProperty(propertyName); 73 | 74 | propInfo.SetValue(this, value, null); 75 | } 76 | 77 | /// 78 | /// Sets the dynamic property value by name. 79 | /// 80 | /// Name of the property. 81 | /// The value. 82 | public void SetDynamicPropertyValue(string propertyName, object value) 83 | { 84 | SetDynamicPropertyValue(propertyName, value); 85 | } 86 | 87 | /// 88 | /// Gets or sets the with the specified name. 89 | /// 90 | /// The . 91 | /// The name. 92 | /// Value from the property. 93 | public object this[string name] 94 | { 95 | get 96 | { 97 | if (Properties.TryGetValue(name, out object result)) 98 | { 99 | return result; 100 | } 101 | 102 | return null; 103 | } 104 | 105 | set 106 | { 107 | if (Properties.ContainsKey(name)) 108 | { 109 | Properties[name] = value; 110 | } 111 | else 112 | { 113 | Properties.Add(name, value); 114 | } 115 | } 116 | } 117 | 118 | /// 119 | /// Returns the enumeration of all dynamic member names. 120 | /// 121 | /// 122 | /// A sequence that contains dynamic member names. 123 | /// 124 | public override IEnumerable GetDynamicMemberNames() 125 | { 126 | return Properties.Keys; 127 | } 128 | 129 | /// 130 | /// Provides the implementation for operations that get member values. Classes derived from the class can override this method to specify dynamic behavior for operations such as getting a value for a property. 131 | /// 132 | /// Provides information about the object that called the dynamic operation. The binder.Name property provides the name of the member on which the dynamic operation is performed. For example, for the Console.WriteLine(sampleObject.SampleProperty) statement, where sampleObject is an instance of the class derived from the class, binder.Name returns "SampleProperty". The binder.IgnoreCase property specifies whether the member name is case-sensitive. 133 | /// The result of the get operation. For example, if the method is called for a property, you can assign the property value to . 134 | /// 135 | /// true if the operation is successful; otherwise, false. If this method returns false, the run-time binder of the language determines the behavior. (In most cases, a run-time exception is thrown.) 136 | /// 137 | public override bool TryGetMember(GetMemberBinder binder, out object result) 138 | { 139 | string name = binder.Name; 140 | Properties.TryGetValue(name, out result); 141 | 142 | return true; 143 | } 144 | 145 | /// 146 | /// Provides the implementation for operations that set member values. Classes derived from the class can override this method to specify dynamic behavior for operations such as setting a value for a property. 147 | /// 148 | /// Provides information about the object that called the dynamic operation. The binder.Name property provides the name of the member to which the value is being assigned. For example, for the statement sampleObject.SampleProperty = "Test", where sampleObject is an instance of the class derived from the class, binder.Name returns "SampleProperty". The binder.IgnoreCase property specifies whether the member name is case-sensitive. 149 | /// The value to set to the member. For example, for sampleObject.SampleProperty = "Test", where sampleObject is an instance of the class derived from the class, the is "Test". 150 | /// 151 | /// true if the operation is successful; otherwise, false. If this method returns false, the run-time binder of the language determines the behavior. (In most cases, a language-specific run-time exception is thrown.) 152 | /// 153 | public override bool TrySetMember(SetMemberBinder binder, object value) 154 | { 155 | string name = binder.Name; 156 | if (Properties.ContainsKey(name)) 157 | { 158 | Properties[name] = value; 159 | } 160 | else 161 | { 162 | Properties.Add(name, value); 163 | } 164 | 165 | return true; 166 | } 167 | } 168 | } -------------------------------------------------------------------------------- /PoweredSoft.DynamicLinq/Extensions/EnumerableExtensions.cs: -------------------------------------------------------------------------------- 1 | using PoweredSoft.DynamicLinq.Fluent; 2 | using System; 3 | using System.Collections; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | 8 | namespace PoweredSoft.DynamicLinq 9 | { 10 | public static class EnumerableExtensions 11 | { 12 | public static IEnumerable Where(this IEnumerable list, string path, ConditionOperators conditionOperator, object value, 13 | QueryConvertStrategy convertStrategy = QueryConvertStrategy.ConvertConstantToComparedPropertyOrField, 14 | QueryCollectionHandling collectionHandling = QueryCollectionHandling.Any, StringComparison? stringComparision = null) 15 | => list.AsQueryable().Where(path, conditionOperator, value, convertStrategy: convertStrategy, collectionHandling: collectionHandling, stringComparision: stringComparision); 16 | 17 | public static IEnumerable Where(this IEnumerable list, Action callback) 18 | => list.Query(callback); 19 | 20 | public static IEnumerable Query(this IEnumerable list, Action callback) 21 | => list.AsQueryable().Query(callback); 22 | 23 | public static IEnumerable Sort(this IEnumerable list, string path, QueryOrderByDirection sortDirection, bool appendSort) 24 | => list.AsQueryable().OrderBy(path, sortDirection, appendSort); 25 | 26 | public static IEnumerable OrderBy(this IEnumerable list, string path) 27 | => list.AsQueryable().OrderBy(path); 28 | 29 | public static IEnumerable OrderByDescending(this IEnumerable list, string path) 30 | => list.AsQueryable().OrderByDescending(path); 31 | 32 | public static IEnumerable ThenBy(this IEnumerable list, string path) 33 | => list.AsQueryable().ThenBy(path); 34 | 35 | public static IEnumerable ThenByDescending(this IEnumerable list, string path) 36 | => list.AsQueryable().ThenByDescending(path); 37 | 38 | public static IQueryable GroupBy(this IEnumerable list, string path) 39 | => list.AsQueryable().GroupBy(typeof(T), path); 40 | 41 | public static IQueryable GroupBy(this IEnumerable list, Type type, string path) 42 | => list.AsQueryable().GroupBy(type, path); 43 | 44 | public static IQueryable GroupBy(this IEnumerable list, Action callback) 45 | => list.AsQueryable().GroupBy(typeof(T), callback); 46 | 47 | public static IQueryable GroupBy(this IEnumerable list, Type type, Action callback) 48 | => list.AsQueryable().GroupBy(type, callback); 49 | 50 | public static IQueryable EmptyGroupBy(this IEnumerable list, Type underlyingType) 51 | => list.AsQueryable().EmptyGroupBy(underlyingType); 52 | 53 | public static List Reversed(this List list) 54 | { 55 | var copy = list.ToList(); 56 | copy.Reverse(); 57 | return copy; 58 | } 59 | 60 | public delegate void ForEachDelegate(T element, int index); 61 | 62 | public static void ForEach(this List list, ForEachDelegate callback) 63 | { 64 | for (var i = 0; i < list.Count; i++) 65 | callback(list[i], i); 66 | } 67 | 68 | public static void ReversedForEach(this List list, Action action) 69 | { 70 | list.Reversed().ForEach(action); 71 | } 72 | 73 | public static void ReversedForEach(this List list, ForEachDelegate callback) 74 | { 75 | for (var i = list.Count - 1; i >= 0; i--) 76 | callback(list[i], i); 77 | } 78 | 79 | 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicLinq/Extensions/QueryableExtensions.cs: -------------------------------------------------------------------------------- 1 | using PoweredSoft.DynamicLinq.Fluent; 2 | using PoweredSoft.DynamicLinq.Helpers; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Linq.Expressions; 7 | using System.Reflection; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | 11 | namespace PoweredSoft.DynamicLinq 12 | { 13 | public static class QueryableExtensions 14 | { 15 | public static IQueryable Where(this IQueryable query, string path, ConditionOperators conditionOperator, object value, 16 | QueryConvertStrategy convertStrategy = QueryConvertStrategy.ConvertConstantToComparedPropertyOrField, 17 | QueryCollectionHandling collectionHandling = QueryCollectionHandling.Any, StringComparison? stringComparision = null) 18 | { 19 | query = query.Query(qb => qb.Compare(path, conditionOperator, value, convertStrategy: convertStrategy, collectionHandling: collectionHandling, stringComparision: stringComparision)); 20 | return query; 21 | } 22 | 23 | public static IQueryable Where(this IQueryable query, string path, ConditionOperators conditionOperator, object value, 24 | QueryConvertStrategy convertStrategy = QueryConvertStrategy.ConvertConstantToComparedPropertyOrField, 25 | QueryCollectionHandling collectionHandling = QueryCollectionHandling.Any, StringComparison? stringComparision = null) 26 | { 27 | query = query.Query(qb => qb.Compare(path, conditionOperator, value, convertStrategy: convertStrategy, collectionHandling: collectionHandling, stringComparision: stringComparision)); 28 | return query; 29 | } 30 | 31 | public static IQueryable Where(this IQueryable query, Action callback) 32 | => query.Query(callback); 33 | 34 | public static IQueryable Query(this IQueryable query, Action callback) 35 | { 36 | var queryBuilder = new WhereBuilder(query); 37 | callback(queryBuilder); 38 | var ret = queryBuilder.Build(); 39 | return (IQueryable)ret; 40 | } 41 | 42 | // non generics were missing. 43 | public static IQueryable Where(this IQueryable query, Action callback) 44 | => query.Query(callback); 45 | 46 | public static IQueryable Query(this IQueryable query, Action callback) 47 | { 48 | var queryBuilder = new WhereBuilder(query); 49 | callback(queryBuilder); 50 | var ret = queryBuilder.Build(); 51 | return ret; 52 | } 53 | 54 | // generic. 55 | public static IQueryable OrderBy(this IQueryable query, string path, QueryOrderByDirection direction, bool append) 56 | { 57 | IQueryable queryable = query; 58 | query = queryable.OrderBy(path, direction, append) as IQueryable; 59 | return query; 60 | } 61 | 62 | public static IQueryable OrderBy(this IQueryable query, string path) 63 | => query.OrderBy(path, QueryOrderByDirection.Ascending, false); 64 | 65 | public static IQueryable OrderByDescending(this IQueryable query, string path) 66 | => query.OrderBy(path, QueryOrderByDirection.Descending, false); 67 | 68 | public static IQueryable ThenBy(this IQueryable query, string path) 69 | => query.OrderBy(path, QueryOrderByDirection.Ascending, true); 70 | 71 | public static IQueryable ThenByDescending(this IQueryable query, string path) 72 | => query.OrderBy(path, QueryOrderByDirection.Descending, true); 73 | 74 | // non generic. 75 | public static IQueryable OrderBy(this IQueryable query, string path, QueryOrderByDirection direction, bool append) 76 | { 77 | var qb = new OrderByBuilder(query); 78 | qb.OrderBy(path, direction, append); 79 | var ret = qb.Build(); 80 | return ret; 81 | } 82 | 83 | public static IQueryable OrderBy(this IQueryable query, string path) 84 | => query.OrderBy(path, QueryOrderByDirection.Ascending, false); 85 | 86 | public static IQueryable OrderByDescending(this IQueryable query, string path) 87 | => query.OrderBy(path, QueryOrderByDirection.Descending, false); 88 | 89 | public static IQueryable ThenBy(this IQueryable query, string path) 90 | => query.OrderBy(path, QueryOrderByDirection.Ascending, true); 91 | 92 | public static IQueryable ThenByDescending(this IQueryable query, string path) 93 | => query.OrderBy(path, QueryOrderByDirection.Descending, true); 94 | 95 | // group by 96 | public static IQueryable GroupBy(this IQueryable query, string path) 97 | => QueryableHelpers.GroupBy(query, typeof(T), path); 98 | 99 | public static IQueryable GroupBy(this IQueryable query, Type type, string path) 100 | => QueryableHelpers.GroupBy(query, type, path); 101 | 102 | public static IQueryable GroupBy(this IQueryable query, Action callback) 103 | => query.GroupBy(typeof(T), callback); 104 | 105 | public static IQueryable GroupBy(this IQueryable query, Type type, Action callback) 106 | { 107 | var groupBuilder = new GroupBuilder(query); 108 | callback(groupBuilder); 109 | var ret = groupBuilder.Build(); 110 | return ret; 111 | } 112 | 113 | public static IQueryable Select(this IQueryable query, Action callback) 114 | { 115 | var sb = new SelectBuilder(query); 116 | callback(sb); 117 | var ret = sb.Build(); 118 | return ret; 119 | } 120 | 121 | public static List ToObjectList(this IQueryable query) 122 | { 123 | // Expression call tolist? 124 | var ret = new List(); 125 | foreach (var o in query) 126 | ret.Add(o); 127 | 128 | return ret; 129 | } 130 | 131 | public static List ToDynamicList(this IQueryable query) 132 | { 133 | var ret = new List(); 134 | foreach (var o in query) 135 | ret.Add(o); 136 | 137 | return ret; 138 | } 139 | public static List ToDynamicClassList(this IQueryable query) 140 | { 141 | if (!typeof(DynamicClass).IsAssignableFrom(query.ElementType)) 142 | throw new Exception($"{query.ElementType} does not inherit DynamicClass"); 143 | 144 | var ret = query.Cast().ToList(); 145 | return ret; 146 | } 147 | 148 | private static MethodInfo _internalCount = typeof(QueryableExtensions).GetMethod(nameof(QueryableExtensions.InternalCount), BindingFlags.Static | BindingFlags.NonPublic); 149 | private static int InternalCount(IQueryable q) => System.Linq.Queryable.Count(q); 150 | public static int Count(this IQueryable query) => (int)_internalCount.MakeGenericMethod(query.ElementType).Invoke(null, new object[] {query}); 151 | 152 | private static MethodInfo _internalLongCount = typeof(QueryableExtensions).GetMethod(nameof(QueryableExtensions.InternalLongCount), BindingFlags.Static | BindingFlags.NonPublic); 153 | private static long InternalLongCount(IQueryable q) => System.Linq.Queryable.LongCount(q); 154 | public static long LongCount(this IQueryable query) 155 | { 156 | var method =_internalLongCount.MakeGenericMethod(query.ElementType); 157 | var result = method.Invoke(null, new object[] {query}); 158 | return (long) result; 159 | } 160 | 161 | public static IQueryable EmptyGroupBy(this IQueryable queryable, Type underlyingType) 162 | { 163 | var parameter = Expression.Parameter(underlyingType); 164 | var genericMethod = Constants.GroupByMethod.MakeGenericMethod(underlyingType, typeof(bool)); 165 | var trueConstant = Expression.Constant(true); 166 | var lambda = Expression.Lambda(trueConstant, parameter); 167 | var groupByExpression = Expression.Call(genericMethod, queryable.Expression, lambda); 168 | var result = queryable.Provider.CreateQuery(groupByExpression); 169 | return result; 170 | } 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicLinq/Fluent/Group/GroupBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Linq; 5 | using PoweredSoft.DynamicLinq.Helpers; 6 | 7 | namespace PoweredSoft.DynamicLinq.Fluent 8 | { 9 | public class GroupBuilder : IQueryBuilder 10 | { 11 | public List<(string path, string propertyName)> Parts { get; set; } = new List<(string path, string propertyName)>(); 12 | public Type Type { get; set; } 13 | public bool Empty => !Parts.Any(); 14 | public Type EqualityComparerType { get; set; } 15 | 16 | public IQueryable Query { get; protected set; } 17 | 18 | public bool IsNullCheckingEnabled { get; protected set; } = false; 19 | 20 | public GroupBuilder(IQueryable query) 21 | { 22 | Query = query; 23 | } 24 | 25 | public GroupBuilder Path(string path, string propertyName = null) 26 | { 27 | if (propertyName == null) 28 | { 29 | var name = path; 30 | if (name.Contains(".")) 31 | { 32 | var parts = name.Split('.'); 33 | name = parts[parts.Length - 1]; // the last one. 34 | } 35 | 36 | if (Parts.Any(t => t.propertyName == name)) 37 | throw new Exception($"{name} is already taken by another group part, you can specify a property name instead to resolve this issue"); 38 | 39 | propertyName = name; 40 | } 41 | 42 | Parts.Add((path, propertyName)); 43 | return this; 44 | } 45 | 46 | public GroupBuilder NullChecking(bool nullChecking = true) 47 | { 48 | IsNullCheckingEnabled = nullChecking; 49 | return this; 50 | } 51 | 52 | public GroupBuilder UseType(Type type) 53 | { 54 | Type = type; 55 | return this; 56 | } 57 | 58 | public GroupBuilder EqualityComparer(Type type) 59 | { 60 | EqualityComparerType = type; 61 | return this; 62 | } 63 | 64 | public virtual IQueryable Build() 65 | { 66 | if (Empty) 67 | throw new Exception("No group specified, please specify at least one group"); 68 | 69 | var ret = QueryableHelpers.GroupBy(Query, Query.ElementType, Parts, groupToType: Type, equalityCompareType: EqualityComparerType, nullChecking: IsNullCheckingEnabled); 70 | return ret; 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicLinq/Fluent/OrderBy/OrderByBuilder.cs: -------------------------------------------------------------------------------- 1 | using PoweredSoft.DynamicLinq.Helpers; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | 7 | namespace PoweredSoft.DynamicLinq.Fluent 8 | { 9 | public class OrderByBuilder : IQueryBuilder 10 | { 11 | public IQueryable Query { get; } 12 | 13 | public OrderByBuilder(IQueryable query) 14 | { 15 | Query = query; 16 | } 17 | 18 | public virtual IQueryable Build() 19 | { 20 | var query = Query; 21 | 22 | Sorts.ForEach(sort => 23 | { 24 | query = QueryableHelpers.CreateOrderByExpression(query, sort.Path, sort.Direction, sort.Append); 25 | }); 26 | 27 | return query; 28 | } 29 | 30 | public List Sorts { get; protected set; } = new List(); 31 | 32 | public virtual OrderByBuilder OrderBy(string path, QueryOrderByDirection direction, bool append) 33 | { 34 | if (append == false) 35 | Sorts.Clear(); 36 | 37 | Sorts.Add(new OrderByPart 38 | { 39 | Path = path, 40 | Direction = direction, 41 | Append = append 42 | }); 43 | return this; 44 | } 45 | 46 | #region shortcuts 47 | public virtual OrderByBuilder OrderBy(string path) 48 | => OrderBy(path, QueryOrderByDirection.Ascending, false); 49 | 50 | public virtual OrderByBuilder OrderByDescending(string path) 51 | => OrderBy(path, QueryOrderByDirection.Descending, false); 52 | 53 | public virtual OrderByBuilder ThenBy(string path) 54 | => OrderBy(path, QueryOrderByDirection.Ascending, true); 55 | 56 | public virtual OrderByBuilder ThenByDescending(string path) 57 | => OrderBy(path, QueryOrderByDirection.Descending, true); 58 | #endregion 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicLinq/Fluent/OrderBy/OrderByPart.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace PoweredSoft.DynamicLinq.Fluent 8 | { 9 | public class OrderByPart 10 | { 11 | public string Path { get; set; } 12 | 13 | public QueryOrderByDirection Direction { get; set; } = QueryOrderByDirection.Ascending; 14 | 15 | public bool Append { get; set; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicLinq/Fluent/Select/SelectBuilder.cs: -------------------------------------------------------------------------------- 1 | using PoweredSoft.DynamicLinq.Helpers; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | 7 | namespace PoweredSoft.DynamicLinq.Fluent 8 | { 9 | public class SelectPart 10 | { 11 | public string Path { get; set; } 12 | public string PropertyName { get; set; } 13 | public SelectTypes SelectType { get; set; } 14 | public SelectCollectionHandling SelectCollectionHandling { get; set; } 15 | } 16 | 17 | public class SelectBuilder : IQueryBuilder 18 | { 19 | public List Parts = new List(); 20 | public Type DestinationType { get; set; } 21 | public bool Empty => Parts?.Count == 0; 22 | public IQueryable Query { get; protected set; } 23 | public bool IsNullCheckingEnabled { get; protected set; } 24 | 25 | public SelectBuilder(IQueryable query) 26 | { 27 | Query = query; 28 | } 29 | 30 | public SelectBuilder NullChecking(bool check = true) 31 | { 32 | IsNullCheckingEnabled = check; 33 | return this; 34 | } 35 | 36 | protected void ThrowIfUsedOrEmpty(string propertyName) 37 | { 38 | if (string.IsNullOrEmpty(propertyName)) 39 | throw new ArgumentNullException($"{propertyName} cannot end up be empty."); 40 | 41 | if (Parts.Any(t => t.PropertyName == propertyName)) 42 | throw new Exception($"{propertyName} is already used"); 43 | } 44 | 45 | public SelectBuilder Aggregate(string path, SelectTypes type, string propertyName = null, SelectCollectionHandling selectCollectionHandling = SelectCollectionHandling.LeaveAsIs) 46 | { 47 | if (propertyName == null && path == null) 48 | throw new Exception("if property name is not specified, a path must be supplied."); 49 | 50 | if (propertyName == null) 51 | propertyName = path.Split('.').LastOrDefault(); 52 | 53 | ThrowIfUsedOrEmpty(propertyName); 54 | 55 | Parts.Add(new SelectPart 56 | { 57 | Path = path, 58 | PropertyName = propertyName, 59 | SelectType = type, 60 | SelectCollectionHandling = selectCollectionHandling 61 | }); 62 | 63 | return this; 64 | } 65 | 66 | public SelectBuilder Key(string propertyName, string path = null) => Aggregate(path == null ? "Key" : $"Key.{path}", SelectTypes.Key, propertyName); 67 | public SelectBuilder Path(string path, string propertyName = null) => Aggregate(path, SelectTypes.Path, propertyName); 68 | public SelectBuilder Count(string propertyName) => Aggregate(null, SelectTypes.Count, propertyName); 69 | public SelectBuilder LongCount(string propertyName) => Aggregate(null, SelectTypes.LongCount, propertyName); 70 | public SelectBuilder Sum(string path, string propertyName = null) => Aggregate(path, SelectTypes.Sum, propertyName); 71 | public SelectBuilder Average(string path, string propertyName = null) => Aggregate(path, SelectTypes.Average, propertyName); 72 | public SelectBuilder Min(string path, string propertyName = null) => Aggregate(path, SelectTypes.Min, propertyName); 73 | public SelectBuilder Max(string path, string propertyName = null) => Aggregate(path, SelectTypes.Max, propertyName); 74 | public SelectBuilder ToList(string propertyName) => Aggregate(null, SelectTypes.ToList, propertyName); 75 | public SelectBuilder LastOrDefault(string propertyName) => Aggregate(null, SelectTypes.LastOrDefault, propertyName); 76 | public SelectBuilder FirstOrDefault(string propertyName) => Aggregate(null, SelectTypes.FirstOrDefault, propertyName); 77 | public SelectBuilder Last(string propertyName) => Aggregate(null, SelectTypes.Last, propertyName); 78 | public SelectBuilder First(string propertyName) => Aggregate(null, SelectTypes.First, propertyName); 79 | 80 | [System.Obsolete("Use ToList instead", true)] 81 | public SelectBuilder PathToList(string path, string propertyName = null, SelectCollectionHandling selectCollectionHandling = SelectCollectionHandling.LeaveAsIs) 82 | => ToList(path, propertyName, selectCollectionHandling); 83 | 84 | public SelectBuilder ToList(string path, string propertyName = null, SelectCollectionHandling selectCollectionHandling = SelectCollectionHandling.LeaveAsIs) 85 | => Aggregate(path, SelectTypes.ToList, propertyName: propertyName, selectCollectionHandling: selectCollectionHandling); 86 | 87 | public SelectBuilder First(string path, string propertyName = null, SelectCollectionHandling selectCollectionHandling = SelectCollectionHandling.LeaveAsIs) 88 | => Aggregate(path, SelectTypes.First, propertyName: propertyName, selectCollectionHandling: selectCollectionHandling); 89 | 90 | public SelectBuilder FirstOrDefault(string path, string propertyName = null, SelectCollectionHandling selectCollectionHandling = SelectCollectionHandling.LeaveAsIs) 91 | => Aggregate(path, SelectTypes.FirstOrDefault, propertyName: propertyName, selectCollectionHandling: selectCollectionHandling); 92 | 93 | public SelectBuilder Last(string path, string propertyName = null, SelectCollectionHandling selectCollectionHandling = SelectCollectionHandling.LeaveAsIs) 94 | => Aggregate(path, SelectTypes.Last, propertyName: propertyName, selectCollectionHandling: selectCollectionHandling); 95 | 96 | public SelectBuilder LastOrDefault(string path, string propertyName = null, SelectCollectionHandling selectCollectionHandling = SelectCollectionHandling.LeaveAsIs) 97 | => Aggregate(path, SelectTypes.LastOrDefault, propertyName: propertyName, selectCollectionHandling: selectCollectionHandling); 98 | 99 | public virtual IQueryable Build() 100 | { 101 | if (Empty) 102 | throw new Exception("No select specified, please specify at least one select path"); 103 | 104 | var partsTuple = Parts.Select(t => (selectType: t.SelectType, propertyName: t.PropertyName, path: t.Path, selectCollectionHandling: t.SelectCollectionHandling)).ToList(); 105 | return QueryableHelpers.Select(Query, partsTuple, DestinationType, nullChecking: IsNullCheckingEnabled); 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicLinq/Fluent/Where/WhereBuilder.cs: -------------------------------------------------------------------------------- 1 | using PoweredSoft.DynamicLinq.Helpers; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Linq.Expressions; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace PoweredSoft.DynamicLinq.Fluent 10 | { 11 | public partial class WhereBuilder : IQueryBuilder 12 | { 13 | public IQueryable Query { get; set; } 14 | public Type QueryableType { get; set; } 15 | public List Filters { get; protected set; } = new List(); 16 | 17 | public WhereBuilder(IQueryable query) 18 | { 19 | Query = query; 20 | QueryableType = query.ElementType; 21 | } 22 | 23 | public bool IsNullCheckingEnabled { get; protected set; } = false; 24 | 25 | public virtual WhereBuilder NullChecking(bool check = true) 26 | { 27 | IsNullCheckingEnabled = check; 28 | return this; 29 | } 30 | 31 | public virtual WhereBuilder Compare(string path, ConditionOperators conditionOperators, object value, 32 | QueryConvertStrategy convertStrategy = QueryConvertStrategy.ConvertConstantToComparedPropertyOrField, 33 | bool and = true, QueryCollectionHandling collectionHandling = QueryCollectionHandling.Any, StringComparison? stringComparision = null, bool negate = false) 34 | { 35 | Filters.Add(new WhereBuilderCondition 36 | { 37 | And = and, 38 | ConditionOperator = conditionOperators, 39 | Path = path, 40 | Value = value, 41 | ConvertStrategy = convertStrategy, 42 | CollectionHandling = collectionHandling, 43 | StringComparisation = stringComparision, 44 | Negate = negate 45 | }); 46 | 47 | return this; 48 | } 49 | 50 | public virtual WhereBuilder SubQuery(Action subQuery, bool and = true) 51 | { 52 | // create query builder for same type. 53 | var qb = new WhereBuilder(Query); 54 | qb.NullChecking(IsNullCheckingEnabled); 55 | 56 | // callback. 57 | subQuery(qb); 58 | 59 | // create a query part. 60 | var part = new WhereBuilderCondition(); 61 | part.And = and; 62 | part.Conditions = qb.Filters; 63 | Filters.Add(part); 64 | 65 | //return self. 66 | return this; 67 | } 68 | 69 | public virtual IQueryable Build() 70 | { 71 | // the query. 72 | var query = Query; 73 | 74 | if (Filters == null || Filters?.Count() == 0) 75 | return query; 76 | 77 | // shared parameter. 78 | var sharedParameter = Expression.Parameter(QueryableType, "t"); 79 | 80 | // build the expression. 81 | var filterExpressionMerged = BuildConditionExpression(sharedParameter, Filters); 82 | 83 | // create the where expression. 84 | var whereExpression = Expression.Call(typeof(Queryable), "Where", new[] { query.ElementType }, query.Expression, filterExpressionMerged); 85 | 86 | // lets see what happens here. 87 | query = query.Provider.CreateQuery(whereExpression); 88 | 89 | return query; 90 | } 91 | 92 | protected virtual Expression BuildConditionExpression(ParameterExpression parameter, List filters) 93 | { 94 | Expression temp = null; 95 | 96 | filters.ForEach(filter => 97 | { 98 | Expression innerExpression; 99 | if (filter.Conditions?.Any() == true) 100 | innerExpression = BuildConditionExpression(parameter, filter.Conditions); 101 | else 102 | innerExpression = BuildConditionExpression(parameter, filter); 103 | 104 | if (temp == null) 105 | { 106 | temp = innerExpression; 107 | } 108 | else 109 | { 110 | var body = ((LambdaExpression)temp).Body; 111 | var innerEpressionBody = ((LambdaExpression)innerExpression).Body; 112 | 113 | if (filter.And) 114 | temp = Expression.Lambda(Expression.AndAlso(body, innerEpressionBody), parameter); 115 | else 116 | temp = Expression.Lambda(Expression.OrElse(body, innerEpressionBody), parameter); 117 | } 118 | 119 | }); 120 | 121 | return temp; 122 | } 123 | 124 | protected virtual Expression BuildConditionExpression(ParameterExpression parameter, WhereBuilderCondition filter) 125 | { 126 | var ret = QueryableHelpers.CreateConditionExpression( 127 | parameter.Type, 128 | filter.Path, 129 | filter.ConditionOperator, 130 | filter.Value, 131 | filter.ConvertStrategy, 132 | filter.CollectionHandling, 133 | parameter: parameter, 134 | nullChecking: IsNullCheckingEnabled, 135 | stringComparision: filter.StringComparisation, 136 | negate: filter.Negate 137 | ); 138 | 139 | return ret; 140 | } 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicLinq/Fluent/Where/WhereBuilder.shortcuts.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace PoweredSoft.DynamicLinq.Fluent 6 | { 7 | public partial class WhereBuilder 8 | { 9 | public WhereBuilder And(string path, ConditionOperators conditionOperator, object value, 10 | QueryConvertStrategy convertStrategy = QueryConvertStrategy.ConvertConstantToComparedPropertyOrField, QueryCollectionHandling collectionHandling = QueryCollectionHandling.Any, StringComparison? stringComparision = null, bool negate = false) 11 | => Compare(path, conditionOperator, value, convertStrategy: convertStrategy, collectionHandling: collectionHandling, and: true, stringComparision: stringComparision, negate: negate); 12 | 13 | public WhereBuilder Or(string path, ConditionOperators conditionOperator, object value, 14 | QueryConvertStrategy convertStrategy = QueryConvertStrategy.ConvertConstantToComparedPropertyOrField, QueryCollectionHandling collectionHandling = QueryCollectionHandling.Any, StringComparison? stringComparision = null, bool negate = false) 15 | => Compare(path, conditionOperator, value, convertStrategy: convertStrategy, collectionHandling: collectionHandling, and: false, stringComparision: stringComparision, negate: negate); 16 | 17 | public WhereBuilder And(Action subQuery) 18 | => SubQuery(subQuery, true); 19 | 20 | public WhereBuilder Or(Action subQuery) 21 | => SubQuery(subQuery, false); 22 | 23 | #region equal 24 | public WhereBuilder Equal(string path, object value, QueryConvertStrategy convertStrategy = QueryConvertStrategy.ConvertConstantToComparedPropertyOrField, QueryCollectionHandling collectionHandling = QueryCollectionHandling.Any, StringComparison? stringComparision = null, bool negate = false) 25 | => And(path, ConditionOperators.Equal, value, convertStrategy: convertStrategy, collectionHandling: collectionHandling, stringComparision: stringComparision, negate: negate); 26 | 27 | public WhereBuilder AndEqual(string path, object value, QueryConvertStrategy convertStrategy = QueryConvertStrategy.ConvertConstantToComparedPropertyOrField, QueryCollectionHandling collectionHandling = QueryCollectionHandling.Any, StringComparison? stringComparision = null, bool negate = false) 28 | => And(path, ConditionOperators.Equal, value, convertStrategy: convertStrategy, collectionHandling: collectionHandling, stringComparision: stringComparision, negate: negate); 29 | 30 | public WhereBuilder OrEqual(string path, object value, QueryConvertStrategy convertStrategy = QueryConvertStrategy.ConvertConstantToComparedPropertyOrField, QueryCollectionHandling collectionHandling = QueryCollectionHandling.Any, StringComparison? stringComparision = null, bool negate = false) 31 | => Or(path, ConditionOperators.Equal, value, convertStrategy: convertStrategy, collectionHandling: collectionHandling, stringComparision: stringComparision, negate: negate); 32 | #endregion 33 | 34 | #region not equal 35 | public WhereBuilder NotEqual(string path, object value, QueryConvertStrategy convertStrategy = QueryConvertStrategy.ConvertConstantToComparedPropertyOrField, QueryCollectionHandling collectionHandling = QueryCollectionHandling.Any, StringComparison? stringComparision = null, bool negate = false) 36 | => And(path, ConditionOperators.NotEqual, value, convertStrategy: convertStrategy, collectionHandling: collectionHandling, stringComparision: stringComparision, negate: negate); 37 | 38 | public WhereBuilder AndNotEqual(string path, object value, QueryConvertStrategy convertStrategy = QueryConvertStrategy.ConvertConstantToComparedPropertyOrField, QueryCollectionHandling collectionHandling = QueryCollectionHandling.Any, StringComparison? stringComparision = null, bool negate = false) 39 | => And(path, ConditionOperators.NotEqual, value, convertStrategy: convertStrategy, collectionHandling: collectionHandling, stringComparision: stringComparision, negate: negate); 40 | 41 | public WhereBuilder OrNotEqual(string path, object value, QueryConvertStrategy convertStrategy = QueryConvertStrategy.ConvertConstantToComparedPropertyOrField, QueryCollectionHandling collectionHandling = QueryCollectionHandling.Any, StringComparison? stringComparision = null, bool negate = false) 42 | => Or(path, ConditionOperators.NotEqual, value, convertStrategy: convertStrategy, collectionHandling: collectionHandling, stringComparision: stringComparision, negate: negate); 43 | #endregion 44 | 45 | #region GreaterThan 46 | public WhereBuilder GreaterThan(string path, object value, QueryConvertStrategy convertStrategy = QueryConvertStrategy.ConvertConstantToComparedPropertyOrField, QueryCollectionHandling collectionHandling = QueryCollectionHandling.Any) 47 | => And(path, ConditionOperators.GreaterThan, value, convertStrategy: convertStrategy, collectionHandling: collectionHandling); 48 | 49 | public WhereBuilder AndGreaterThan(string path, object value, QueryConvertStrategy convertStrategy = QueryConvertStrategy.ConvertConstantToComparedPropertyOrField, QueryCollectionHandling collectionHandling = QueryCollectionHandling.Any) 50 | => And(path, ConditionOperators.GreaterThan, value, convertStrategy: convertStrategy, collectionHandling: collectionHandling); 51 | 52 | public WhereBuilder OrGreaterThan(string path, object value, QueryConvertStrategy convertStrategy = QueryConvertStrategy.ConvertConstantToComparedPropertyOrField, QueryCollectionHandling collectionHandling = QueryCollectionHandling.Any) 53 | => Or(path, ConditionOperators.GreaterThan, value, convertStrategy: convertStrategy, collectionHandling: collectionHandling); 54 | #endregion 55 | 56 | #region GreaterThanOrEqual 57 | public WhereBuilder GreaterThanOrEqual(string path, object value, QueryConvertStrategy convertStrategy = QueryConvertStrategy.ConvertConstantToComparedPropertyOrField, QueryCollectionHandling collectionHandling = QueryCollectionHandling.Any) 58 | => And(path, ConditionOperators.GreaterThanOrEqual, value, convertStrategy: convertStrategy, collectionHandling: collectionHandling); 59 | 60 | public WhereBuilder AndGreaterThanOrEqual(string path, object value, QueryConvertStrategy convertStrategy = QueryConvertStrategy.ConvertConstantToComparedPropertyOrField, QueryCollectionHandling collectionHandling = QueryCollectionHandling.Any) 61 | => And(path, ConditionOperators.GreaterThanOrEqual, value, convertStrategy: convertStrategy, collectionHandling: collectionHandling); 62 | 63 | public WhereBuilder OrGreaterThanOrEqual(string path, object value, QueryConvertStrategy convertStrategy = QueryConvertStrategy.ConvertConstantToComparedPropertyOrField, QueryCollectionHandling collectionHandling = QueryCollectionHandling.Any) 64 | => Or(path, ConditionOperators.GreaterThanOrEqual, value, convertStrategy: convertStrategy, collectionHandling: collectionHandling); 65 | #endregion 66 | 67 | #region LessThan 68 | public WhereBuilder LessThan(string path, object value, QueryConvertStrategy convertStrategy = QueryConvertStrategy.ConvertConstantToComparedPropertyOrField, QueryCollectionHandling collectionHandling = QueryCollectionHandling.Any) 69 | => And(path, ConditionOperators.LessThan, value, convertStrategy: convertStrategy, collectionHandling: collectionHandling); 70 | 71 | public WhereBuilder AndLessThan(string path, object value, QueryConvertStrategy convertStrategy = QueryConvertStrategy.ConvertConstantToComparedPropertyOrField, QueryCollectionHandling collectionHandling = QueryCollectionHandling.Any) 72 | => And(path, ConditionOperators.LessThan, value, convertStrategy: convertStrategy, collectionHandling: collectionHandling); 73 | 74 | public WhereBuilder OrLessThan(string path, object value, QueryConvertStrategy convertStrategy = QueryConvertStrategy.ConvertConstantToComparedPropertyOrField, QueryCollectionHandling collectionHandling = QueryCollectionHandling.Any) 75 | => Or(path, ConditionOperators.LessThan, value, convertStrategy: convertStrategy, collectionHandling: collectionHandling); 76 | #endregion 77 | 78 | #region LessThanOrEqual 79 | public WhereBuilder LessThanOrEqual(string path, object value, QueryConvertStrategy convertStrategy = QueryConvertStrategy.ConvertConstantToComparedPropertyOrField, QueryCollectionHandling collectionHandling = QueryCollectionHandling.Any) 80 | => And(path, ConditionOperators.LessThanOrEqual, value, convertStrategy: convertStrategy, collectionHandling: collectionHandling); 81 | 82 | public WhereBuilder AndLessThanOrEqual(string path, object value, QueryConvertStrategy convertStrategy = QueryConvertStrategy.ConvertConstantToComparedPropertyOrField, QueryCollectionHandling collectionHandling = QueryCollectionHandling.Any) 83 | => And(path, ConditionOperators.LessThanOrEqual, value, convertStrategy: convertStrategy, collectionHandling: collectionHandling); 84 | 85 | public WhereBuilder OrLessThanOrEqual(string path, object value, QueryConvertStrategy convertStrategy = QueryConvertStrategy.ConvertConstantToComparedPropertyOrField, QueryCollectionHandling collectionHandling = QueryCollectionHandling.Any) 86 | => Or(path, ConditionOperators.LessThanOrEqual, value, convertStrategy: convertStrategy, collectionHandling: collectionHandling); 87 | #endregion 88 | 89 | #region contains 90 | public WhereBuilder Contains(string path, object value, QueryConvertStrategy convertStrategy = QueryConvertStrategy.ConvertConstantToComparedPropertyOrField, QueryCollectionHandling collectionHandling = QueryCollectionHandling.Any, StringComparison? stringComparision = null, bool negate = false) 91 | => And(path, ConditionOperators.Contains, value, convertStrategy: convertStrategy, collectionHandling: collectionHandling, stringComparision: stringComparision, negate: negate); 92 | 93 | public WhereBuilder AndContains(string path, object value, QueryConvertStrategy convertStrategy = QueryConvertStrategy.ConvertConstantToComparedPropertyOrField, QueryCollectionHandling collectionHandling = QueryCollectionHandling.Any, StringComparison? stringComparision = null, bool negate = false) 94 | => And(path, ConditionOperators.Contains, value, convertStrategy: convertStrategy, collectionHandling: collectionHandling, stringComparision: stringComparision, negate: negate); 95 | 96 | public WhereBuilder OrContains(string path, object value, QueryConvertStrategy convertStrategy = QueryConvertStrategy.ConvertConstantToComparedPropertyOrField, QueryCollectionHandling collectionHandling = QueryCollectionHandling.Any, StringComparison? stringComparision = null, bool negate = false) 97 | => Or(path, ConditionOperators.Contains, value, convertStrategy: convertStrategy, collectionHandling: collectionHandling, stringComparision: stringComparision, negate: negate); 98 | #endregion 99 | 100 | #region not contains 101 | public WhereBuilder NotContains(string path, object value, QueryConvertStrategy convertStrategy = QueryConvertStrategy.ConvertConstantToComparedPropertyOrField, QueryCollectionHandling collectionHandling = QueryCollectionHandling.Any, StringComparison? stringComparision = null, bool negate = false) 102 | => And(path, ConditionOperators.NotContains, value, convertStrategy: convertStrategy, collectionHandling: collectionHandling, stringComparision: stringComparision, negate: negate); 103 | 104 | public WhereBuilder AndNotContains(string path, object value, QueryConvertStrategy convertStrategy = QueryConvertStrategy.ConvertConstantToComparedPropertyOrField, QueryCollectionHandling collectionHandling = QueryCollectionHandling.Any, StringComparison? stringComparision = null, bool negate = false) 105 | => And(path, ConditionOperators.NotContains, value, convertStrategy: convertStrategy, collectionHandling: collectionHandling, stringComparision: stringComparision, negate: negate); 106 | 107 | public WhereBuilder OrNotContains(string path, object value, QueryConvertStrategy convertStrategy = QueryConvertStrategy.ConvertConstantToComparedPropertyOrField, QueryCollectionHandling collectionHandling = QueryCollectionHandling.Any, StringComparison? stringComparision = null, bool negate = false) 108 | => Or(path, ConditionOperators.NotContains, value, convertStrategy: convertStrategy, collectionHandling: collectionHandling, stringComparision: stringComparision, negate: negate); 109 | #endregion 110 | 111 | #region starts with 112 | public WhereBuilder StartsWith(string path, object value, QueryConvertStrategy convertStrategy = QueryConvertStrategy.ConvertConstantToComparedPropertyOrField, QueryCollectionHandling collectionHandling = QueryCollectionHandling.Any, StringComparison? stringComparision = null, bool negate = false) 113 | => And(path, ConditionOperators.StartsWith, value, convertStrategy: convertStrategy, collectionHandling: collectionHandling, stringComparision: stringComparision, negate: negate); 114 | 115 | public WhereBuilder AndStartsWith(string path, object value, QueryConvertStrategy convertStrategy = QueryConvertStrategy.ConvertConstantToComparedPropertyOrField, QueryCollectionHandling collectionHandling = QueryCollectionHandling.Any, StringComparison? stringComparision = null, bool negate = false) 116 | => And(path, ConditionOperators.StartsWith, value, convertStrategy: convertStrategy, collectionHandling: collectionHandling, stringComparision: stringComparision, negate: negate); 117 | 118 | public WhereBuilder OrStartsWith(string path, object value, QueryConvertStrategy convertStrategy = QueryConvertStrategy.ConvertConstantToComparedPropertyOrField, QueryCollectionHandling collectionHandling = QueryCollectionHandling.Any, StringComparison? stringComparision = null, bool negate = false) 119 | => Or(path, ConditionOperators.StartsWith, value, convertStrategy: convertStrategy, collectionHandling: collectionHandling, stringComparision: stringComparision, negate: negate); 120 | #endregion 121 | 122 | #region ends with 123 | public WhereBuilder EndsWith(string path, object value, QueryConvertStrategy convertStrategy = QueryConvertStrategy.ConvertConstantToComparedPropertyOrField, QueryCollectionHandling collectionHandling = QueryCollectionHandling.Any, StringComparison? stringComparision = null, bool negate = false) 124 | => And(path, ConditionOperators.EndsWith, value, convertStrategy: convertStrategy, collectionHandling: collectionHandling, stringComparision: stringComparision, negate: negate); 125 | 126 | public WhereBuilder AndEndsWith(string path, object value, QueryConvertStrategy convertStrategy = QueryConvertStrategy.ConvertConstantToComparedPropertyOrField, QueryCollectionHandling collectionHandling = QueryCollectionHandling.Any, StringComparison? stringComparision = null, bool negate = false) 127 | => And(path, ConditionOperators.EndsWith, value, convertStrategy: convertStrategy, collectionHandling: collectionHandling, stringComparision: stringComparision, negate: negate); 128 | 129 | public WhereBuilder OrEndsWith(string path, object value, QueryConvertStrategy convertStrategy = QueryConvertStrategy.ConvertConstantToComparedPropertyOrField, QueryCollectionHandling collectionHandling = QueryCollectionHandling.Any, StringComparison? stringComparision = null, bool negate = false) 130 | => Or(path, ConditionOperators.EndsWith, value, convertStrategy: convertStrategy, collectionHandling: collectionHandling, stringComparision: stringComparision, negate: negate); 131 | #endregion 132 | 133 | #region In 134 | public WhereBuilder In(string path, object value, QueryConvertStrategy convertStrategy = QueryConvertStrategy.ConvertConstantToComparedPropertyOrField, QueryCollectionHandling collectionHandling = QueryCollectionHandling.Any, StringComparison? stringComparision = null) 135 | => And(path, ConditionOperators.In, value, convertStrategy: convertStrategy, collectionHandling: collectionHandling, stringComparision: stringComparision); 136 | 137 | public WhereBuilder AndIn(string path, object value, QueryConvertStrategy convertStrategy = QueryConvertStrategy.ConvertConstantToComparedPropertyOrField, QueryCollectionHandling collectionHandling = QueryCollectionHandling.Any, StringComparison? stringComparision = null) 138 | => And(path, ConditionOperators.In, value, convertStrategy: convertStrategy, collectionHandling: collectionHandling, stringComparision: stringComparision); 139 | 140 | public WhereBuilder OrIn(string path, object value, QueryConvertStrategy convertStrategy = QueryConvertStrategy.ConvertConstantToComparedPropertyOrField, QueryCollectionHandling collectionHandling = QueryCollectionHandling.Any, StringComparison? stringComparision = null) 141 | => Or(path, ConditionOperators.In, value, convertStrategy: convertStrategy, collectionHandling: collectionHandling, stringComparision: stringComparision); 142 | #endregion 143 | 144 | #region NotIn 145 | public WhereBuilder NotIn(string path, object value, QueryConvertStrategy convertStrategy = QueryConvertStrategy.ConvertConstantToComparedPropertyOrField, QueryCollectionHandling collectionHandling = QueryCollectionHandling.Any, StringComparison? stringComparision = null) 146 | => And(path, ConditionOperators.NotIn, value, convertStrategy: convertStrategy, collectionHandling: collectionHandling, stringComparision: stringComparision); 147 | 148 | public WhereBuilder AndNotIn(string path, object value, QueryConvertStrategy convertStrategy = QueryConvertStrategy.ConvertConstantToComparedPropertyOrField, QueryCollectionHandling collectionHandling = QueryCollectionHandling.Any, StringComparison? stringComparision = null) 149 | => And(path, ConditionOperators.NotIn, value, convertStrategy: convertStrategy, collectionHandling: collectionHandling, stringComparision: stringComparision); 150 | 151 | public WhereBuilder OrNotIn(string path, object value, QueryConvertStrategy convertStrategy = QueryConvertStrategy.ConvertConstantToComparedPropertyOrField, QueryCollectionHandling collectionHandling = QueryCollectionHandling.Any, StringComparison? stringComparision = null) 152 | => Or(path, ConditionOperators.NotIn, value, convertStrategy: convertStrategy, collectionHandling: collectionHandling, stringComparision: stringComparision); 153 | #endregion 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicLinq/Fluent/Where/WhereBuilderCondition.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace PoweredSoft.DynamicLinq.Fluent 8 | { 9 | public class WhereBuilderCondition 10 | { 11 | public string Path { get; set; } 12 | public ConditionOperators ConditionOperator { get; set; } 13 | public object Value { get; set; } 14 | public bool And { get; set; } 15 | public QueryConvertStrategy ConvertStrategy { get; set; } 16 | public List Conditions { get; set; } = new List(); 17 | public QueryCollectionHandling CollectionHandling { get; set; } 18 | public StringComparison? StringComparisation { get; set; } = null; 19 | public bool Negate { get; set; } = false; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicLinq/Helpers/TypeHelpers.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.Reflection.Emit; 7 | using System.Reflection; 8 | 9 | namespace PoweredSoft.DynamicLinq.Helpers 10 | { 11 | public static class TypeHelpers 12 | { 13 | /* 14 | internal static Lazy DynamicAssemblyName = new Lazy(() => new AssemblyName("PoweredSoft.DynamicLinq.DynamicTypes")); 15 | internal static Lazy DynamicAssembly = new Lazy(() => AssemblyBuilder.DefineDynamicAssembly(DynamicAssemblyName.Value, AssemblyBuilderAccess.Run)); 16 | internal static Lazy DynamicModule = new Lazy(() => DynamicAssembly.Value.DefineDynamicModule("PoweredSoft.DynamicLinq.DynamicTypes"));*/ 17 | 18 | public static bool IsNullable(Type type) 19 | { 20 | if (!type.IsValueType) 21 | return true; // ref-type 22 | 23 | return Nullable.GetUnderlyingType(type) != null; 24 | } 25 | 26 | 27 | 28 | 29 | /* 30 | /// 31 | /// Use this to create anonymous type 32 | /// 33 | /// 34 | /// 35 | internal static TypeInfo CreateSimpleAnonymousType(List<(Type type, string name)> fields) 36 | { 37 | // DYNAMIC TYPE CREATION 38 | var typeName = $"PSDLProxy_{Guid.NewGuid().ToString("N")}"; 39 | var dynamicType = DynamicModule.Value.DefineType(typeName, TypeAttributes.Class | TypeAttributes.Public); 40 | fields.ForEach(field => 41 | { 42 | CreatePropertyOnType(dynamicType, field.name, field.type); 43 | }); 44 | // not needed at the end. 45 | // CreateConstructorWithAllPropsOnType(dynamicType, fields); 46 | var ret = dynamicType.CreateTypeInfo(); 47 | return ret; 48 | }*/ 49 | 50 | /* 51 | * concstructor 52 | * https://stackoverflow.com/questions/6879279/using-typebuilder-to-create-a-pass-through-constructor-for-the-base-class 53 | * works but wasn't needed at the end. 54 | private static void CreateConstructorWithAllPropsOnType(TypeBuilder dynamicType, List<(Type type, string name)> fields) 55 | { 56 | var ctor = dynamicType.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, fields.Select(t => t.type).ToArray()); 57 | var parameters = fields 58 | .Select((field, i) => 59 | { 60 | return ctor.DefineParameter(i++, ParameterAttributes.None, $"{field.name}_1"); 61 | }) 62 | .ToList(); 63 | 64 | var emitter = ctor.GetILGenerator(); 65 | emitter.Emit(OpCodes.Nop); 66 | 67 | // Load `this` and call base constructor with arguments 68 | emitter.Emit(OpCodes.Ldarg_0); 69 | for (var i = 1; i <= parameters.Count; ++i) 70 | { 71 | emitter.Emit(OpCodes.Ldarg, i); 72 | } 73 | emitter.Emit(OpCodes.Call, ctor); 74 | emitter.Emit(OpCodes.Ret); 75 | }*/ 76 | 77 | /* 78 | internal static void CreatePropertyOnType(TypeBuilder typeBuilder, string propertyName, Type propertyType) 79 | { 80 | // Generate a property called "Name" 81 | var field = typeBuilder.DefineField("_" + propertyName, propertyType, FieldAttributes.Private); 82 | var attributes = MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig; 83 | 84 | // generate property 85 | var propertyBuilder = typeBuilder.DefineProperty(propertyName, PropertyAttributes.HasDefault, propertyType, null); 86 | 87 | // Generate getter method 88 | var getter = typeBuilder.DefineMethod("get_" + propertyName, attributes, propertyType, Type.EmptyTypes); 89 | var il = getter.GetILGenerator(); 90 | il.Emit(OpCodes.Ldarg_0); // Push "this" on the stack 91 | il.Emit(OpCodes.Ldfld, field); // Load the field "_Name" 92 | il.Emit(OpCodes.Ret); // Return 93 | propertyBuilder.SetGetMethod(getter); 94 | 95 | // Generate setter method 96 | var setter = typeBuilder.DefineMethod("set_" + propertyName, attributes, null, new[] { propertyType }); 97 | il = setter.GetILGenerator(); 98 | il.Emit(OpCodes.Ldarg_0); // Push "this" on the stack 99 | il.Emit(OpCodes.Ldarg_1); // Push "value" on the stack 100 | il.Emit(OpCodes.Stfld, field); // Set the field "_Name" to "value" 101 | il.Emit(OpCodes.Ret); // Return 102 | propertyBuilder.SetSetMethod(setter); 103 | } 104 | */ 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicLinq/Interfaces/IQueryBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace PoweredSoft.DynamicLinq 7 | { 8 | public interface IQueryBuilder 9 | { 10 | IQueryable Query { get; } 11 | 12 | IQueryable Build(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicLinq/Parser/ExpressionParser.cs: -------------------------------------------------------------------------------- 1 | using PoweredSoft.DynamicLinq.Helpers; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Linq.Expressions; 6 | 7 | namespace PoweredSoft.DynamicLinq.Parser 8 | { 9 | public class ExpressionParser 10 | { 11 | public ParameterExpression Parameter { get; protected set; } 12 | public string Path { get; set; } 13 | public List Pieces { get; set; } = new List(); 14 | public bool IsParsed => Pieces?.Count > 0; 15 | 16 | public ExpressionParser(Type type, string path) : this(Expression.Parameter(type), path) 17 | { 18 | 19 | } 20 | 21 | public ExpressionParser(ParameterExpression parameter, string path) 22 | { 23 | Parameter = parameter; 24 | Path = path; 25 | } 26 | 27 | public static ExpressionParserPiece GetFirstEnumerableParent(ExpressionParserPiece piece) 28 | { 29 | if (piece.Parent == null) 30 | return null; 31 | 32 | if (piece.Parent.IsGenericEnumerable) 33 | return piece.Parent; 34 | 35 | return GetFirstEnumerableParent(piece.Parent); 36 | } 37 | 38 | public void Parse() 39 | { 40 | Pieces.Clear(); 41 | 42 | var pathPieces = Path.Split('.').ToList(); 43 | var param = Parameter; 44 | ExpressionParserPiece parent = null; 45 | 46 | pathPieces.ForEach(pp => 47 | { 48 | var memberExpression = Expression.PropertyOrField(param, pp); 49 | var current = new ExpressionParserPiece 50 | { 51 | Type = memberExpression.Type, 52 | IsGenericEnumerable = QueryableHelpers.IsGenericEnumerable(memberExpression), 53 | EnumerableType = QueryableHelpers.GetTypeOfEnumerable(memberExpression.Type, false), 54 | Parent = parent, 55 | Name = pp 56 | }; 57 | 58 | Pieces.Add(current); 59 | 60 | // for next iteration. 61 | param = Expression.Parameter(current.IsGenericEnumerable ? current.EnumerableType : current.Type); 62 | parent = current; 63 | }); 64 | } 65 | 66 | private ExpressionParserPieceGroup CreateAndAddGroup(List groups, ParameterExpression parameter, ExpressionParserPieceGroup parent) 67 | { 68 | var group = new ExpressionParserPieceGroup(); 69 | group.Parameter = parameter; 70 | group.Parent = parent; 71 | groups.Add(group); 72 | return group; 73 | } 74 | 75 | public List GroupBySharedParameters() 76 | { 77 | var groups = new List(); 78 | 79 | var group = CreateAndAddGroup(groups, Parameter, null); 80 | Pieces.ForEach(piece => 81 | { 82 | group.Pieces.Add(piece); 83 | if (piece.IsGenericEnumerable) 84 | group = CreateAndAddGroup(groups, Expression.Parameter(piece.EnumerableType), group); 85 | }); 86 | 87 | // if the last piece is empty. 88 | if (group.Pieces.Count == 0) 89 | groups.Remove(group); 90 | 91 | return groups; 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicLinq/Parser/ExpressionParserPiece.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace PoweredSoft.DynamicLinq.Parser 4 | { 5 | public class ExpressionParserPiece 6 | { 7 | public Type Type { get; set; } 8 | public bool IsGenericEnumerable { get; set; } 9 | public Type EnumerableType { get; set; } 10 | public ExpressionParserPiece Parent { get; set; } 11 | public string Name { get; internal set; } 12 | 13 | #if DEBUG 14 | public string DebugPath => $"{Type?.Name} {Name} -> {Parent?.DebugPath ?? "ROOT PARAMETER"}"; 15 | public override string ToString() => $"{Type?.Name} {Name}"; 16 | #endif 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicLinq/Parser/ExpressionParserPieceGroup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Text; 6 | 7 | namespace PoweredSoft.DynamicLinq.Parser 8 | { 9 | public class ExpressionParserPieceGroup 10 | { 11 | public List Pieces { get; set; } = new List(); 12 | public ParameterExpression Parameter { get; set; } 13 | public ExpressionParserPieceGroup Parent { get; set; } 14 | 15 | #if DEBUG 16 | public override string ToString() => $"{Parameter?.ToString()} is {Parameter?.Type} | {(Pieces == null ? "" : string.Join(" -> ", Pieces.Select(t2 => t2.ToString())))}"; 17 | #endif 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicLinq/Parser/ParserExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Text; 6 | 7 | namespace PoweredSoft.DynamicLinq.Parser 8 | { 9 | public static class ParserExtensions 10 | { 11 | public static ExpressionParserPiece FirstEnumerableParent(this ExpressionParserPiece piece) 12 | { 13 | var result = ExpressionParser.GetFirstEnumerableParent(piece); 14 | return result; 15 | } 16 | 17 | 18 | 19 | public static Type GroupEnumerableType(this ExpressionParserPieceGroup group) 20 | { 21 | return group.Pieces.Last().EnumerableType; 22 | } 23 | 24 | public static Type ResolveNullHandlingType(this List groups) 25 | { 26 | if (groups.Count() == 1) 27 | { 28 | return groups.First().Pieces.Last().Type; 29 | } 30 | 31 | var type = groups.Last().Pieces.Last().Type; 32 | return typeof(IEnumerable<>).MakeGenericType(type); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicLinq/PoweredSoft.DynamicLinq.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | True 6 | Powered Softwares Inc. 7 | David Lebée 8 | 9 | DynamicLinq by PoweredSoft 10 | Allows users to make dynamic query over a IQueryable<T> 11 | https://github.com/PoweredSoft/DynamicLinq 12 | https://secure.gravatar.com/avatar/4e32f73820c16718909a06c2927f1f8b?s=512&amp;r=g&amp;d=retro 13 | github 14 | dynamic linq queryable 15 | 1.1.0$(VersionSuffix) 16 | https://github.com/PoweredSoft/DynamicLinq 17 | PoweredSoft.DynamicLinq 18 | Added not contains thanks to Jon-Galloway. 19 | Added negate parameter to allow negating any conditions. 20 | False 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicLinq/Properties/PublishProfiles/FolderProfile.pubxml: -------------------------------------------------------------------------------- 1 |  2 | 5 | 6 | 7 | FileSystem 8 | Release 9 | Any CPU 10 | netstandard2.0 11 | bin\Release\netstandard2.0\publish\ 12 | 13 | -------------------------------------------------------------------------------- /PoweredSoft.DynamicLinq/Resolver/PathExpressionResolver.cs: -------------------------------------------------------------------------------- 1 | using PoweredSoft.DynamicLinq.Helpers; 2 | using PoweredSoft.DynamicLinq.Parser; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Linq.Expressions; 7 | using System.Text; 8 | 9 | namespace PoweredSoft.DynamicLinq.Resolver 10 | { 11 | public class PathExpressionResolver 12 | { 13 | public bool NullChecking { get; set; } = false; 14 | public SelectCollectionHandling CollectionHandling { get; set; } = SelectCollectionHandling.LeaveAsIs; 15 | public ExpressionParser Parser { get; protected set; } 16 | 17 | public Expression Result { get; protected set; } 18 | 19 | public PathExpressionResolver(ExpressionParser parser) 20 | { 21 | Parser = parser; 22 | } 23 | 24 | protected Expression CompileGroup(ExpressionParserPieceGroup group, bool nullChecking) 25 | { 26 | var expr = group.Parameter as Expression; 27 | group.Pieces.ForEach(piece => 28 | { 29 | expr = Expression.PropertyOrField(expr, piece.Name); 30 | }); 31 | return expr; 32 | } 33 | 34 | public void Resolve() 35 | { 36 | Result = null; 37 | 38 | // parse the expression. 39 | if (!Parser.IsParsed) 40 | Parser.Parse(); 41 | 42 | // group the piece by common parameters 43 | var groups = Parser.GroupBySharedParameters(); 44 | 45 | Expression currentExpression = null; 46 | foreach (var group in groups.Reversed()) 47 | { 48 | var isLastGroup = groups.IndexOf(group) == groups.Count - 1; 49 | 50 | if (currentExpression == null) 51 | { 52 | var groupExpression = CompileGroup(group, NullChecking); 53 | var groupExpressionLambda = Expression.Lambda(groupExpression, group.Parameter); 54 | 55 | if (group.Parent == null) 56 | { 57 | currentExpression = groupExpression; 58 | if (NullChecking != false) 59 | currentExpression = CheckNullOnFirstGroup(group, currentExpression); 60 | 61 | currentExpression = Expression.Lambda(currentExpression, group.Parameter); 62 | continue; 63 | } 64 | 65 | var parent = group.Parent; 66 | var parentExpression = CompileGroup(parent, NullChecking); 67 | 68 | // check null with where. 69 | var isSelectMany = CollectionHandling == SelectCollectionHandling.Flatten && QueryableHelpers.IsGenericEnumerable(groupExpression); 70 | if (NullChecking != false) 71 | parentExpression = CheckNullOnEnumerableParent(group, parent, parentExpression, isLastGroup && !isSelectMany); 72 | 73 | // the select expression. 74 | if (isSelectMany) 75 | { 76 | var selectType = parent.GroupEnumerableType(); 77 | var groupExpressionEnumerableType = QueryableHelpers.GetTypeOfEnumerable(groupExpression.Type, true); 78 | var selectExpression = Expression.Call(typeof(Enumerable), "SelectMany", 79 | new Type[] { selectType, groupExpressionEnumerableType }, 80 | parentExpression, groupExpressionLambda); 81 | currentExpression = selectExpression; 82 | } 83 | else 84 | { 85 | var selectType = parent.GroupEnumerableType(); 86 | var selectExpression = Expression.Call(typeof(Enumerable), "Select", 87 | new Type[] { selectType, groupExpression.Type }, 88 | parentExpression, groupExpressionLambda); 89 | currentExpression = selectExpression; 90 | } 91 | } 92 | else 93 | { 94 | if (group.Parent == null) 95 | { 96 | if (NullChecking != false) 97 | currentExpression = CheckNullOnFirstGroup(group, currentExpression); 98 | 99 | currentExpression = Expression.Lambda(currentExpression, group.Parameter); 100 | continue; 101 | } 102 | 103 | var parent = group.Parent; 104 | var parentExpression = CompileGroup(parent, NullChecking); 105 | var selectType = parent.GroupEnumerableType(); 106 | 107 | bool isSelectMany = CollectionHandling == SelectCollectionHandling.Flatten && QueryableHelpers.IsGenericEnumerable(currentExpression); 108 | if (NullChecking != false) 109 | parentExpression = CheckNullOnEnumerableParent(group, parent, parentExpression, isLastGroup && !isSelectMany); 110 | 111 | if (isSelectMany) 112 | { 113 | var currentExpressionEnumerableType = QueryableHelpers.GetTypeOfEnumerable(currentExpression.Type, true); 114 | var currentExpressionLambda = Expression.Lambda(currentExpression, group.Parameter); 115 | currentExpression = Expression.Call(typeof(Enumerable), "SelectMany", 116 | new Type[] { selectType, currentExpressionEnumerableType }, 117 | parentExpression, currentExpressionLambda); 118 | } 119 | else 120 | { 121 | var currentExpressionLambda = Expression.Lambda(currentExpression, group.Parameter); 122 | currentExpression = Expression.Call(typeof(Enumerable), "Select", 123 | new Type[] { selectType, currentExpression.Type }, 124 | parentExpression, currentExpressionLambda); 125 | } 126 | } 127 | } 128 | 129 | Result = currentExpression; 130 | } 131 | 132 | private Expression CheckNullOnFirstGroup(ExpressionParserPieceGroup group, Expression currentExpression) 133 | { 134 | var path = string.Join(".", group.Pieces.Select(t => t.Name)); 135 | var whereExpression = QueryableHelpers.CreateConditionExpression(group.Parameter.Type, path, 136 | ConditionOperators.NotEqual, null, QueryConvertStrategy.ConvertConstantToComparedPropertyOrField, 137 | parameter: group.Parameter, nullChecking: true); 138 | 139 | var whereBodyExpression = (whereExpression as LambdaExpression).Body; 140 | whereBodyExpression =Expression.Not(whereBodyExpression); 141 | 142 | 143 | 144 | 145 | var nullType = currentExpression.Type; 146 | Expression ifTrueExpression = null; 147 | if (QueryableHelpers.IsGenericEnumerable(nullType)) 148 | { 149 | var enumerableType = QueryableHelpers.GetTypeOfEnumerable(nullType, true); 150 | var listType = typeof(List<>).MakeGenericType(enumerableType); 151 | ifTrueExpression = Expression.New(listType); 152 | 153 | } 154 | else 155 | { 156 | ifTrueExpression = Expression.Default(nullType); 157 | } 158 | 159 | return Expression.Condition(whereBodyExpression, ifTrueExpression, currentExpression, currentExpression.Type); 160 | } 161 | 162 | private static Expression CheckNullOnEnumerableParent(ExpressionParserPieceGroup group, ExpressionParserPieceGroup parent, Expression parentExpression, bool shouldSkipLast) 163 | { 164 | string path = null; 165 | if (shouldSkipLast) 166 | path = string.Join(".", group.Pieces.Take(group.Pieces.Count - 1).Select(t => t.Name)); 167 | else 168 | path = string.Join(".", group.Pieces.Select(t => t.Name)); 169 | 170 | if (!string.IsNullOrEmpty(path)) 171 | { 172 | var whereExpression = QueryableHelpers.CreateConditionExpression(group.Parameter.Type, path, 173 | ConditionOperators.NotEqual, null, QueryConvertStrategy.ConvertConstantToComparedPropertyOrField, 174 | parameter: group.Parameter, nullChecking: true); 175 | 176 | //public static IEnumerable Where(this IEnumerable source, Func predicate); 177 | parentExpression = Expression.Call(typeof(Enumerable), "Where", 178 | new Type[] { parent.GroupEnumerableType() }, 179 | parentExpression, whereExpression); 180 | } 181 | 182 | return parentExpression; 183 | } 184 | 185 | public Expression GetResultBodyExpression() 186 | { 187 | if (Result == null) 188 | return Result; 189 | 190 | return ((LambdaExpression)Result).Body; 191 | } 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DynamicLinq 2 | Adds extensions to Linq to offer dynamic queryables. 3 | 4 | ## Roadmap 5 | Check "Projects" section of github to see whats going on. 6 | 7 | https://github.com/PoweredSoft/DynamicLinq/projects/1 8 | 9 | ## Download 10 | Full Version | NuGet | NuGet Install 11 | ------------ | :-------------: | :-------------: 12 | PoweredSoft.DynamicLinq | [![NuGet](https://img.shields.io/nuget/v/PoweredSoft.DynamicLinq.svg?style=flat-square&label=nuget)](https://www.nuget.org/packages/PoweredSoft.DynamicLinq/) | ```PM> Install-Package PoweredSoft.DynamicLinq``` 13 | PoweredSoft.DynamicLinq.EntityFramework | [![NuGet](https://img.shields.io/nuget/v/PoweredSoft.DynamicLinq.EntityFramework.svg?style=flat-square&label=nuget)](https://www.nuget.org/packages/PoweredSoft.DynamicLinq.EntityFramework/) | ```PM> Install-Package PoweredSoft.DynamicLinq.EntityFramework``` 14 | PoweredSoft.DynamicLinq.EntityFrameworkCore | [![NuGet](https://img.shields.io/nuget/v/PoweredSoft.DynamicLinq.EntityFrameworkCore.svg?style=flat-square&label=nuget)](https://www.nuget.org/packages/PoweredSoft.DynamicLinq.EntityFrameworkCore/) | ```PM> Install-Package PoweredSoft.DynamicLinq.EntityFrameworkCore``` 15 | 16 | 17 | ## Samples 18 | Complex Query 19 | ```csharp 20 | query = query.Query(q => 21 | { 22 | q.Compare("AuthorId", ConditionOperators.Equal, 1); 23 | q.And(sq => 24 | { 25 | sq.Compare("Content", ConditionOperators.Equal, "World"); 26 | sq.Or("Title", ConditionOperators.Contains, 3); 27 | }); 28 | }); 29 | ``` 30 | 31 | ### Shortcuts 32 | Shortcuts allow to avoid specifying the condition operator by having it handy in the method name 33 | ```csharp 34 | queryable.Query(t => t.Contains("FirstName", "Dav").OrContains("FirstName", "Jo")); 35 | ``` 36 | > You may visit this test for more examples: https://github.com/PoweredSoft/DynamicLinq/blob/master/PoweredSoft.DynamicLinq.Test/ShortcutTests.cs 37 | 38 | ### Simple Query 39 | ```csharp 40 | query.Where("FirstName", ConditionOperators.Equal, "David"); 41 | ``` 42 | 43 | ### Grouping Support 44 | ```csharp 45 | TestData.Sales 46 | .AsQueryable() 47 | .GroupBy(t => t.Path("ClientId")) 48 | .Select(t => 49 | { 50 | t.Key("TheClientId", "ClientId"); 51 | t.Count("Count"); 52 | t.LongCount("LongCount"); 53 | t.Sum("NetSales"); 54 | t.Average("Tax", "TaxAverage"); 55 | t.Aggregate("Tax", SelectTypes.Average, "TaxAverage2"); // Starting 1.0.5 56 | t.ToList("Sales"); 57 | }); 58 | ``` 59 | > Is equivalent to 60 | ```csharp 61 | TestSales 62 | .GroupBy(t => new { t.ClientId }) 63 | .Select(t => new { 64 | TheClientId = t.Key.ClientId, 65 | Count = t.Count(), 66 | LongCount = t.LongCount(), 67 | NetSales = t.Sum(t2 => t2.NetSales), 68 | TaxAverage = t.Average(t2 => t2.Tax), 69 | TaxAverage2 = t.Average(t2 => t2.Tax), 70 | Sales = t.ToList() 71 | }); 72 | ``` 73 | 74 | ### Empty Group By 75 | 76 | This is common to create aggregate totals. 77 | 78 | ```csharp 79 | someQueryable.EmptyGroupBy(typeof(SomeClass)); 80 | ``` 81 | > Is equivalent to 82 | 83 | ```csharp 84 | someQueryableOfT.GroupBy(t => true); 85 | ``` 86 | 87 | ### Count shortcut 88 | 89 | ```csharp 90 | IQueryable someQueryable = ; 91 | someQueryable.Count(); 92 | ``` 93 | 94 | > Is equivalent to 95 | 96 | ```csharp 97 | IQueryable someQueryableOfT = ; 98 | someQsomeQueryableOfTueryable.Count(); 99 | ``` 100 | 101 | ### Select 102 | 103 | > Note **PathToList** has been renamed to just **ToList** it seemed redudant, sorry for breaking change. 104 | 105 | ```csharp 106 | var querySelect = query.Select(t => 107 | { 108 | t.NullChecking(true); // not obligated but usefull for in memory queries. 109 | t.ToList("Posts.Comments.CommentLikes", selectCollectionHandling: SelectCollectionHandling.Flatten); 110 | t.Path("FirstName"); 111 | t.Path("LastName", "ChangePropertyNameOfLastName"); 112 | }); 113 | ``` 114 | 115 | ### In Support 116 | You can filter with a list, this will generate a contains with your list. 117 | ```csharp 118 | var ageGroup = new List() { 28, 27, 50 }; 119 | Persons.AsQueryable().Query(t => t.In("Age", ageGroup)); 120 | ``` 121 | 122 | ### String Comparision Support 123 | ```csharp 124 | Persons.AsQueryable().Query(t => t.Equal("FirstName", "DAVID", stringComparision: StringComparison.OrdinalIgnoreCase)); 125 | ``` 126 | You may visit this test for more examples: 127 | https://github.com/PoweredSoft/DynamicLinq/blob/master/PoweredSoft.DynamicLinq.Test/StringComparision.cs 128 | 129 | ### Simple Sorting 130 | ```csharp 131 | query = query.OrderByDescending("AuthorId"); 132 | query = query.ThenBy("Id"); 133 | ``` 134 | 135 | ### Collection Filtering 136 | You don't have to Worry about it. 137 | The library will do it for you. 138 | ```csharp 139 | var query = authors.AsQueryable(); 140 | query = query.Query(qb => 141 | { 142 | qb.NullChecking(); 143 | // you can specify here which collection handling you wish to use Any and All is supported for now. 144 | qb.And("Posts.Comments.Email", ConditionOperators.Equal, "john.doe@me.com", collectionHandling: QueryCollectionHandling.Any); 145 | }); 146 | ``` 147 | 148 | ### Null Checking is automatic (practical for in memory dynamic queries) 149 | ```csharp 150 | var query = authors.AsQueryable(); 151 | query = query.Query(qb => 152 | { 153 | qb.NullChecking(); 154 | qb.And("Posts.Comments.Email", ConditionOperators.Equal, "john.doe@me.com", collectionHandling: QueryCollectionHandling.Any); 155 | }); 156 | ``` 157 | 158 | ### Using Query Builder 159 | ```csharp 160 | // subject. 161 | var posts = new List() 162 | { 163 | new Post { Id = 1, AuthorId = 1, Title = "Hello 1", Content = "World" }, 164 | new Post { Id = 2, AuthorId = 1, Title = "Hello 2", Content = "World" }, 165 | new Post { Id = 3, AuthorId = 2, Title = "Hello 3", Content = "World" }, 166 | }; 167 | 168 | // the query. 169 | var query = posts.AsQueryable(); 170 | var queryBuilder = new QueryBuilder(query); 171 | 172 | queryBuilder.Compare("AuthorId", ConditionOperators.Equal, 1); 173 | queryBuilder.And(subQuery => 174 | { 175 | subQuery.Compare("Content", ConditionOperators.Equal, "World"); 176 | subQuery.Or("Title", ConditionOperators.Contains, 3); 177 | }); 178 | 179 | query = queryBuilder.Build(); 180 | ``` 181 | 182 | ### Entity Framework 183 | 184 | Using PoweredSoft.DynamicLinq.EntityFramework it adds an helper that allows you to do the following. 185 | 186 | ```csharp 187 | var context = new (); 188 | var queryable = context.Query(typeof(Author), q => q.Compare("FirstName", ConditionOperators.Equal, "David")); 189 | var result = queryable.ToListAsync().Result; 190 | var first = result.FirstOrDefault() as Author; 191 | Assert.AreEqual(first?.FirstName, "David"); 192 | ``` 193 | 194 | ### How it can be used in a web api 195 | 196 | > I highly suggest looking @ https://github.com/poweredsoft/dynamicquery if you are interested in this sample. 197 | 198 | > Sample how to use DynamicQuery with asp.net mvc core and EF Core: https://github.com/PoweredSoft/DynamicQueryAspNetCoreSample 199 | 200 | ```csharp 201 | [HttpGet][Route("FindClients")] 202 | public IHttpActionResult FindClients(string filterField = null, string filterValue = null, 203 | string sortProperty = "Id", int? page = null, int pageSize = 50) 204 | { 205 | var ctx = new MyDbContext(); 206 | var query = ctx.Clients.AsQueryable(); 207 | 208 | if (!string.IsNullOrEmpty(filterField) && !string.IsNullOrEmpty(filterValue)) 209 | query = query.Query(t => t.Contains(filterField, filterValue)).OrderBy(sortProperty); 210 | 211 | // count. 212 | var clientCount = query.Count(); 213 | int? pages = null; 214 | 215 | if (page.HasValue && pageSize > 0) 216 | { 217 | if (clientCount == 0) 218 | pages = 0; 219 | else 220 | pages = clientCount / pageSize + (clientCount % pageSize != 0 ? 1 : 0); 221 | } 222 | 223 | if (page.HasValue) 224 | query = query.Skip((page.Value-1) * pageSize).Take(pageSize); 225 | 226 | var clients = query.ToList(); 227 | 228 | return Ok(new 229 | { 230 | total = clientCount, 231 | pages = pages, 232 | data = clients 233 | }); 234 | } 235 | ``` 236 | --------------------------------------------------------------------------------