diff --git a/PoweredSoft.DynamicLinq.Dal/App.config b/PoweredSoft.DynamicLinq.Dal/App.config
new file mode 100644
index 0000000..2fb423e
--- /dev/null
+++ b/PoweredSoft.DynamicLinq.Dal/App.config
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/PoweredSoft.DynamicLinq.Dal/BlogContext.cs b/PoweredSoft.DynamicLinq.Dal/BlogContext.cs
new file mode 100644
index 0000000..3328646
--- /dev/null
+++ b/PoweredSoft.DynamicLinq.Dal/BlogContext.cs
@@ -0,0 +1,41 @@
+using PoweredSoft.DynamicLinq.Dal.Configurations;
+using PoweredSoft.DynamicLinq.Dal.Pocos;
+using System;
+using System.Collections.Generic;
+using System.Data.Entity;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PoweredSoft.DynamicLinq.Dal
+{
+ public class BlogContext : DbContext
+ {
+ public DbSet Authors { get; set; }
+ public DbSet Comments { get; set; }
+ public DbSet Posts { get; set; }
+
+ static BlogContext()
+ {
+ Database.SetInitializer(new DropCreateDatabaseAlways());
+ }
+
+ public BlogContext()
+ {
+
+ }
+
+ public BlogContext(string connectionString) : base(connectionString)
+ {
+
+ }
+
+ protected override void OnModelCreating(DbModelBuilder modelBuilder)
+ {
+ base.OnModelCreating(modelBuilder);
+ modelBuilder.Configurations.Add(new AuthorConfiguration());
+ modelBuilder.Configurations.Add(new CommentConfiguration());
+ modelBuilder.Configurations.Add(new PostConfiguration());
+ }
+ }
+}
diff --git a/PoweredSoft.DynamicLinq.Dal/Configurations/Configurations.cs b/PoweredSoft.DynamicLinq.Dal/Configurations/Configurations.cs
new file mode 100644
index 0000000..0049aaa
--- /dev/null
+++ b/PoweredSoft.DynamicLinq.Dal/Configurations/Configurations.cs
@@ -0,0 +1,73 @@
+using PoweredSoft.DynamicLinq.Dal.Pocos;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations.Schema;
+using System.Data.Entity.ModelConfiguration;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PoweredSoft.DynamicLinq.Dal.Configurations
+{
+ public class AuthorConfiguration : EntityTypeConfiguration
+ {
+ public AuthorConfiguration() : this("dbo")
+ {
+
+ }
+
+ public AuthorConfiguration(string schema)
+ {
+ ToTable("Author", schema);
+ HasKey(t => t.Id);
+ Property(t => t.Id).HasColumnName("Id").HasColumnType("bigint").IsRequired().HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
+ Property(t => t.FirstName).HasColumnType("nvarchar").HasMaxLength(50).IsRequired();
+ Property(t => t.LastName).HasColumnType("nvarchar").HasMaxLength(50).IsRequired();
+ }
+ }
+
+ public class PostConfiguration : EntityTypeConfiguration
+ {
+ public PostConfiguration() : this("dbo")
+ {
+
+ }
+
+ public PostConfiguration(string schema)
+ {
+ ToTable("Post", schema);
+ HasKey(t => t.Id);
+ Property(t => t.Id).HasColumnName("Id").HasColumnType("bigint").IsRequired().HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
+ Property(t => t.AuthorId).HasColumnName("AuthorId").HasColumnType("bigint").IsRequired();
+ Property(t => t.Title).HasColumnName("Title").HasColumnType("nvarchar").HasMaxLength(100).IsRequired();
+ Property(t => t.Content).HasColumnName("Content").HasColumnType("nvarchar(max)").IsRequired();
+ Property(t => t.CreateTime).HasColumnName("CreateTime").HasColumnType("datetimeoffset").IsRequired();
+ Property(t => t.PublishTime).HasColumnName("PublishTime").HasColumnType("datetimeoffset").IsOptional();
+
+ HasRequired(t => t.Author).WithMany(t => t.Posts).HasForeignKey(t => t.AuthorId).WillCascadeOnDelete(false);
+ }
+ }
+
+ public class CommentConfiguration : EntityTypeConfiguration
+ {
+ public CommentConfiguration() : this("dbo")
+ {
+
+ }
+
+ public CommentConfiguration(string schema)
+ {
+ ToTable("Comment", schema);
+ HasKey(t => t.Id);
+ Property(t => t.Id).HasColumnName("Id").HasColumnType("bigint").IsRequired().HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
+ Property(t => t.ParentCommentId).HasColumnName("ParentCommentId").HasColumnType("bigint").IsOptional();
+ Property(t => t.PostId).HasColumnName("PostId").HasColumnType("bigint").IsRequired();
+ Property(t => t.DisplayName).HasColumnName("DisplayName").HasColumnType("nvarchar").HasMaxLength(100).IsRequired();
+ Property(t => t.Email).HasColumnName("Email").HasColumnType("nvarchar").IsOptional();
+ Property(t => t.CommentText).HasColumnName("CommentText").HasColumnType("nvarchar").HasMaxLength(255).IsOptional();
+
+ HasRequired(t => t.Post).WithMany(t => t.Comments).HasForeignKey(t => t.PostId).WillCascadeOnDelete(false);
+ HasOptional(t => t.ParentComment).WithMany(t => t.Comments).HasForeignKey(t => t.ParentCommentId).WillCascadeOnDelete(false);
+ }
+ }
+}
diff --git a/PoweredSoft.DynamicLinq.Dal/Pocos/Author.cs b/PoweredSoft.DynamicLinq.Dal/Pocos/Author.cs
new file mode 100644
index 0000000..1ae9114
--- /dev/null
+++ b/PoweredSoft.DynamicLinq.Dal/Pocos/Author.cs
@@ -0,0 +1,17 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PoweredSoft.DynamicLinq.Dal.Pocos
+{
+ public class Author
+ {
+ public long Id { get; set; }
+ public string FirstName { get; set; }
+ public string LastName { get; set; }
+
+ public ICollection Posts { get; set; }
+ }
+}
diff --git a/PoweredSoft.DynamicLinq.Dal/Pocos/Comment.cs b/PoweredSoft.DynamicLinq.Dal/Pocos/Comment.cs
new file mode 100644
index 0000000..b0c003f
--- /dev/null
+++ b/PoweredSoft.DynamicLinq.Dal/Pocos/Comment.cs
@@ -0,0 +1,22 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PoweredSoft.DynamicLinq.Dal.Pocos
+{
+ public class Comment
+ {
+ public long Id { get; set; }
+ public long? ParentCommentId { get; set; }
+ public long PostId { get; set; }
+ public string DisplayName { get; set; }
+ public string Email { get; set; }
+ public string CommentText { get; set; }
+
+ public Comment ParentComment { get; set; }
+ public ICollection Comments { get; set; }
+ public Post Post { get; set; }
+ }
+}
diff --git a/PoweredSoft.DynamicLinq.Dal/Pocos/Post.cs b/PoweredSoft.DynamicLinq.Dal/Pocos/Post.cs
new file mode 100644
index 0000000..2c88a79
--- /dev/null
+++ b/PoweredSoft.DynamicLinq.Dal/Pocos/Post.cs
@@ -0,0 +1,21 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PoweredSoft.DynamicLinq.Dal.Pocos
+{
+ public class Post
+ {
+ public long Id { get; set; }
+ public long AuthorId { get; set; }
+ public string Title { get; set; }
+ public string Content { get; set; }
+ public DateTimeOffset CreateTime { get; set; }
+ public DateTimeOffset? PublishTime { get; set; }
+
+ public Author Author { get; set; }
+ public virtual ICollection Comments { get; set; }
+ }
+}
diff --git a/PoweredSoft.DynamicLinq.Dal/PoweredSoft.DynamicLinq.Dal.csproj b/PoweredSoft.DynamicLinq.Dal/PoweredSoft.DynamicLinq.Dal.csproj
new file mode 100644
index 0000000..701aa82
--- /dev/null
+++ b/PoweredSoft.DynamicLinq.Dal/PoweredSoft.DynamicLinq.Dal.csproj
@@ -0,0 +1,63 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {C16927E7-1358-4B9D-BDD7-149E505DE6CC}
+ Library
+ Properties
+ PoweredSoft.DynamicLinq.Dal
+ PoweredSoft.DynamicLinq.Dal
+ v4.6.1
+ 512
+
+
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+ ..\packages\EntityFramework.6.2.0\lib\net45\EntityFramework.dll
+
+
+ ..\packages\EntityFramework.6.2.0\lib\net45\EntityFramework.SqlServer.dll
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/PoweredSoft.DynamicLinq.Dal/Properties/AssemblyInfo.cs b/PoweredSoft.DynamicLinq.Dal/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..d30d914
--- /dev/null
+++ b/PoweredSoft.DynamicLinq.Dal/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("PoweredSoft.DynamicLinq.Dal")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("PoweredSoft.DynamicLinq.Dal")]
+[assembly: AssemblyCopyright("Copyright © 2018")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("c16927e7-1358-4b9d-bdd7-149e505de6cc")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/PoweredSoft.DynamicLinq.Dal/packages.config b/PoweredSoft.DynamicLinq.Dal/packages.config
new file mode 100644
index 0000000..b3daf0d
--- /dev/null
+++ b/PoweredSoft.DynamicLinq.Dal/packages.config
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/PoweredSoft.DynamicLinq.Test/ComplexQueryTest.cs b/PoweredSoft.DynamicLinq.Test/ComplexQueryTest.cs
new file mode 100644
index 0000000..b659793
--- /dev/null
+++ b/PoweredSoft.DynamicLinq.Test/ComplexQueryTest.cs
@@ -0,0 +1,35 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using PoweredSoft.DynamicLinq.Dal.Pocos;
+using PoweredSoft.DynamicLinq.Extensions;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PoweredSoft.DynamicLinq.Test
+{
+ [TestClass]
+ public class ComplexQueryTest
+ {
+ [TestMethod]
+ public void ComplexQueryBuilder()
+ {
+ // subject.
+ var authors = new List()
+ {
+ new Post { Id = 1, AuthorId = 1, Title = "Hello 1", Content = "World" },
+ new Post { Id = 2, AuthorId = 1, Title = "Hello 2", Content = "World" },
+ new Post { Id = 3, AuthorId = 2, Title = "Hello 3", Content = "World" },
+ };
+
+ // the query.
+ var query = authors.AsQueryable();
+
+ query = query.Query(q => q
+ .Compare("AuthorId", ConditionOperators.Equal, 1)
+ .Or("AuthorId", ConditionOperators.Equal, 2)
+ );
+ }
+ }
+}
diff --git a/PoweredSoft.DynamicLinq.Test/PoweredSoft.DynamicLinq.Test.csproj b/PoweredSoft.DynamicLinq.Test/PoweredSoft.DynamicLinq.Test.csproj
new file mode 100644
index 0000000..1e904d8
--- /dev/null
+++ b/PoweredSoft.DynamicLinq.Test/PoweredSoft.DynamicLinq.Test.csproj
@@ -0,0 +1,81 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {6F5C80F0-9045-4098-913F-7BDAD135E6DD}
+ Library
+ Properties
+ PoweredSoft.DynamicLinq.Test
+ PoweredSoft.DynamicLinq.Test
+ v4.6.1
+ 512
+ {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
+ 15.0
+ $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
+ $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages
+ False
+ UnitTest
+
+
+
+
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+ ..\packages\Faker.Data.1.0.7\lib\net45\Faker.dll
+
+
+ ..\packages\MSTest.TestFramework.1.2.0\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll
+
+
+ ..\packages\MSTest.TestFramework.1.2.0\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {C16927E7-1358-4B9D-BDD7-149E505DE6CC}
+ PoweredSoft.DynamicLinq.Dal
+
+
+ {2abc5a60-b549-4ecd-bef4-31ca7ba4ef06}
+ PoweredSoft.DynamicLinq
+
+
+
+
+
+
+ This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.
+
+
+
+
+
+
\ No newline at end of file
diff --git a/PoweredSoft.DynamicLinq.Test/Properties/AssemblyInfo.cs b/PoweredSoft.DynamicLinq.Test/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..33a9eed
--- /dev/null
+++ b/PoweredSoft.DynamicLinq.Test/Properties/AssemblyInfo.cs
@@ -0,0 +1,20 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+[assembly: AssemblyTitle("PoweredSoft.DynamicLinq.Test")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("PoweredSoft.DynamicLinq.Test")]
+[assembly: AssemblyCopyright("Copyright © 2018")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+[assembly: ComVisible(false)]
+
+[assembly: Guid("6f5c80f0-9045-4098-913f-7bdad135e6dd")]
+
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/PoweredSoft.DynamicLinq.Test/QueryTests.cs b/PoweredSoft.DynamicLinq.Test/QueryTests.cs
new file mode 100644
index 0000000..5a70e8f
--- /dev/null
+++ b/PoweredSoft.DynamicLinq.Test/QueryTests.cs
@@ -0,0 +1,132 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Linq.Expressions;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using PoweredSoft.DynamicLinq.Dal.Pocos;
+using PoweredSoft.DynamicLinq.Extensions;
+
+namespace PoweredSoft.DynamicLinq.Test
+{
+ [TestClass]
+ public class QueryTests
+ {
+ [TestMethod]
+ public void Equal()
+ {
+ // subject.
+ var authors = new List()
+ {
+ new Author { Id = long.MaxValue, FirstName = "David", LastName = "Lebee" }
+ };
+
+ // the query.
+ var query = authors.AsQueryable();
+
+ // simple where.
+ var newQuery = query.Where("FirstName", ConditionOperators.Equal, "David");
+
+ // must match.
+ Assert.IsTrue(newQuery.Any(), "Must have at least one author that matches");
+ }
+
+ [TestMethod]
+ public void Contains()
+ {
+ // subject.
+ var authors = new List()
+ {
+ new Author { Id = long.MaxValue, FirstName = "David", LastName = "Lebee" }
+ };
+
+ // the query.
+ var query = authors.AsQueryable();
+
+ // simple where.
+ var newQuery = query.Where("FirstName", ConditionOperators.Contains, "Da");
+
+ // must match.
+ Assert.IsTrue(newQuery.Any(), "Must have at least one author that matches");
+ }
+
+ [TestMethod]
+ public void StartsWith()
+ {
+ // subject.
+ var authors = new List()
+ {
+ new Author { Id = long.MaxValue, FirstName = "David", LastName = "Lebee" }
+ };
+
+ // the query.
+ var query = authors.AsQueryable();
+
+ // simple where.
+ var newQuery = query.Where("FirstName", ConditionOperators.StartsWith, "Da");
+
+ // must match.
+ Assert.IsTrue(newQuery.Any(), "Must have at least one author that matches");
+ }
+
+ [TestMethod]
+ public void EndsWith()
+ {
+ // subject.
+ var authors = new List()
+ {
+ new Author { Id = long.MaxValue, FirstName = "David", LastName = "Lebee" }
+ };
+
+ // the query.
+ var query = authors.AsQueryable();
+
+ // simple where.
+ var newQuery = query.Where("FirstName", ConditionOperators.EndsWith, "Da");
+
+ // must match.
+ Assert.IsFalse(newQuery.Any(), "Not suppose to find any matches");
+ }
+
+ [TestMethod]
+ public void LessThen()
+ {
+ // subject.
+ var authors = new List()
+ {
+ new Post { Id = 1, AuthorId = 1, Title = "Hello 1", Content = "World" },
+ new Post { Id = 2, AuthorId = 1, Title = "Hello 2", Content = "World" },
+ new Post { Id = 3, AuthorId = 2, Title = "Hello 3", Content = "World" },
+ };
+
+ // the query.
+ var query = authors.AsQueryable();
+
+ // simple where.
+ var newQuery = query.Where("AuthorId", ConditionOperators.LessThan, 2);
+
+ // must match.
+ Assert.AreEqual(2, newQuery.Count());
+ }
+
+ [TestMethod]
+ public void GreaterThanOrEqual()
+ {
+ // subject.
+ var authors = new List()
+ {
+ new Post { Id = 1, AuthorId = 1, Title = "Hello 1", Content = "World" },
+ new Post { Id = 2, AuthorId = 1, Title = "Hello 2", Content = "World" },
+ new Post { Id = 3, AuthorId = 2, Title = "Hello 3", Content = "World" },
+ };
+
+ // the query.
+ var query = authors.AsQueryable();
+
+ // simple where.
+ var newQuery = query.Where("AuthorId", ConditionOperators.GreaterThanOrEqual, 2);
+
+ // must match.
+ Assert.AreEqual(1, newQuery.Count());
+ }
+ }
+}
diff --git a/PoweredSoft.DynamicLinq.Test/packages.config b/PoweredSoft.DynamicLinq.Test/packages.config
new file mode 100644
index 0000000..5dfd80b
--- /dev/null
+++ b/PoweredSoft.DynamicLinq.Test/packages.config
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/PoweredSoft.DynamicLinq.sln b/PoweredSoft.DynamicLinq.sln
new file mode 100644
index 0000000..9a9401f
--- /dev/null
+++ b/PoweredSoft.DynamicLinq.sln
@@ -0,0 +1,37 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 15
+VisualStudioVersion = 15.0.27130.2010
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PoweredSoft.DynamicLinq", "PoweredSoft.DynamicLinq\PoweredSoft.DynamicLinq.csproj", "{2ABC5A60-B549-4ECD-BEF4-31CA7BA4EF06}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PoweredSoft.DynamicLinq.Dal", "PoweredSoft.DynamicLinq.Dal\PoweredSoft.DynamicLinq.Dal.csproj", "{C16927E7-1358-4B9D-BDD7-149E505DE6CC}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PoweredSoft.DynamicLinq.Test", "PoweredSoft.DynamicLinq.Test\PoweredSoft.DynamicLinq.Test.csproj", "{6F5C80F0-9045-4098-913F-7BDAD135E6DD}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {2ABC5A60-B549-4ECD-BEF4-31CA7BA4EF06}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {2ABC5A60-B549-4ECD-BEF4-31CA7BA4EF06}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {2ABC5A60-B549-4ECD-BEF4-31CA7BA4EF06}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {2ABC5A60-B549-4ECD-BEF4-31CA7BA4EF06}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C16927E7-1358-4B9D-BDD7-149E505DE6CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C16927E7-1358-4B9D-BDD7-149E505DE6CC}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C16927E7-1358-4B9D-BDD7-149E505DE6CC}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C16927E7-1358-4B9D-BDD7-149E505DE6CC}.Release|Any CPU.Build.0 = Release|Any CPU
+ {6F5C80F0-9045-4098-913F-7BDAD135E6DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {6F5C80F0-9045-4098-913F-7BDAD135E6DD}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6F5C80F0-9045-4098-913F-7BDAD135E6DD}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {6F5C80F0-9045-4098-913F-7BDAD135E6DD}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {DC29EF19-8131-41DC-943F-813CF103F7E9}
+ EndGlobalSection
+EndGlobal
diff --git a/PoweredSoft.DynamicLinq/Constants.cs b/PoweredSoft.DynamicLinq/Constants.cs
new file mode 100644
index 0000000..4cb0a85
--- /dev/null
+++ b/PoweredSoft.DynamicLinq/Constants.cs
@@ -0,0 +1,28 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PoweredSoft.DynamicLinq
+{
+ public enum ConditionOperators
+ {
+ Equal,
+ GreaterThan,
+ GreaterThanOrEqual,
+ LessThan,
+ LessThanOrEqual,
+ Contains,
+ StartsWith,
+ EndsWith
+ }
+
+ internal static class Constants
+ {
+ internal static readonly MethodInfo ContainsMethod = typeof(string).GetMethod("Contains");
+ internal static readonly MethodInfo StartsWithMethod = typeof(string).GetMethod("StartsWith", new Type[] { typeof(string) });
+ internal static readonly MethodInfo EndsWithMethod = typeof(string).GetMethod("EndsWith", new Type[] { typeof(string) });
+ }
+}
diff --git a/PoweredSoft.DynamicLinq/Extensions/QueryableExtensions.cs b/PoweredSoft.DynamicLinq/Extensions/QueryableExtensions.cs
new file mode 100644
index 0000000..2594fe0
--- /dev/null
+++ b/PoweredSoft.DynamicLinq/Extensions/QueryableExtensions.cs
@@ -0,0 +1,28 @@
+using PoweredSoft.DynamicLinq.Fluent;
+using PoweredSoft.DynamicLinq.Helpers;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Linq.Expressions;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PoweredSoft.DynamicLinq.Extensions
+{
+ public static class QueryableExtensions
+ {
+ public static IQueryable Where(this IQueryable query, string path, ConditionOperators conditionOperator, object value, bool convertConstantToLeftOperator = true)
+ {
+ query = query.Query(qb => qb.Compare(path, conditionOperator, value, convertConstantToLeftOperator: convertConstantToLeftOperator));
+ return query;
+ }
+
+ public static IQueryable Query (this IQueryable query, Action> callback)
+ {
+ var queryBuilder = new QueryBuilder(query);
+ callback(queryBuilder);
+ var ret = queryBuilder.Build();
+ return ret;
+ }
+ }
+}
diff --git a/PoweredSoft.DynamicLinq/Fluent/QueryBuilder.cs b/PoweredSoft.DynamicLinq/Fluent/QueryBuilder.cs
new file mode 100644
index 0000000..10f8a37
--- /dev/null
+++ b/PoweredSoft.DynamicLinq/Fluent/QueryBuilder.cs
@@ -0,0 +1,107 @@
+using PoweredSoft.DynamicLinq.Helpers;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Linq.Expressions;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PoweredSoft.DynamicLinq.Fluent
+{
+ public class QueryBuilder
+ {
+ public IQueryable Query { get; set; }
+
+ public Type QueryableType { get; set; }
+
+ public List Parts { get; protected set; } = new List();
+
+ public QueryBuilder(IQueryable query)
+ {
+ Query = query;
+ }
+
+ public QueryBuilder Compare(string path, ConditionOperators conditionOperators, object value,
+ bool convertConstantToLeftOperator = true, bool and = true)
+ {
+ Parts.Add(new QueryFilterPart
+ {
+ And = and,
+ ConditionOperator = conditionOperators,
+ Path = path,
+ Value = value,
+ ConvertConstantToLeftOperator = convertConstantToLeftOperator
+ });
+
+ return this;
+ }
+
+ public QueryBuilder SubQuery(Action> subQuery, bool and = true)
+ {
+ // create query builder for same type.
+ var qb = new QueryBuilder(Query);
+
+ // callback.
+ subQuery(qb);
+
+ // create a query part.
+ var part = new QueryFilterPart();
+ part.And = and;
+ part.Parts = qb.Parts;
+ Parts.Add(part);
+
+ //return self.
+ return this;
+ }
+
+ public QueryBuilder And(string path, ConditionOperators conditionOperator, object value, bool convertConstantToLeftOperator = true)
+ => Compare(path, conditionOperator, value, convertConstantToLeftOperator: convertConstantToLeftOperator, and: true);
+
+ public QueryBuilder Or(string path, ConditionOperators conditionOperator, object value, bool convertConstantToLeftOperator = true)
+ => Compare(path, conditionOperator, value, convertConstantToLeftOperator: convertConstantToLeftOperator, and: false);
+
+ public QueryBuilder And(Action> subQuery)
+ => SubQuery(subQuery, true);
+
+ public QueryBuilder Or(Action> subQuery)
+ => SubQuery(subQuery, false);
+
+ public IQueryable Build()
+ {
+ var parameter = Expression.Parameter(typeof(T), "t");
+ var expression = BuildExpression(parameter, Parts);
+ var lambda = Expression.Lambda>(expression, parameter);
+ var query = Query.Where(lambda);
+ return query;
+ }
+
+ protected Expression BuildExpression(ParameterExpression parameter, List parts)
+ {
+ Expression ret = null;
+
+ parts.ForEach(part =>
+ {
+ Expression innerExpression;
+ if (part.Parts?.Any() == true)
+ innerExpression = BuildExpression(parameter, part.Parts);
+ else
+ innerExpression = BuildExpression(parameter, part);
+
+ if (ret != null)
+ ret = part.And ? Expression.And(ret, innerExpression) : Expression.Or(ret, innerExpression);
+ else
+ ret = innerExpression;
+ });
+
+ return ret;
+ }
+
+ private Expression BuildExpression(ParameterExpression parameter, QueryFilterPart part)
+ {
+ var member = QueryableHelpers.ResolvePathForExpression(parameter, part.Path);
+ var constant = part.ConvertConstantToLeftOperator ? QueryableHelpers.GetConstantSameAsLeftOperator(member, part.Value) : Expression.Constant(part.Value);
+ var expression = QueryableHelpers.GetConditionExpressionForMember(parameter, member, part.ConditionOperator, constant);
+ return expression;
+ }
+ }
+}
diff --git a/PoweredSoft.DynamicLinq/Fluent/QueryFilterPart.cs b/PoweredSoft.DynamicLinq/Fluent/QueryFilterPart.cs
new file mode 100644
index 0000000..42b88a9
--- /dev/null
+++ b/PoweredSoft.DynamicLinq/Fluent/QueryFilterPart.cs
@@ -0,0 +1,19 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PoweredSoft.DynamicLinq.Fluent
+{
+ public class QueryFilterPart
+ {
+ public string Path { get; set; }
+ public ConditionOperators ConditionOperator { get; set; }
+ public object Value { get; set; }
+ public bool And { get; set; }
+ public bool ConvertConstantToLeftOperator { get; set; }
+
+ public List Parts { get; set; } = new List();
+ }
+}
diff --git a/PoweredSoft.DynamicLinq/Helpers/QueryableHelpers.cs b/PoweredSoft.DynamicLinq/Helpers/QueryableHelpers.cs
new file mode 100644
index 0000000..5661841
--- /dev/null
+++ b/PoweredSoft.DynamicLinq/Helpers/QueryableHelpers.cs
@@ -0,0 +1,84 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Linq.Expressions;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PoweredSoft.DynamicLinq.Helpers
+{
+ public static class QueryableHelpers
+ {
+ public static Expression GetConditionExpressionForMember(ParameterExpression parameter, Expression member, ConditionOperators conditionOperator, ConstantExpression constant)
+ {
+ if (parameter == null)
+ throw new ArgumentNullException("parameter");
+
+ if (member == null)
+ throw new ArgumentNullException("member");
+
+ if (constant == null)
+ throw new ArgumentNullException("constant");
+
+ Expression ret = null;
+
+ if (conditionOperator == ConditionOperators.Equal)
+ ret = Expression.Equal(member, constant);
+ else if (conditionOperator == ConditionOperators.GreaterThan)
+ ret = Expression.GreaterThan(member, constant);
+ else if (conditionOperator == ConditionOperators.GreaterThanOrEqual)
+ ret = Expression.GreaterThanOrEqual(member, constant);
+ else if (conditionOperator == ConditionOperators.LessThan)
+ ret = Expression.LessThan(member, constant);
+ else if (conditionOperator == ConditionOperators.LessThanOrEqual)
+ ret = Expression.LessThanOrEqual(member, constant);
+ else if (conditionOperator == ConditionOperators.Contains)
+ ret = Expression.Call(member, Constants.ContainsMethod, constant);
+ else if (conditionOperator == ConditionOperators.StartsWith)
+ ret = Expression.Call(member, Constants.StartsWithMethod, constant);
+ else if (conditionOperator == ConditionOperators.EndsWith)
+ ret = Expression.Call(member, Constants.EndsWithMethod, constant);
+ else
+ throw new ArgumentException("conditionOperator", "Must supply a known condition operator");
+
+ return ret;
+ }
+
+ ///
+ /// Returns the right expression for a path supplied.
+ ///
+ /// Expression.Parameter(typeOfClassOrInterface)
+ /// the path you wish to resolve example Contact.Profile.FirstName
+ ///
+ public static Expression ResolvePathForExpression(ParameterExpression param, string path)
+ {
+ Expression body = param;
+ foreach (var member in path.Split('.'))
+ {
+ body = Expression.PropertyOrField(body, member);
+ }
+ return body;
+ }
+
+ public static ConstantExpression GetConstantSameAsLeftOperator(Expression member, object value)
+ {
+ if (member == null)
+ throw new ArgumentNullException("member");
+
+ if (value == null)
+ return Expression.Constant(null);
+
+ // the types.
+ var valueType = value.GetType();
+ var memberType = member.Type;
+
+ // if match.
+ if (valueType == memberType)
+ return Expression.Constant(value);
+
+ // attempt a conversion.
+ object convertedValue = TypeHelpers.ConvertFrom(memberType, value);
+ return Expression.Constant(convertedValue);
+ }
+ }
+}
diff --git a/PoweredSoft.DynamicLinq/Helpers/TypeHelpers.cs b/PoweredSoft.DynamicLinq/Helpers/TypeHelpers.cs
new file mode 100644
index 0000000..f6df8d6
--- /dev/null
+++ b/PoweredSoft.DynamicLinq/Helpers/TypeHelpers.cs
@@ -0,0 +1,32 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PoweredSoft.DynamicLinq.Helpers
+{
+ public static class TypeHelpers
+ {
+ public static object ConvertFrom(Type type, object source)
+ {
+ object ret = null;
+
+ // safe if null.
+ if (source == null)
+ return ret;
+
+ // not nullable type.
+ var notNullableType = Nullable.GetUnderlyingType(type);
+ if (notNullableType == null)
+ {
+ ret = Convert.ChangeType(source, type);
+ return ret;
+ }
+
+ // the ret.
+ ret = Convert.ChangeType(source, notNullableType);
+ return ret;
+ }
+ }
+}
diff --git a/PoweredSoft.DynamicLinq/PoweredSoft.DynamicLinq.csproj b/PoweredSoft.DynamicLinq/PoweredSoft.DynamicLinq.csproj
new file mode 100644
index 0000000..a06f403
--- /dev/null
+++ b/PoweredSoft.DynamicLinq/PoweredSoft.DynamicLinq.csproj
@@ -0,0 +1,53 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {2ABC5A60-B549-4ECD-BEF4-31CA7BA4EF06}
+ Library
+ Properties
+ PoweredSoft.DynamicLinq
+ PoweredSoft.DynamicLinq
+ v4.6.1
+ 512
+
+
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/PoweredSoft.DynamicLinq/Properties/AssemblyInfo.cs b/PoweredSoft.DynamicLinq/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..5bc6913
--- /dev/null
+++ b/PoweredSoft.DynamicLinq/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("PoweredSoft.DynamicLinq")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("PoweredSoft.DynamicLinq")]
+[assembly: AssemblyCopyright("Copyright © 2018")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("2abc5a60-b549-4ecd-bef4-31ca7ba4ef06")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]