From 514109cb7a97470bd3c4b06336490c74dd822762 Mon Sep 17 00:00:00 2001 From: Alex Gritton Date: Tue, 16 Mar 2021 22:23:41 -0400 Subject: [PATCH 1/6] Added NotContains and IEnumerable --- .vscode/launch.json | 27 ++++++++++++ .vscode/tasks.json | 42 +++++++++++++++++++ DynamicExpressions.UnitTests/Data.cs | 12 +++--- .../PredicateTests.cs | 30 ++++++++----- .../PropertyGetterTests.cs | 22 +++++----- DynamicExpressions/DynamicExpressions.cs | 21 +++++++++- DynamicExpressions/FilterOperator.cs | 1 + 7 files changed, 125 insertions(+), 30 deletions(-) create mode 100644 .vscode/launch.json create mode 100644 .vscode/tasks.json diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..4437e2f --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,27 @@ +{ + "version": "0.2.0", + "configurations": [ + { + // Use IntelliSense to find out which attributes exist for C# debugging + // Use hover for the description of the existing attributes + // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md + "name": ".NET Core Launch (console)", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + // If you have changed target frameworks, make sure to update the program path. + "program": "${workspaceFolder}/DynamicExpressions.Benchmarks/bin/Debug/netcoreapp3.1/DynamicExpressions.Benchmarks.dll", + "args": [], + "cwd": "${workspaceFolder}/DynamicExpressions.Benchmarks", + // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console + "console": "internalConsole", + "stopAtEntry": false + }, + { + "name": ".NET Core Attach", + "type": "coreclr", + "request": "attach", + "processId": "${command:pickProcess}" + } + ] +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..fedd936 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,42 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "build", + "command": "dotnet", + "type": "process", + "args": [ + "build", + "${workspaceFolder}/DynamicExpressions.Benchmarks/DynamicExpressions.Benchmarks.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "publish", + "command": "dotnet", + "type": "process", + "args": [ + "publish", + "${workspaceFolder}/DynamicExpressions.Benchmarks/DynamicExpressions.Benchmarks.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "watch", + "command": "dotnet", + "type": "process", + "args": [ + "watch", + "run", + "${workspaceFolder}/DynamicExpressions.Benchmarks/DynamicExpressions.Benchmarks.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + } + ] +} \ No newline at end of file diff --git a/DynamicExpressions.UnitTests/Data.cs b/DynamicExpressions.UnitTests/Data.cs index 6b8189f..abdc079 100644 --- a/DynamicExpressions.UnitTests/Data.cs +++ b/DynamicExpressions.UnitTests/Data.cs @@ -1,24 +1,24 @@ namespace DynamicExpressions.UnitTests { - internal class Entry + internal class Entry { - public Entry(int id, SubEntry subEntry = null) + public Entry(int id, SubEntry subEntry = null) { Id = id; SubEntry = subEntry; } public int Id { get; } - public SubEntry SubEntry { get; } + public SubEntry SubEntry { get; } } - internal class SubEntry + internal class SubEntry { - public SubEntry(string title) + public SubEntry(T title) { Title = title; } - public string Title { get; } + public T Title { get; } } } diff --git a/DynamicExpressions.UnitTests/PredicateTests.cs b/DynamicExpressions.UnitTests/PredicateTests.cs index 6596fcd..1b24e0c 100644 --- a/DynamicExpressions.UnitTests/PredicateTests.cs +++ b/DynamicExpressions.UnitTests/PredicateTests.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.Collections.Generic; using Xunit; namespace DynamicExpressions.UnitTests @@ -21,31 +17,43 @@ public class PredicateTests public void GetPredicate_ShouldHandleNumericalOperators(int id, string title, string property, FilterOperator op, object value) { - var entry = new Entry(id, new SubEntry(title)); - var predicate = DynamicExpressions.GetPredicate(property, op, value).Compile(); + var entry = new Entry(id, new SubEntry(title)); + var predicate = DynamicExpressions.GetPredicate>(property, op, value).Compile(); Assert.True(predicate(entry)); } [Theory] [InlineData(3, "Title 3", FilterOperator.Contains, "3")] + [InlineData(3, "Title 3", FilterOperator.NotContains, "5")] [InlineData(4, "Title 4", FilterOperator.StartsWith, "Title")] [InlineData(5, "Title 5", FilterOperator.EndsWith, "5")] public void GetPredicate_ShouldHandleNestedStringOperators(int id, string title, FilterOperator op, object value) { - var entry = new Entry(id, new SubEntry(title)); - var predicate = DynamicExpressions.GetPredicate("SubEntry.Title", op, value).Compile(); + var entry = new Entry(id, new SubEntry(title)); + var predicate = DynamicExpressions.GetPredicate>("SubEntry.Title", op, value).Compile(); + Assert.True(predicate(entry)); + } + + [Theory] + [InlineData(3, new string[1] { "Title 3" }, FilterOperator.Contains, "Title 3")] + [InlineData(3, new string[1] { "Title 3" }, FilterOperator.NotContains, "Title 5")] + public void GetPredicate_ShouldHandleEnumerableStringOperators(int id, string[] title, FilterOperator op, object value) + { + var entry = new Entry(id, new SubEntry(title)); + var predicate = DynamicExpressions.GetPredicate>("SubEntry.Title", op, value).Compile(); Assert.True(predicate(entry)); } [Theory] [InlineData(1, "Title 1", FilterOperator.Contains, 1)] + [InlineData(3, "Title 3", FilterOperator.NotContains, 5)] [InlineData(1, "1 Title", FilterOperator.StartsWith, 1)] [InlineData(1, "Title 1", FilterOperator.EndsWith, 1)] public void GetPredicate_ShouldWork_WhenValueIsNotStringAndOperatorIsStringBased(int id, string title, FilterOperator op, object value) { - var entry = new Entry(id, new SubEntry(title)); - var predicate = DynamicExpressions.GetPredicate("SubEntry.Title", op, value).Compile(); + var entry = new Entry(id, new SubEntry(title)); + var predicate = DynamicExpressions.GetPredicate>("SubEntry.Title", op, value).Compile(); Assert.True(predicate(entry)); } } diff --git a/DynamicExpressions.UnitTests/PropertyGetterTests.cs b/DynamicExpressions.UnitTests/PropertyGetterTests.cs index acd316a..be592d0 100644 --- a/DynamicExpressions.UnitTests/PropertyGetterTests.cs +++ b/DynamicExpressions.UnitTests/PropertyGetterTests.cs @@ -21,9 +21,9 @@ public void GetPropertyGetter_ShouldThrow_WhenPropertyIsNull() [Fact] public void GetPropertyGetter_ShouldReturnCorrectGetter() { - var entry = new Entry(2); + var entry = new Entry(2); - var getter = DynamicExpressions.GetPropertyGetter("Id").Compile(); + var getter = DynamicExpressions.GetPropertyGetter>("Id").Compile(); var id = getter(entry); Assert.Equal(entry.Id, id); @@ -32,13 +32,13 @@ public void GetPropertyGetter_ShouldReturnCorrectGetter() [Fact] public void GetPropertyGetter_ShouldBeUsableInQueryableOrderBy() { - var entries = new List + var entries = new List> { - new Entry(1), - new Entry(2), + new Entry(1), + new Entry(2), }; - var getter = DynamicExpressions.GetPropertyGetter("Id"); + var getter = DynamicExpressions.GetPropertyGetter>("Id"); var sortedEntries = entries.AsQueryable().OrderByDescending(getter).ToList(); Assert.Equal(entries[0], sortedEntries[1]); @@ -48,19 +48,19 @@ public void GetPropertyGetter_ShouldBeUsableInQueryableOrderBy() [Fact] public void GetPropertyGetter_ShouldThrow_WhenPropertyDoesntExist() { - var entry = new Entry(2); + var entry = new Entry(2); - var ex = Assert.Throws(() => DynamicExpressions.GetPropertyGetter("Test")); + var ex = Assert.Throws(() => DynamicExpressions.GetPropertyGetter>("Test")); - Assert.Equal("Instance property 'Test' is not defined for type 'DynamicExpressions.UnitTests.Entry' (Parameter 'propertyName')", ex.Message); + Assert.Equal("Instance property 'Test' is not defined for type 'DynamicExpressions.UnitTests.Entry`1[System.String]' (Parameter 'propertyName')", ex.Message); } [Fact] public void GetPropertyGetter_ShouldHandleNestedProperty() { - var entry = new Entry(1, new SubEntry("Title")); + var entry = new Entry(1, new SubEntry("Title")); - var getter = DynamicExpressions.GetPropertyGetter("SubEntry.Title").Compile(); + var getter = DynamicExpressions.GetPropertyGetter>("SubEntry.Title").Compile(); var value = getter(entry); Assert.Equal(entry.SubEntry.Title, value); diff --git a/DynamicExpressions/DynamicExpressions.cs b/DynamicExpressions/DynamicExpressions.cs index b8ddf25..5f27456 100644 --- a/DynamicExpressions/DynamicExpressions.cs +++ b/DynamicExpressions/DynamicExpressions.cs @@ -1,4 +1,7 @@ using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; using System.Linq.Expressions; using System.Reflection; @@ -7,11 +10,13 @@ namespace DynamicExpressions public static class DynamicExpressions { private static readonly Type _stringType = typeof(string); + private static readonly Type _enumerableType = typeof(IEnumerable<>); private static readonly MethodInfo _toStringMethod = typeof(object).GetMethod("ToString"); - private static readonly MethodInfo _containsMethod = typeof(string).GetMethod("Contains" + private static readonly MethodInfo _stringContainsMethod = typeof(string).GetMethod("Contains" , new Type[] { typeof(string) }); + private static readonly MethodInfo _enumerableContainsMethod = typeof(Enumerable).GetMethods().Where(x => string.Equals(x.Name, "Contains", StringComparison.OrdinalIgnoreCase)).Single(x => x.GetParameters().Length == 2).MakeGenericMethod(typeof(string)); private static readonly MethodInfo _endsWithMethod = typeof(string).GetMethod("EndsWith", new Type[] { typeof(string) }); @@ -50,7 +55,8 @@ private static Expression CreateFilter(MemberExpression prop, FilterOperator op, FilterOperator.Equals => Expression.Equal(prop, constant), FilterOperator.GreaterThan => Expression.GreaterThan(prop, constant), FilterOperator.LessThan => Expression.LessThan(prop, constant), - FilterOperator.Contains => Expression.Call(prop, _containsMethod, PrepareConstant(constant)), + FilterOperator.Contains => GetContainsMethodCallExpression(prop, op, constant), + FilterOperator.NotContains => Expression.Not(GetContainsMethodCallExpression(prop, op, constant)), FilterOperator.StartsWith => Expression.Call(prop, _startsWithMethod, PrepareConstant(constant)), FilterOperator.EndsWith => Expression.Call(prop, _endsWithMethod, PrepareConstant(constant)), FilterOperator.DoesntEqual => Expression.NotEqual(prop, constant), @@ -60,10 +66,21 @@ private static Expression CreateFilter(MemberExpression prop, FilterOperator op, }; } + private static MethodCallExpression GetContainsMethodCallExpression(MemberExpression prop, FilterOperator filterOperator, ConstantExpression constant) + { + if (prop.Type == _stringType) + return Expression.Call(prop, _stringContainsMethod, PrepareConstant(constant)); + else return Expression.Call(_enumerableContainsMethod, prop, PrepareConstant(constant)); + } + private static Expression PrepareConstant(ConstantExpression constant) { if (constant.Type == _stringType) return constant; + else if (constant.GetType().GetInterfaces().Any( + i => i.IsGenericType && + i.GetGenericTypeDefinition() == typeof(IEnumerable<>))) + return constant; var convertedExpr = Expression.Convert(constant, typeof(object)); return Expression.Call(convertedExpr, _toStringMethod); diff --git a/DynamicExpressions/FilterOperator.cs b/DynamicExpressions/FilterOperator.cs index cef71d8..9971d86 100644 --- a/DynamicExpressions/FilterOperator.cs +++ b/DynamicExpressions/FilterOperator.cs @@ -9,6 +9,7 @@ public enum FilterOperator LessThan, LessThanOrEqual, Contains, + NotContains, StartsWith, EndsWith } From 3e4e6295058a74b021f525ebe2655c202f4daec5 Mon Sep 17 00:00:00 2001 From: Alex Gritton Date: Wed, 17 Mar 2021 16:27:02 -0400 Subject: [PATCH 2/6] Added new filter operators as well as string dictionary support. --- .gitignore | 3 ++ .../PredicateTests.cs | 52 +++++++++++++++++-- DynamicExpressions/DynamicExpressions.cs | 27 +++++++--- DynamicExpressions/DynamicExpressions.csproj | 5 ++ DynamicExpressions/FilterOperator.cs | 8 ++- 5 files changed, 83 insertions(+), 12 deletions(-) diff --git a/.gitignore b/.gitignore index e645270..21c2eec 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,9 @@ *.userosscache *.sln.docstates +nuget.config +.nuspec + # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs diff --git a/DynamicExpressions.UnitTests/PredicateTests.cs b/DynamicExpressions.UnitTests/PredicateTests.cs index 1b24e0c..73ddefa 100644 --- a/DynamicExpressions.UnitTests/PredicateTests.cs +++ b/DynamicExpressions.UnitTests/PredicateTests.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using Xunit; +using Xunit.Extensions; namespace DynamicExpressions.UnitTests { @@ -35,15 +36,56 @@ public void GetPredicate_ShouldHandleNestedStringOperators(int id, string title, } [Theory] - [InlineData(3, new string[1] { "Title 3" }, FilterOperator.Contains, "Title 3")] - [InlineData(3, new string[1] { "Title 3" }, FilterOperator.NotContains, "Title 5")] - public void GetPredicate_ShouldHandleEnumerableStringOperators(int id, string[] title, FilterOperator op, object value) + [InlineData(3, "", FilterOperator.IsEmpty, "doesn't matter. ignored")] + [InlineData(3, null, FilterOperator.IsEmpty, "")] + [InlineData(3, "Title 3", FilterOperator.IsNotEmpty, "")] + public void GetPredicate_ShouldHandleNullOrEmtpyStringOperators(int id, string title, FilterOperator op, object value) { - var entry = new Entry(id, new SubEntry(title)); - var predicate = DynamicExpressions.GetPredicate>("SubEntry.Title", op, value).Compile(); + var entry = new Entry(id, new SubEntry(title)); + var predicate = DynamicExpressions.GetPredicate>("SubEntry.Title", op, value).Compile(); + Assert.True(predicate(entry)); + } + + [Theory] + [InlineData(3, "I'm not empty", FilterOperator.IsEmpty, "doesn't matter. ignored")] + [InlineData(3, "Neither am i", FilterOperator.IsEmpty, "")] + [InlineData(3, "", FilterOperator.IsNotEmpty, "")] + [InlineData(3, null, FilterOperator.IsNotEmpty, "")] + public void GetPredicate_ShouldHandleNullOrEmtpyStringOperatorsFalse(int id, string title, FilterOperator op, object value) + { + var entry = new Entry(id, new SubEntry(title)); + var predicate = DynamicExpressions.GetPredicate>("SubEntry.Title", op, value).Compile(); + Assert.False(predicate(entry)); + } + + [Theory] + [MemberData(nameof(ListTestData))] + public void GetPredicate_ShouldHandleEnumerableStringOperators(int id, T title, FilterOperator op, object value) + { + var entry = new Entry(id, new SubEntry(title)); + var predicate = DynamicExpressions.GetPredicate>("SubEntry.Title", op, value).Compile(); Assert.True(predicate(entry)); } + public static IEnumerable ListTestData + { + get + { + return new[]{ + new object[]{3, new List() { "Title 3" }, FilterOperator.Contains, "Title 3" }, + new object[]{3, new List() { "Title 3" }, FilterOperator.NotContains, "Title 5" }, + new object[]{3, new Dictionary { { "Key 3", "Value 1" } }, FilterOperator.Contains, "Key 3" }, + new object[]{3, new Dictionary { { "Key 3", "Value 1" } }, FilterOperator.Contains, "Value 1" }, + new object[]{3, new Dictionary { { "Key 3", "Value 1" } }, FilterOperator.NotContains, "Key 5" }, + new object[]{3, new Dictionary { { "Key 3", "Value 1" } }, FilterOperator.NotContains, "Value 5" }, + new object[]{3, new Dictionary { { "Key 3", "Value 1" } }, FilterOperator.ContainsKey, "Key 3" }, + new object[]{3, new Dictionary { { "Key 3", "Value 1" } }, FilterOperator.ContainsValue, "Value 1" }, + new object[]{3, new Dictionary { { "Key 3", "Value 1" } }, FilterOperator.NotContainsKey, "Key 5" }, + new object[]{3, new Dictionary { { "Key 3", "Value 1" } }, FilterOperator.NotContainsValue, "Value 5" } + }; + } + } + [Theory] [InlineData(1, "Title 1", FilterOperator.Contains, 1)] [InlineData(3, "Title 3", FilterOperator.NotContains, 5)] diff --git a/DynamicExpressions/DynamicExpressions.cs b/DynamicExpressions/DynamicExpressions.cs index 5f27456..3af40b9 100644 --- a/DynamicExpressions/DynamicExpressions.cs +++ b/DynamicExpressions/DynamicExpressions.cs @@ -18,9 +18,15 @@ public static class DynamicExpressions , new Type[] { typeof(string) }); private static readonly MethodInfo _enumerableContainsMethod = typeof(Enumerable).GetMethods().Where(x => string.Equals(x.Name, "Contains", StringComparison.OrdinalIgnoreCase)).Single(x => x.GetParameters().Length == 2).MakeGenericMethod(typeof(string)); + private static readonly MethodInfo _dictionaryContainsKeyMethod = typeof(Dictionary).GetMethods().Where(x => string.Equals(x.Name, "ContainsKey", StringComparison.OrdinalIgnoreCase)).Single(); + private static readonly MethodInfo _dictionaryContainsValueMethod = typeof(Dictionary).GetMethods().Where(x => string.Equals(x.Name, "ContainsValue", StringComparison.OrdinalIgnoreCase)).Single(); + private static readonly MethodInfo _endsWithMethod = typeof(string).GetMethod("EndsWith", new Type[] { typeof(string) }); + private static readonly MethodInfo _isNullOrEmtpyMethod + = typeof(string).GetMethod("IsNullOrEmpty", new Type[] { typeof(string) }); + private static readonly MethodInfo _startsWithMethod = typeof(string).GetMethod("StartsWith", new Type[] { typeof(string) }); @@ -57,30 +63,39 @@ private static Expression CreateFilter(MemberExpression prop, FilterOperator op, FilterOperator.LessThan => Expression.LessThan(prop, constant), FilterOperator.Contains => GetContainsMethodCallExpression(prop, op, constant), FilterOperator.NotContains => Expression.Not(GetContainsMethodCallExpression(prop, op, constant)), + FilterOperator.ContainsKey => Expression.Call(prop, _dictionaryContainsKeyMethod, PrepareConstant(constant)), + FilterOperator.NotContainsKey => Expression.Not(Expression.Call(prop, _dictionaryContainsKeyMethod, PrepareConstant(constant))), + FilterOperator.ContainsValue => Expression.Call(prop, _dictionaryContainsValueMethod, PrepareConstant(constant)), + FilterOperator.NotContainsValue => Expression.Not(Expression.Call(prop, _dictionaryContainsValueMethod, PrepareConstant(constant))), FilterOperator.StartsWith => Expression.Call(prop, _startsWithMethod, PrepareConstant(constant)), FilterOperator.EndsWith => Expression.Call(prop, _endsWithMethod, PrepareConstant(constant)), FilterOperator.DoesntEqual => Expression.NotEqual(prop, constant), FilterOperator.GreaterThanOrEqual => Expression.GreaterThanOrEqual(prop, constant), FilterOperator.LessThanOrEqual => Expression.LessThanOrEqual(prop, constant), + FilterOperator.IsEmpty => Expression.Call(_isNullOrEmtpyMethod, prop), + FilterOperator.IsNotEmpty => Expression.Not(Expression.Call(_isNullOrEmtpyMethod, prop)), _ => throw new NotImplementedException() }; } - private static MethodCallExpression GetContainsMethodCallExpression(MemberExpression prop, FilterOperator filterOperator, ConstantExpression constant) + private static Expression GetContainsMethodCallExpression(MemberExpression prop, FilterOperator filterOperator, ConstantExpression constant) { if (prop.Type == _stringType) return Expression.Call(prop, _stringContainsMethod, PrepareConstant(constant)); - else return Expression.Call(_enumerableContainsMethod, prop, PrepareConstant(constant)); + else if (prop.Type.GetInterfaces().Contains(typeof(IDictionary))) + return Expression.Or(Expression.Call(prop, _dictionaryContainsKeyMethod, PrepareConstant(constant)), Expression.Call(prop, _dictionaryContainsValueMethod, PrepareConstant(constant))); + else if (prop.Type.GetInterfaces().Contains(typeof(IEnumerable))) + return Expression.Call(_enumerableContainsMethod, prop, PrepareConstant(constant)); + + throw new NotImplementedException($"{prop.Type} contains is not implemented."); + + } private static Expression PrepareConstant(ConstantExpression constant) { if (constant.Type == _stringType) return constant; - else if (constant.GetType().GetInterfaces().Any( - i => i.IsGenericType && - i.GetGenericTypeDefinition() == typeof(IEnumerable<>))) - return constant; var convertedExpr = Expression.Convert(constant, typeof(object)); return Expression.Call(convertedExpr, _toStringMethod); diff --git a/DynamicExpressions/DynamicExpressions.csproj b/DynamicExpressions/DynamicExpressions.csproj index 5793b87..a4f35cd 100644 --- a/DynamicExpressions/DynamicExpressions.csproj +++ b/DynamicExpressions/DynamicExpressions.csproj @@ -4,6 +4,11 @@ netstandard2.1 DynamicExpressions DynamicExpressions + 1.0.0 + alexgritton.DynamicExpressions + alexgritton.DynamicExpressions + https://github.com/alexgritton/DynamicExpressions + git diff --git a/DynamicExpressions/FilterOperator.cs b/DynamicExpressions/FilterOperator.cs index 9971d86..da192b0 100644 --- a/DynamicExpressions/FilterOperator.cs +++ b/DynamicExpressions/FilterOperator.cs @@ -11,6 +11,12 @@ public enum FilterOperator Contains, NotContains, StartsWith, - EndsWith + EndsWith, + IsEmpty, + IsNotEmpty, + ContainsKey, + NotContainsKey, + ContainsValue, + NotContainsValue } } From baa0b2d49d58b46052efb668c4f69c7f6ed1672a Mon Sep 17 00:00:00 2001 From: Alex Gritton Date: Wed, 17 Mar 2021 16:49:09 -0400 Subject: [PATCH 3/6] Removed local nuget publishing files --- .vscode/launch.json | 27 --------------------------- .vscode/tasks.json | 42 ------------------------------------------ 2 files changed, 69 deletions(-) delete mode 100644 .vscode/launch.json delete mode 100644 .vscode/tasks.json diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index 4437e2f..0000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "version": "0.2.0", - "configurations": [ - { - // Use IntelliSense to find out which attributes exist for C# debugging - // Use hover for the description of the existing attributes - // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md - "name": ".NET Core Launch (console)", - "type": "coreclr", - "request": "launch", - "preLaunchTask": "build", - // If you have changed target frameworks, make sure to update the program path. - "program": "${workspaceFolder}/DynamicExpressions.Benchmarks/bin/Debug/netcoreapp3.1/DynamicExpressions.Benchmarks.dll", - "args": [], - "cwd": "${workspaceFolder}/DynamicExpressions.Benchmarks", - // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console - "console": "internalConsole", - "stopAtEntry": false - }, - { - "name": ".NET Core Attach", - "type": "coreclr", - "request": "attach", - "processId": "${command:pickProcess}" - } - ] -} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json deleted file mode 100644 index fedd936..0000000 --- a/.vscode/tasks.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "version": "2.0.0", - "tasks": [ - { - "label": "build", - "command": "dotnet", - "type": "process", - "args": [ - "build", - "${workspaceFolder}/DynamicExpressions.Benchmarks/DynamicExpressions.Benchmarks.csproj", - "/property:GenerateFullPaths=true", - "/consoleloggerparameters:NoSummary" - ], - "problemMatcher": "$msCompile" - }, - { - "label": "publish", - "command": "dotnet", - "type": "process", - "args": [ - "publish", - "${workspaceFolder}/DynamicExpressions.Benchmarks/DynamicExpressions.Benchmarks.csproj", - "/property:GenerateFullPaths=true", - "/consoleloggerparameters:NoSummary" - ], - "problemMatcher": "$msCompile" - }, - { - "label": "watch", - "command": "dotnet", - "type": "process", - "args": [ - "watch", - "run", - "${workspaceFolder}/DynamicExpressions.Benchmarks/DynamicExpressions.Benchmarks.csproj", - "/property:GenerateFullPaths=true", - "/consoleloggerparameters:NoSummary" - ], - "problemMatcher": "$msCompile" - } - ] -} \ No newline at end of file From f387c3de68e7f775532e1b580b89f277269137db Mon Sep 17 00:00:00 2001 From: Alex Gritton Date: Wed, 17 Mar 2021 16:49:46 -0400 Subject: [PATCH 4/6] Moved local nuget files to separate untracked files. --- .gitignore | 2 ++ DynamicExpressions/DynamicExpressions.csproj | 10 ++++------ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index 21c2eec..adcf094 100644 --- a/.gitignore +++ b/.gitignore @@ -10,8 +10,10 @@ *.userosscache *.sln.docstates +.vscode/**/* nuget.config .nuspec +Nuget.csproj # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs diff --git a/DynamicExpressions/DynamicExpressions.csproj b/DynamicExpressions/DynamicExpressions.csproj index a4f35cd..45c6a9e 100644 --- a/DynamicExpressions/DynamicExpressions.csproj +++ b/DynamicExpressions/DynamicExpressions.csproj @@ -3,12 +3,10 @@ netstandard2.1 DynamicExpressions - DynamicExpressions - 1.0.0 - alexgritton.DynamicExpressions - alexgritton.DynamicExpressions - https://github.com/alexgritton/DynamicExpressions - git + DynamicExpressions + + + From 6c63a515b3a78c3d71c80cfbb246c3eef681497f Mon Sep 17 00:00:00 2001 From: Alex Gritton Date: Wed, 17 Mar 2021 20:16:38 -0400 Subject: [PATCH 5/6] Added better boolean equals cases --- .../PredicateTests.cs | 15 ++++++++++++++ DynamicExpressions/DynamicExpressions.cs | 20 ++++++++++++++----- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/DynamicExpressions.UnitTests/PredicateTests.cs b/DynamicExpressions.UnitTests/PredicateTests.cs index 73ddefa..4b60348 100644 --- a/DynamicExpressions.UnitTests/PredicateTests.cs +++ b/DynamicExpressions.UnitTests/PredicateTests.cs @@ -23,6 +23,21 @@ public void GetPredicate_ShouldHandleNumericalOperators(int id, string title, Assert.True(predicate(entry)); } + [Theory] + [InlineData(1, true, "SubEntry.Title", FilterOperator.Equals, true)] + [InlineData(1, true, "SubEntry.Title", FilterOperator.Equals, "true")] + [InlineData(1, true, "SubEntry.Title", FilterOperator.Equals, "True")] + [InlineData(1, false, "SubEntry.Title", FilterOperator.Equals, "False")] + [InlineData(1, false, "SubEntry.Title", FilterOperator.Equals, "false")] + [InlineData(1, false, "SubEntry.Title", FilterOperator.Equals, false)] + public void GetPredicate_ShouldHandleEqualsGenericOperators(int id, T title, + string property, FilterOperator op, object value) + { + var entry = new Entry(id, new SubEntry(title)); + var predicate = DynamicExpressions.GetPredicate>(property, op, value).Compile(); + Assert.True(predicate(entry)); + } + [Theory] [InlineData(3, "Title 3", FilterOperator.Contains, "3")] [InlineData(3, "Title 3", FilterOperator.NotContains, "5")] diff --git a/DynamicExpressions/DynamicExpressions.cs b/DynamicExpressions/DynamicExpressions.cs index 3af40b9..c2ef463 100644 --- a/DynamicExpressions/DynamicExpressions.cs +++ b/DynamicExpressions/DynamicExpressions.cs @@ -58,18 +58,18 @@ private static Expression CreateFilter(MemberExpression prop, FilterOperator op, { return op switch { - FilterOperator.Equals => Expression.Equal(prop, constant), + FilterOperator.Equals => RobustEquals(prop, constant), FilterOperator.GreaterThan => Expression.GreaterThan(prop, constant), FilterOperator.LessThan => Expression.LessThan(prop, constant), - FilterOperator.Contains => GetContainsMethodCallExpression(prop, op, constant), - FilterOperator.NotContains => Expression.Not(GetContainsMethodCallExpression(prop, op, constant)), + FilterOperator.Contains => GetContainsMethodCallExpression(prop, constant), + FilterOperator.NotContains => Expression.Not(GetContainsMethodCallExpression(prop, constant)), FilterOperator.ContainsKey => Expression.Call(prop, _dictionaryContainsKeyMethod, PrepareConstant(constant)), FilterOperator.NotContainsKey => Expression.Not(Expression.Call(prop, _dictionaryContainsKeyMethod, PrepareConstant(constant))), FilterOperator.ContainsValue => Expression.Call(prop, _dictionaryContainsValueMethod, PrepareConstant(constant)), FilterOperator.NotContainsValue => Expression.Not(Expression.Call(prop, _dictionaryContainsValueMethod, PrepareConstant(constant))), FilterOperator.StartsWith => Expression.Call(prop, _startsWithMethod, PrepareConstant(constant)), FilterOperator.EndsWith => Expression.Call(prop, _endsWithMethod, PrepareConstant(constant)), - FilterOperator.DoesntEqual => Expression.NotEqual(prop, constant), + FilterOperator.DoesntEqual => Expression.Not(RobustEquals(prop, constant)), FilterOperator.GreaterThanOrEqual => Expression.GreaterThanOrEqual(prop, constant), FilterOperator.LessThanOrEqual => Expression.LessThanOrEqual(prop, constant), FilterOperator.IsEmpty => Expression.Call(_isNullOrEmtpyMethod, prop), @@ -78,7 +78,16 @@ private static Expression CreateFilter(MemberExpression prop, FilterOperator op, }; } - private static Expression GetContainsMethodCallExpression(MemberExpression prop, FilterOperator filterOperator, ConstantExpression constant) + private static Expression RobustEquals(MemberExpression prop, ConstantExpression constant) + { + if (prop.Type == typeof(bool) && bool.TryParse(constant.Value.ToString(), out var val)) + { + return Expression.Equal(prop, Expression.Constant(val)); + } + return Expression.Equal(prop, constant); + } + + private static Expression GetContainsMethodCallExpression(MemberExpression prop, ConstantExpression constant) { if (prop.Type == _stringType) return Expression.Call(prop, _stringContainsMethod, PrepareConstant(constant)); @@ -94,6 +103,7 @@ private static Expression GetContainsMethodCallExpression(MemberExpression prop, private static Expression PrepareConstant(ConstantExpression constant) { + if (constant.Type == _stringType) return constant; From 6e6f28e5b4ec830f9da21bee2882152f64627601 Mon Sep 17 00:00:00 2001 From: zHaytam Date: Sun, 14 Nov 2021 22:56:41 +0100 Subject: [PATCH 6/6] Update DynamicExpressions.csproj --- DynamicExpressions/DynamicExpressions.csproj | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/DynamicExpressions/DynamicExpressions.csproj b/DynamicExpressions/DynamicExpressions.csproj index 45c6a9e..e3a27c1 100644 --- a/DynamicExpressions/DynamicExpressions.csproj +++ b/DynamicExpressions/DynamicExpressions.csproj @@ -3,8 +3,15 @@ netstandard2.1 DynamicExpressions - DynamicExpressions - + DynamicExpressions + true + MIT + zHaytam + zHaytam + DynamicExpressions.NET + DynamicExpressions.NET + https://github.com/zHaytam/DynamicExpressions + A dynamic expression builder that can be used to dynamically sort and/or filter LINQ/EF queries