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")]