diff --git a/System.Linq.Dynamic.Core.sln b/System.Linq.Dynamic.Core.sln index 52668ccc..358f876f 100644 --- a/System.Linq.Dynamic.Core.sln +++ b/System.Linq.Dynamic.Core.sln @@ -39,7 +39,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test-console", "test-consol EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test-uap", "test-uap", "{ECA5702B-5D32-4888-A34E-9461FC533F23}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConsoleApp_netcore2.0_EF2.0.1", "src-console\ConsoleAppEF2.0\ConsoleApp_netcore2.0_EF2.0.1.csproj", "{60CE11E0-E057-45A2-8F8A-73B1BD045BFB}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConsoleApp_netcore2.0_EF2.0.1", "src-console\ConsoleAppEF2.0\ConsoleApp_netcore2.0_EF2.0.1.csproj", "{60CE11E0-E057-45A2-8F8A-73B1BD045BFB}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConsoleApp_netcore1.1_EF1.1.2", "src-console\ConsoleAppEF1.1\ConsoleApp_netcore1.1_EF1.1.2.csproj", "{59E67CE3-5DB7-4D37-B308-E4E22E3DE4F4}" EndProject @@ -49,6 +49,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConsoleApp_net40_sqlite", " EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConsoleApp_net452_EF6", "src-console\ConsoleApp_net452_EF6\ConsoleApp_net452_EF6.csproj", "{5DC68E83-ABE0-4887-B17E-1ED4EEE89C2C}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConsoleApp_netcore2.0_EF2.0.2_InMemory", "src-console\ConsoleAppEF2.0.2_InMemory\ConsoleApp_netcore2.0_EF2.0.2_InMemory.csproj", "{437473EE-7FBB-4C28-96EC-41E1AEE161F3}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -251,6 +253,22 @@ Global {5DC68E83-ABE0-4887-B17E-1ED4EEE89C2C}.Release|x64.Build.0 = Release|Any CPU {5DC68E83-ABE0-4887-B17E-1ED4EEE89C2C}.Release|x86.ActiveCfg = Release|Any CPU {5DC68E83-ABE0-4887-B17E-1ED4EEE89C2C}.Release|x86.Build.0 = Release|Any CPU + {437473EE-7FBB-4C28-96EC-41E1AEE161F3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {437473EE-7FBB-4C28-96EC-41E1AEE161F3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {437473EE-7FBB-4C28-96EC-41E1AEE161F3}.Debug|ARM.ActiveCfg = Debug|Any CPU + {437473EE-7FBB-4C28-96EC-41E1AEE161F3}.Debug|ARM.Build.0 = Debug|Any CPU + {437473EE-7FBB-4C28-96EC-41E1AEE161F3}.Debug|x64.ActiveCfg = Debug|Any CPU + {437473EE-7FBB-4C28-96EC-41E1AEE161F3}.Debug|x64.Build.0 = Debug|Any CPU + {437473EE-7FBB-4C28-96EC-41E1AEE161F3}.Debug|x86.ActiveCfg = Debug|Any CPU + {437473EE-7FBB-4C28-96EC-41E1AEE161F3}.Debug|x86.Build.0 = Debug|Any CPU + {437473EE-7FBB-4C28-96EC-41E1AEE161F3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {437473EE-7FBB-4C28-96EC-41E1AEE161F3}.Release|Any CPU.Build.0 = Release|Any CPU + {437473EE-7FBB-4C28-96EC-41E1AEE161F3}.Release|ARM.ActiveCfg = Release|Any CPU + {437473EE-7FBB-4C28-96EC-41E1AEE161F3}.Release|ARM.Build.0 = Release|Any CPU + {437473EE-7FBB-4C28-96EC-41E1AEE161F3}.Release|x64.ActiveCfg = Release|Any CPU + {437473EE-7FBB-4C28-96EC-41E1AEE161F3}.Release|x64.Build.0 = Release|Any CPU + {437473EE-7FBB-4C28-96EC-41E1AEE161F3}.Release|x86.ActiveCfg = Release|Any CPU + {437473EE-7FBB-4C28-96EC-41E1AEE161F3}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -268,6 +286,7 @@ Global {0EB12661-F5CF-4071-9812-F4C8AF3D4531} = {7971CAEB-B9F2-416B-966D-2D697C4C1E62} {E587974C-19A9-426A-A952-216D5F883EB6} = {7971CAEB-B9F2-416B-966D-2D697C4C1E62} {5DC68E83-ABE0-4887-B17E-1ED4EEE89C2C} = {7971CAEB-B9F2-416B-966D-2D697C4C1E62} + {437473EE-7FBB-4C28-96EC-41E1AEE161F3} = {7971CAEB-B9F2-416B-966D-2D697C4C1E62} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {94C56722-194E-4B8B-BC23-B3F754E89A20} diff --git a/src-console/ConsoleAppEF2.0.2_InMemory/ConsoleApp_netcore2.0_EF2.0.2_InMemory.csproj b/src-console/ConsoleAppEF2.0.2_InMemory/ConsoleApp_netcore2.0_EF2.0.2_InMemory.csproj new file mode 100644 index 00000000..12d6f7e8 --- /dev/null +++ b/src-console/ConsoleAppEF2.0.2_InMemory/ConsoleApp_netcore2.0_EF2.0.2_InMemory.csproj @@ -0,0 +1,19 @@ + + + + Exe + netcoreapp2.0 + ConsoleAppEF2 + ConsoleAppEF2 + + + + + + + + + + + + diff --git a/src-console/ConsoleAppEF2.0.2_InMemory/Database/Car.cs b/src-console/ConsoleAppEF2.0.2_InMemory/Database/Car.cs new file mode 100644 index 00000000..fa817738 --- /dev/null +++ b/src-console/ConsoleAppEF2.0.2_InMemory/Database/Car.cs @@ -0,0 +1,23 @@ +using System.ComponentModel.DataAnnotations; + +namespace ConsoleAppEF2.Database +{ + public class Car + { + [Key] + public int Key { get; set; } + + [Required] + [StringLength(8)] + public string Vin { get; set; } + + [Required] + public string Year { get; set; } + + [Required] + public string Brand { get; set; } + + [Required] + public string Color { get; set; } + } +} diff --git a/src-console/ConsoleAppEF2.0.2_InMemory/Database/TestContext.cs b/src-console/ConsoleAppEF2.0.2_InMemory/Database/TestContext.cs new file mode 100644 index 00000000..ac411cd9 --- /dev/null +++ b/src-console/ConsoleAppEF2.0.2_InMemory/Database/TestContext.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; + +namespace ConsoleAppEF2.Database +{ + public class TestContext : DbContext + { + public virtual DbSet Cars { get; set; } + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + optionsBuilder.UseInMemoryDatabase("cars"); + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity().HasKey(c => c.Key); + } + + // https://stackoverflow.com/questions/46212704/how-do-i-write-ef-functions-extension-method + public static bool Like(string matchExpression, string pattern) => EF.Functions.Like(matchExpression, pattern); + } +} diff --git a/src-console/ConsoleAppEF2.0.2_InMemory/Program.cs b/src-console/ConsoleAppEF2.0.2_InMemory/Program.cs new file mode 100644 index 00000000..20ac2ff5 --- /dev/null +++ b/src-console/ConsoleAppEF2.0.2_InMemory/Program.cs @@ -0,0 +1,119 @@ +using System; +using System.Collections.Generic; +using System.Dynamic; +using System.Linq; +using System.Linq.Dynamic.Core; +using System.Linq.Dynamic.Core.CustomTypeProviders; +using ConsoleAppEF2.Database; +using Microsoft.EntityFrameworkCore; +using Newtonsoft.Json; + +namespace ConsoleAppEF2 +{ + class Program + { + class C : AbstractDynamicLinqCustomTypeProvider, IDynamicLinkCustomTypeProvider + { + public HashSet GetCustomTypes() + { + var assemblies = AppDomain.CurrentDomain.GetAssemblies(); + + var set = new HashSet(FindTypesMarkedWithDynamicLinqTypeAttribute(assemblies)) + { + typeof(TestContext) + }; + + return set; + } + } + + private static IQueryable GetQueryable() + { + var random = new Random((int)DateTime.Now.Ticks); + + var x = Enumerable.Range(0, 10).Select(i => new + { + Id = i, + Value = random.Next(), + }); + + return x.AsQueryable().Select("new (it as Id, @0 as Value)", random.Next()); + // return x.AsQueryable(); //x.AsQueryable().Select("new (Id, Value)"); + } + + static void Main(string[] args) + { + IQueryable qry = GetQueryable(); + + var result = qry.Select("it").OrderBy("Value"); + try + { + Console.WriteLine("result {0}", JsonConvert.SerializeObject(result, Formatting.Indented)); + } + catch (Exception) + { + // Console.WriteLine(e); + } + + var all = new + { + test1 = new List { 1, 2, 3 }.ToDynamicList(typeof(int)), + test2 = new List { 4, 5, 6 }.ToDynamicList(typeof(int)), + test3 = new List { 7, 8, 9 }.ToDynamicList(typeof(int)) + }; + Console.WriteLine("all {0}", JsonConvert.SerializeObject(all, Formatting.Indented)); + + var config = new ParsingConfig + { + CustomTypeProvider = new C() + }; + + var context = new TestContext(); + context.Cars.Add(new Car { Brand = "Ford", Color = "Blue", Vin = "yes", Year = "2017" }); + context.Cars.Add(new Car { Brand = "Fiat", Color = "Red", Vin = "yes", Year = "2016" }); + context.Cars.Add(new Car { Brand = "Alfa", Color = "Black", Vin = "no", Year = "1979" }); + context.Cars.Add(new Car { Brand = "Alfa", Color = "Black", Vin = "a%bc", Year = "1979" }); + context.SaveChanges(); + + var carFirstOrDefault = context.Cars.Where(config, "Brand == \"Ford\""); + Console.WriteLine("carFirstOrDefault {0}", JsonConvert.SerializeObject(carFirstOrDefault, Formatting.Indented)); + + var carsLike1 = + from c in context.Cars + where EF.Functions.Like(c.Brand, "%a%") + select c; + Console.WriteLine("carsLike1 {0}", JsonConvert.SerializeObject(carsLike1, Formatting.Indented)); + + var cars2Like = context.Cars.Where(c => EF.Functions.Like(c.Brand, "%a%")); + Console.WriteLine("cars2Like {0}", JsonConvert.SerializeObject(cars2Like, Formatting.Indented)); + + var dynamicCarsLike1 = context.Cars.Where(config, "TestContext.Like(Brand, \"%a%\")"); + Console.WriteLine("dynamicCarsLike1 {0}", JsonConvert.SerializeObject(dynamicCarsLike1, Formatting.Indented)); + + var dynamicCarsLike2 = context.Cars.Where(config, "TestContext.Like(Brand, \"%d%\")"); + Console.WriteLine("dynamicCarsLike2 {0}", JsonConvert.SerializeObject(dynamicCarsLike2, Formatting.Indented)); + + var dynamicFunctionsLike1 = context.Cars.Where(config, "DynamicFunctions.Like(Brand, \"%a%\")"); + Console.WriteLine("dynamicFunctionsLike1 {0}", JsonConvert.SerializeObject(dynamicFunctionsLike1, Formatting.Indented)); + + var dynamicFunctionsLike2 = context.Cars.Where(config, "DynamicFunctions.Like(Vin, \"%a.%b%\", \".\")"); + Console.WriteLine("dynamicFunctionsLike2 {0}", JsonConvert.SerializeObject(dynamicFunctionsLike2, Formatting.Indented)); + + var testDynamic = context.Cars.Select(c => new + { + K = c.Key, + C = c.Color + }); + + var testDynamicResult = testDynamic.Select("it").OrderBy("C"); + try + { + Console.WriteLine("resultX {0}", JsonConvert.SerializeObject(testDynamicResult, Formatting.Indented)); + } + catch (Exception e) + { + Console.WriteLine(e); + } + } + } +} diff --git a/src-console/ConsoleAppEF2.0/Program.cs b/src-console/ConsoleAppEF2.0/Program.cs index 12352e07..93992ca5 100644 --- a/src-console/ConsoleAppEF2.0/Program.cs +++ b/src-console/ConsoleAppEF2.0/Program.cs @@ -49,9 +49,9 @@ static void Main(string[] args) { Console.WriteLine("result {0}", JsonConvert.SerializeObject(result, Formatting.Indented)); } - catch (Exception e) + catch (Exception) { - Console.WriteLine(e); + // Console.WriteLine(e); } var all = new diff --git a/src/System.Linq.Dynamic.Core/DynamicExpressionParser.cs b/src/System.Linq.Dynamic.Core/DynamicExpressionParser.cs index efd4d58f..d0dd8565 100644 --- a/src/System.Linq.Dynamic.Core/DynamicExpressionParser.cs +++ b/src/System.Linq.Dynamic.Core/DynamicExpressionParser.cs @@ -1,4 +1,6 @@ -using System.Linq.Dynamic.Core.Parser; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq.Dynamic.Core.Parser; using JetBrains.Annotations; using System.Linq.Dynamic.Core.Validation; using System.Linq.Expressions; @@ -11,98 +13,169 @@ namespace System.Linq.Dynamic.Core public static class DynamicExpressionParser { /// - /// Parses an expression into a LambdaExpression. (Also create a constructor for all the parameters. Note that this doesn't work for Linq-to-Database entities.) + /// *** Parses an expression into a LambdaExpression. /// + /// The Configuration for the parsing. + /// if set to true then also create a constructor for all the parameters. Note that this doesn't work for Linq-to-Database entities. /// Type of the result. If not specified, it will be generated dynamically. /// The expression. - /// The Configuration for the parsing. /// An object array that contains zero or more objects which are used as replacement values. /// The generated [PublicAPI] - public static LambdaExpression ParseLambda([CanBeNull] ParsingConfig parsingConfig, [CanBeNull] Type resultType, [NotNull] string expression, params object[] values) + public static LambdaExpression ParseLambda([CanBeNull] ParsingConfig parsingConfig, bool createParameterCtor, [CanBeNull] Type resultType, [NotNull] string expression, params object[] values) { - return ParseLambda(parsingConfig, true, resultType, expression, values); + Check.NotEmpty(expression, nameof(expression)); + + var parser = new ExpressionParser(new ParameterExpression[0], expression, values, parsingConfig); + + return Expression.Lambda(parser.Parse(resultType, createParameterCtor)); } /// - /// Parses an expression into a LambdaExpression. (Also create a constructor for all the parameters. Note that this doesn't work for Linq-to-Database entities.) + /// Parses an expression into a Typed Expression. /// + /// The type of the result. + /// The Configuration for the parsing. + /// if set to true then also create a constructor for all the parameters. Note that this doesn't work for Linq-to-Database entities. + /// The expression. + /// An object array that contains zero or more objects which are used as replacement values. + /// The generated + [PublicAPI] + public static Expression> ParseLambda([CanBeNull] ParsingConfig parsingConfig, bool createParameterCtor, [NotNull] string expression, params object[] values) + { + return (Expression>)ParseLambda(parsingConfig, createParameterCtor, typeof(TResult), expression, values); + } + + /// + /// *** Parses an expression into a LambdaExpression. + /// + /// The Configuration for the parsing. + /// if set to true then also create a constructor for all the parameters. Note that this doesn't work for Linq-to-Database entities. + /// A array from ParameterExpressions. /// Type of the result. If not specified, it will be generated dynamically. /// The expression. /// An object array that contains zero or more objects which are used as replacement values. /// The generated [PublicAPI] - public static LambdaExpression ParseLambda([CanBeNull] Type resultType, [NotNull] string expression, params object[] values) + public static LambdaExpression ParseLambda([CanBeNull] ParsingConfig parsingConfig, bool createParameterCtor, [NotNull] ParameterExpression[] parameters, [CanBeNull] Type resultType, [NotNull] string expression, params object[] values) { + Check.NotNull(parameters, nameof(parameters)); + Check.HasNoNulls(parameters, nameof(parameters)); Check.NotEmpty(expression, nameof(expression)); - return ParseLambda(null, true, resultType, expression, values); + var parser = new ExpressionParser(parameters, expression, values, parsingConfig); + + return Expression.Lambda(parser.Parse(resultType, createParameterCtor), parameters); } /// - /// Parses an expression into a LambdaExpression. + /// Parses an expression into a Typed Expression. /// + /// The type of the result. /// The Configuration for the parsing. /// if set to true then also create a constructor for all the parameters. Note that this doesn't work for Linq-to-Database entities. + /// A array from ParameterExpressions. + /// The expression. + /// An object array that contains zero or more objects which are used as replacement values. + /// The generated + [PublicAPI] + public static Expression> ParseLambda([CanBeNull] ParsingConfig parsingConfig, bool createParameterCtor, [NotNull] ParameterExpression[] parameters, [NotNull] string expression, params object[] values) + { + return (Expression>)ParseLambda(parsingConfig, createParameterCtor, parameters, typeof(TResult), expression, values); + } + + /// + /// Parses an expression into a LambdaExpression. + /// + /// if set to true then also create a constructor for all the parameters. Note that this doesn't work for Linq-to-Database entities. + /// The main type from the dynamic class expression. /// Type of the result. If not specified, it will be generated dynamically. /// The expression. /// An object array that contains zero or more objects which are used as replacement values. /// The generated [PublicAPI] - public static LambdaExpression ParseLambda([CanBeNull] ParsingConfig parsingConfig, bool createParameterCtor, [CanBeNull] Type resultType, string expression, params object[] values) + public static LambdaExpression ParseLambda(bool createParameterCtor, [NotNull] Type itType, [CanBeNull] Type resultType, string expression, params object[] values) { + Check.NotNull(itType, nameof(itType)); Check.NotEmpty(expression, nameof(expression)); - var parser = new ExpressionParser(new ParameterExpression[0], expression, values, parsingConfig); + return ParseLambda(createParameterCtor, new[] { ParameterExpressionHelper.CreateParameterExpression(itType, string.Empty) }, resultType, expression, values); + } + + /// + /// Parses an expression into a Typed Expression. + /// + /// The `it`-Type. + /// The type of the result. + /// The Configuration for the parsing. + /// if set to true then also create a constructor for all the parameters. Note that this doesn't work for Linq-to-Database entities. + /// The expression. + /// An object array that contains zero or more objects which are used as replacement values. + /// The generated + [PublicAPI] + public static Expression> ParseLambda([CanBeNull] ParsingConfig parsingConfig, bool createParameterCtor, [NotNull] string expression, params object[] values) + { + Check.NotEmpty(expression, nameof(expression)); - return Expression.Lambda(parser.Parse(resultType, true)); + return (Expression>)ParseLambda(parsingConfig, createParameterCtor, new[] { ParameterExpressionHelper.CreateParameterExpression(typeof(T), string.Empty) }, typeof(TResult), expression, values); } /// /// Parses an expression into a LambdaExpression. (Also create a constructor for all the parameters. Note that this doesn't work for Linq-to-Database entities.) /// - /// The main type from the dynamic class expression. + /// The Configuration for the parsing. /// Type of the result. If not specified, it will be generated dynamically. /// The expression. /// An object array that contains zero or more objects which are used as replacement values. /// The generated [PublicAPI] - public static LambdaExpression ParseLambda([NotNull] Type itType, [CanBeNull] Type resultType, string expression, params object[] values) + public static LambdaExpression ParseLambda([CanBeNull] ParsingConfig parsingConfig, [CanBeNull] Type resultType, [NotNull] string expression, params object[] values) { - return ParseLambda(true, itType, resultType, expression, values); + return ParseLambda(parsingConfig, true, resultType, expression, values); } /// /// Parses an expression into a LambdaExpression. (Also create a constructor for all the parameters. Note that this doesn't work for Linq-to-Database entities.) /// - /// The main type from the dynamic class expression. /// Type of the result. If not specified, it will be generated dynamically. /// The expression. - /// The Configuration for the parsing. /// An object array that contains zero or more objects which are used as replacement values. /// The generated [PublicAPI] - public static LambdaExpression ParseLambda([CanBeNull] ParsingConfig parsingConfig, [NotNull] Type itType, [CanBeNull] Type resultType, string expression, params object[] values) + public static LambdaExpression ParseLambda([CanBeNull] Type resultType, [NotNull] string expression, params object[] values) { - return ParseLambda(true, itType, resultType, expression, parsingConfig, values); + Check.NotEmpty(expression, nameof(expression)); + + return ParseLambda(null, true, resultType, expression, values); } /// - /// Parses an expression into a LambdaExpression. + /// Parses an expression into a LambdaExpression. (Also create a constructor for all the parameters. Note that this doesn't work for Linq-to-Database entities.) /// - /// if set to true then also create a constructor for all the parameters. Note that this doesn't work for Linq-to-Database entities. /// The main type from the dynamic class expression. /// Type of the result. If not specified, it will be generated dynamically. /// The expression. /// An object array that contains zero or more objects which are used as replacement values. /// The generated [PublicAPI] - public static LambdaExpression ParseLambda(bool createParameterCtor, [NotNull] Type itType, [CanBeNull] Type resultType, string expression, params object[] values) + public static LambdaExpression ParseLambda([NotNull] Type itType, [CanBeNull] Type resultType, string expression, params object[] values) { - Check.NotNull(itType, nameof(itType)); - Check.NotEmpty(expression, nameof(expression)); + return ParseLambda(true, itType, resultType, expression, values); + } - return ParseLambda(createParameterCtor, new[] { Expression.Parameter(itType, "") }, resultType, expression, values); + /// + /// Parses an expression into a LambdaExpression. (Also create a constructor for all the parameters. Note that this doesn't work for Linq-to-Database entities.) + /// + /// The Configuration for the parsing. + /// The main type from the dynamic class expression. + /// Type of the result. If not specified, it will be generated dynamically. + /// The expression. + /// An object array that contains zero or more objects which are used as replacement values. + /// The generated + [PublicAPI] + public static LambdaExpression ParseLambda([CanBeNull] ParsingConfig parsingConfig, [NotNull] Type itType, [CanBeNull] Type resultType, string expression, params object[] values) + { + return ParseLambda(true, itType, resultType, expression, parsingConfig, values); } /// @@ -121,7 +194,7 @@ public static LambdaExpression ParseLambda([CanBeNull] ParsingConfig parsingConf Check.NotNull(itType, nameof(itType)); Check.NotEmpty(expression, nameof(expression)); - return ParseLambda(parsingConfig, createParameterCtor, new[] { Expression.Parameter(itType, "") }, resultType, expression, values); + return ParseLambda(parsingConfig, createParameterCtor, new[] { ParameterExpressionHelper.CreateParameterExpression(itType, string.Empty) }, resultType, expression, values); } /// @@ -167,27 +240,5 @@ public static LambdaExpression ParseLambda(bool createParameterCtor, [NotNull] P { return ParseLambda(null, createParameterCtor, parameters, resultType, expression, values); } - - /// - /// Parses an expression into a LambdaExpression. - /// - /// The Configuration for the parsing. - /// if set to true then also create a constructor for all the parameters. Note that this doesn't work for Linq-to-Database entities. - /// A array from ParameterExpressions. - /// Type of the result. If not specified, it will be generated dynamically. - /// The expression. - /// An object array that contains zero or more objects which are used as replacement values. - /// The generated - [PublicAPI] - public static LambdaExpression ParseLambda([CanBeNull] ParsingConfig parsingConfig, bool createParameterCtor, [NotNull] ParameterExpression[] parameters, [CanBeNull] Type resultType, [NotNull] string expression, params object[] values) - { - Check.NotNull(parameters, nameof(parameters)); - Check.Condition(parameters, p => p.Count(x => x == null) == 0, nameof(parameters)); - Check.NotEmpty(expression, nameof(expression)); - - var parser = new ExpressionParser(parameters, expression, values, parsingConfig); - - return Expression.Lambda(parser.Parse(resultType, createParameterCtor), parameters); - } } } diff --git a/src/System.Linq.Dynamic.Core/DynamicQueryableExtensions.cs b/src/System.Linq.Dynamic.Core/DynamicQueryableExtensions.cs index de1dbe0a..3e617426 100644 --- a/src/System.Linq.Dynamic.Core/DynamicQueryableExtensions.cs +++ b/src/System.Linq.Dynamic.Core/DynamicQueryableExtensions.cs @@ -65,7 +65,7 @@ public static object Aggregate([NotNull] this IQueryable source, [NotNull] strin // Properties PropertyInfo property = source.ElementType.GetProperty(member); - ParameterExpression parameter = Expression.Parameter(source.ElementType, "s"); + ParameterExpression parameter = ParameterExpressionHelper.CreateParameterExpression(source.ElementType, "s"); Expression selector = Expression.Lambda(Expression.MakeMemberAccess(parameter, property), parameter); // We've tried to find an expression of the type Expression>, // which is expressed as ( (TSource s) => s.Price ); @@ -140,6 +140,7 @@ public static bool Any([NotNull] this IQueryable source) /// /// /// true if the source sequence contains any elements; otherwise, false. + [PublicAPI] public static bool Any([NotNull] this IQueryable source, [CanBeNull] ParsingConfig config, [NotNull] string predicate, params object[] args) { Check.NotNull(source, nameof(source)); @@ -235,6 +236,7 @@ public static int Count([NotNull] this IQueryable source) /// /// /// The number of elements in the specified sequence that satisfies a condition. + [PublicAPI] public static int Count([NotNull] this IQueryable source, [CanBeNull] ParsingConfig config, [NotNull] string predicate, params object[] args) { Check.NotNull(source, nameof(source)); @@ -358,6 +360,7 @@ public static dynamic First([NotNull] this IQueryable source) /// A function to test each element for a condition. /// An object array that contains zero or more objects to insert into the predicate as parameters. Similar to the way String.Format formats strings. /// The first element in source that passes the test in predicate. + [PublicAPI] #if NET35 public static object First([NotNull] this IQueryable source, [NotNull] string predicate, params object[] args) #else @@ -416,6 +419,7 @@ public static dynamic FirstOrDefault([NotNull] this IQueryable source) /// A function to test each element for a condition. /// An object array that contains zero or more objects to insert into the predicate as parameters. Similar to the way String.Format formats strings. /// default if source is empty or if no element passes the test specified by predicate; otherwise, the first element in source that passes the test specified by predicate. + [PublicAPI] #if NET35 public static object FirstOrDefault([NotNull] this IQueryable source, [NotNull] string predicate, params object[] args) #else @@ -466,6 +470,7 @@ public static dynamic FirstOrDefault([NotNull] this IQueryable source, [NotNull] /// var groupResult2 = queryable.GroupBy("new (NumberPropertyAsKey, StringPropertyAsKey)", "new (StringProperty1, StringProperty2)"); /// /// + [PublicAPI] public static IQueryable GroupBy([NotNull] this IQueryable source, [NotNull] string keySelector, [NotNull] string resultSelector, object[] args) { Check.NotNull(source, nameof(source)); @@ -521,6 +526,7 @@ public static IQueryable GroupBy([NotNull] this IQueryable source, [NotNull] str /// var groupResult2 = queryable.GroupBy("new (NumberPropertyAsKey, StringPropertyAsKey)"); /// /// + [PublicAPI] public static IQueryable GroupBy([NotNull] this IQueryable source, [NotNull] string keySelector, [CanBeNull] params object[] args) { Check.NotNull(source, nameof(source)); @@ -628,8 +634,8 @@ public static IQueryable GroupJoin([NotNull] this IQueryable outer, [NotNull] IE ParameterExpression[] parameters = { - Expression.Parameter(outerType, "outer"), - Expression.Parameter(typeof(IEnumerable<>).MakeGenericType(innerType), "inner") + ParameterExpressionHelper.CreateParameterExpression(outerType, "outer"), + ParameterExpressionHelper.CreateParameterExpression(typeof(IEnumerable<>).MakeGenericType(innerType), "inner") }; LambdaExpression resultSelectorLambda = DynamicExpressionParser.ParseLambda(createParameterCtor, parameters, null, resultSelector, args); @@ -677,7 +683,8 @@ public static IQueryable Join([NotNull] this IQueryable outer, [NotNull] IEnumer ParameterExpression[] parameters = { - Expression.Parameter(outerType, "outer"), Expression.Parameter(innerType, "inner") + ParameterExpressionHelper.CreateParameterExpression(outerType, "outer"), + ParameterExpressionHelper.CreateParameterExpression(innerType, "inner") }; LambdaExpression resultSelectorLambda = DynamicExpressionParser.ParseLambda(createParameterCtor, parameters, null, resultSelector, args); @@ -873,7 +880,7 @@ public static IOrderedQueryable OrderBy([NotNull] this IQueryable source, [NotNu Check.NotNull(source, nameof(source)); Check.NotEmpty(ordering, nameof(ordering)); - ParameterExpression[] parameters = { Expression.Parameter(source.ElementType, "") }; + ParameterExpression[] parameters = { ParameterExpressionHelper.CreateParameterExpression(source.ElementType, string.Empty) }; ExpressionParser parser = new ExpressionParser(parameters, ordering, args, null); IList dynamicOrderings = parser.ParseOrdering(); @@ -1282,8 +1289,8 @@ public static IQueryable SelectMany([NotNull] this IQueryable source, [NotNull] sourceSelectLambda = Expression.Lambda(sourceLambdaDelegateType, sourceSelectLambda.Body, sourceSelectLambda.Parameters); //we have to create additional lambda for result selection - ParameterExpression xParameter = Expression.Parameter(source.ElementType, collectionParameterName); - ParameterExpression yParameter = Expression.Parameter(sourceLambdaResultType, resultParameterName); + ParameterExpression xParameter = ParameterExpressionHelper.CreateParameterExpression(source.ElementType, collectionParameterName); + ParameterExpression yParameter = ParameterExpressionHelper.CreateParameterExpression(sourceLambdaResultType, resultParameterName); LambdaExpression resultSelectLambda = DynamicExpressionParser.ParseLambda(createParameterCtor, new[] { xParameter, yParameter }, null, resultSelector, resultSelectorArgs); Type resultLambdaResultType = resultSelectLambda.Body.Type; @@ -1577,7 +1584,7 @@ public static IOrderedQueryable ThenBy([NotNull] this IOrderedQueryable source, Check.NotNull(source, nameof(source)); Check.NotEmpty(ordering, nameof(ordering)); - ParameterExpression[] parameters = { Expression.Parameter(source.ElementType, "") }; + ParameterExpression[] parameters = { ParameterExpressionHelper.CreateParameterExpression(source.ElementType, string.Empty) }; ExpressionParser parser = new ExpressionParser(parameters, ordering, args, null); IList dynamicOrderings = parser.ParseOrdering(forceThenBy: true); diff --git a/src/System.Linq.Dynamic.Core/ParameterExpressionHelper.cs b/src/System.Linq.Dynamic.Core/ParameterExpressionHelper.cs new file mode 100644 index 00000000..e8de22ba --- /dev/null +++ b/src/System.Linq.Dynamic.Core/ParameterExpressionHelper.cs @@ -0,0 +1,23 @@ +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq.Expressions; + +namespace System.Linq.Dynamic.Core +{ + internal static class ParameterExpressionHelper + { + private static readonly IDictionary ParameterExpressions = new ConcurrentDictionary(); + + public static ParameterExpression CreateParameterExpression(Type type, string name) + { + string key = type.FullName + "_" + name; + + if (!ParameterExpressions.ContainsKey(key)) + { + ParameterExpressions.Add(key, Expression.Parameter(type, name)); + } + + return ParameterExpressions[key]; + } + } +} diff --git a/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs b/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs index 0606324b..9069630e 100644 --- a/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs +++ b/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs @@ -22,7 +22,6 @@ internal class ExpressionParser private readonly ParsingConfig _parsingConfig; private readonly KeywordsHelper _keywordsHelper; - private readonly PredefinedTypesHelper _predefinedTypesHelper; private readonly TextParser _textParser; private readonly Dictionary _internals; private readonly Dictionary _symbols; @@ -53,39 +52,10 @@ public ExpressionParser([CanBeNull] ParameterExpression[] parameters, [NotNull] _parsingConfig = parsingConfig ?? ParsingConfig.Default; - _predefinedTypesHelper = new PredefinedTypesHelper(_parsingConfig); - InitPredefinedTypes(); - - _keywordsHelper = new KeywordsHelper(_parsingConfig, _predefinedTypesHelper); + _keywordsHelper = new KeywordsHelper(_parsingConfig); _textParser = new TextParser(expression); } - void InitPredefinedTypes() - { -#if !(NET35 || SILVERLIGHT || NETFX_CORE || WINDOWS_APP || DOTNET5_1 || UAP10_0 || NETSTANDARD) - //System.Data.Entity is always here, so overwrite short name of it with EntityFramework if EntityFramework is found. - //EF5(or 4.x??), System.Data.Objects.DataClasses.EdmFunctionAttribute - //There is also an System.Data.Entity, Version=3.5.0.0, but no Functions. - _predefinedTypesHelper.TryAdd("System.Data.Objects.EntityFunctions, System.Data.Entity, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", 1); - _predefinedTypesHelper.TryAdd("System.Data.Objects.SqlClient.SqlFunctions, System.Data.Entity, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", 1); - _predefinedTypesHelper.TryAdd("System.Data.Objects.SqlClient.SqlSpatialFunctions, System.Data.Entity, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", 1); - - //EF6,System.Data.Entity.DbFunctionAttribute - _predefinedTypesHelper.TryAdd("System.Data.Entity.Core.Objects.EntityFunctions, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", 2); - _predefinedTypesHelper.TryAdd("System.Data.Entity.DbFunctions, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", 2); - _predefinedTypesHelper.TryAdd("System.Data.Entity.Spatial.DbGeography, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", 2); - _predefinedTypesHelper.TryAdd("System.Data.Entity.SqlServer.SqlFunctions, EntityFramework.SqlServer, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", 2); - _predefinedTypesHelper.TryAdd("System.Data.Entity.SqlServer.SqlSpatialFunctions, EntityFramework.SqlServer, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", 2); -#endif - -#if NETSTANDARD2_0 - _predefinedTypesHelper.TryAdd("Microsoft.EntityFrameworkCore.DynamicLinq.DynamicFunctions, Microsoft.EntityFrameworkCore.DynamicLinq, Version=1.0.0.0, Culture=neutral, PublicKeyToken=974e7e1b462f3693", 3); -#endif - // detect NumberDecimalSeparator from current culture - //NumberFormatInfo numberFormatInfo = CultureInfo.CurrentCulture.NumberFormat; - //NumberDecimalSeparatorFromCulture = numberFormatInfo.NumberDecimalSeparator[0]; - } - void ProcessParameters(ParameterExpression[] parameters) { foreach (ParameterExpression pe in parameters.Where(p => !string.IsNullOrEmpty(p.Name))) @@ -1406,7 +1376,7 @@ Expression ParseMemberAccess(Type type, Expression instance) case 1: MethodInfo method = (MethodInfo)mb; - if (!_predefinedTypesHelper.IsPredefinedType(method.DeclaringType) && !(method.IsPublic && _predefinedTypesHelper.IsPredefinedType(method.ReturnType))) + if (!PredefinedTypesHelper.IsPredefinedType(_parsingConfig, method.DeclaringType) && !(method.IsPublic && PredefinedTypesHelper.IsPredefinedType(_parsingConfig, method.ReturnType))) { throw ParseError(errorPos, Res.MethodsAreInaccessible, TypeHelper.GetTypeName(method.DeclaringType)); } @@ -1514,7 +1484,7 @@ Expression ParseAggregate(Expression instance, Type elementType, string methodNa var oldParent = _parent; ParameterExpression outerIt = _it; - ParameterExpression innerIt = Expression.Parameter(elementType, ""); + ParameterExpression innerIt = ParameterExpressionHelper.CreateParameterExpression(elementType, string.Empty); _parent = _it; diff --git a/src/System.Linq.Dynamic.Core/Parser/KeywordsHelper.cs b/src/System.Linq.Dynamic.Core/Parser/KeywordsHelper.cs index 40214036..bad40994 100644 --- a/src/System.Linq.Dynamic.Core/Parser/KeywordsHelper.cs +++ b/src/System.Linq.Dynamic.Core/Parser/KeywordsHelper.cs @@ -22,7 +22,7 @@ internal class KeywordsHelper { "null", Constants.NullLiteral } }; - public KeywordsHelper(ParsingConfig config, PredefinedTypesHelper predefinedTypesHelper) + public KeywordsHelper(ParsingConfig config) { if (config.AreContextKeywordsEnabled) { @@ -38,13 +38,13 @@ public KeywordsHelper(ParsingConfig config, PredefinedTypesHelper predefinedType _keywords.Add(KEYWORD_NEW, KEYWORD_NEW); _keywords.Add(KEYWORD_ISNULL, KEYWORD_ISNULL); - foreach (Type type in predefinedTypesHelper.PredefinedTypes.OrderBy(kvp => kvp.Value).Select(kvp => kvp.Key)) + foreach (Type type in PredefinedTypesHelper.PredefinedTypes.OrderBy(kvp => kvp.Value).Select(kvp => kvp.Key)) { _keywords[type.FullName] = type; _keywords[type.Name] = type; } - foreach (KeyValuePair pair in predefinedTypesHelper.PredefinedTypesShorthands) + foreach (KeyValuePair pair in PredefinedTypesHelper.PredefinedTypesShorthands) { _keywords.Add(pair.Key, pair.Value); } diff --git a/src/System.Linq.Dynamic.Core/Parser/PredefinedTypesHelper.cs b/src/System.Linq.Dynamic.Core/Parser/PredefinedTypesHelper.cs index ffa736ec..92a92901 100644 --- a/src/System.Linq.Dynamic.Core/Parser/PredefinedTypesHelper.cs +++ b/src/System.Linq.Dynamic.Core/Parser/PredefinedTypesHelper.cs @@ -1,14 +1,13 @@ using System.Collections.Concurrent; using System.Collections.Generic; +using System.Linq.Dynamic.Core.Validation; namespace System.Linq.Dynamic.Core.Parser { - internal class PredefinedTypesHelper + internal static class PredefinedTypesHelper { - private readonly ParsingConfig _config; - - // These shorthands have different name than actual type and therefore not recognized by default from the _predefinedTypes - public readonly IDictionary PredefinedTypesShorthands = new Dictionary + // These shorthands have different name than actual type and therefore not recognized by default from the PredefinedTypes. + public static readonly IDictionary PredefinedTypesShorthands = new Dictionary { { "int", typeof(int) }, { "uint", typeof(uint) }, @@ -17,10 +16,10 @@ internal class PredefinedTypesHelper { "long", typeof(long) }, { "ulong", typeof(ulong) }, { "bool", typeof(bool) }, - { "float", typeof(float) }, + { "float", typeof(float) } }; - public readonly IDictionary PredefinedTypes = new ConcurrentDictionary(new Dictionary { + public static readonly IDictionary PredefinedTypes = new ConcurrentDictionary(new Dictionary { { typeof(object), 0 }, { typeof(bool), 0 }, { typeof(char), 0 }, @@ -45,12 +44,26 @@ internal class PredefinedTypesHelper { typeof(Uri), 0 } }); - public PredefinedTypesHelper(ParsingConfig config) + static PredefinedTypesHelper() { - _config = config; + //System.Data.Entity is always here, so overwrite short name of it with EntityFramework if EntityFramework is found. + //EF5(or 4.x??), System.Data.Objects.DataClasses.EdmFunctionAttribute + //There is also an System.Data.Entity, Version=3.5.0.0, but no Functions. + TryAdd("System.Data.Objects.EntityFunctions, System.Data.Entity, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", 1); + TryAdd("System.Data.Objects.SqlClient.SqlFunctions, System.Data.Entity, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", 1); + TryAdd("System.Data.Objects.SqlClient.SqlSpatialFunctions, System.Data.Entity, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", 1); + + //EF6,System.Data.Entity.DbFunctionAttribute + TryAdd("System.Data.Entity.Core.Objects.EntityFunctions, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", 2); + TryAdd("System.Data.Entity.DbFunctions, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", 2); + TryAdd("System.Data.Entity.Spatial.DbGeography, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", 2); + TryAdd("System.Data.Entity.SqlServer.SqlFunctions, EntityFramework.SqlServer, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", 2); + TryAdd("System.Data.Entity.SqlServer.SqlSpatialFunctions, EntityFramework.SqlServer, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", 2); + + TryAdd("Microsoft.EntityFrameworkCore.DynamicLinq.DynamicFunctions, Microsoft.EntityFrameworkCore.DynamicLinq, Version=1.0.0.0, Culture=neutral, PublicKeyToken=974e7e1b462f3693", 3); } - public void TryAdd(string typeName, int x) + private static void TryAdd(string typeName, int x) { try { @@ -66,19 +79,17 @@ public void TryAdd(string typeName, int x) } } - public bool IsPredefinedType(Type type) + public static bool IsPredefinedType(ParsingConfig config, Type type) { - if (PredefinedTypes.ContainsKey(type)) - { - return true; - } + Check.NotNull(config, nameof(config)); + Check.NotNull(type, nameof(type)); - if (_config.CustomTypeProvider != null && _config.CustomTypeProvider.GetCustomTypes().Contains(type)) + if (PredefinedTypes.ContainsKey(type)) { return true; } - return false; + return config.CustomTypeProvider != null && config.CustomTypeProvider.GetCustomTypes().Contains(type); } } }