From 5bef69e1fad7b26a2988323f25ed453e19c837d9 Mon Sep 17 00:00:00 2001 From: Nick Darvey Date: Tue, 9 Jan 2018 15:15:00 +1100 Subject: [PATCH 1/5] Adds support for dynamic objects in query. If it can't get the property or field, try getting the member by a dynamic operation or by an indexer operation. --- .../DynamicGetMemberBinder.cs | 32 +++++++++++++++++++ .../Parser/ExpressionParser.cs | 29 ++++++++--------- .../ExpressionTests.cs | 5 +-- 3 files changed, 49 insertions(+), 17 deletions(-) create mode 100644 src/System.Linq.Dynamic.Core/DynamicGetMemberBinder.cs diff --git a/src/System.Linq.Dynamic.Core/DynamicGetMemberBinder.cs b/src/System.Linq.Dynamic.Core/DynamicGetMemberBinder.cs new file mode 100644 index 00000000..c2f31131 --- /dev/null +++ b/src/System.Linq.Dynamic.Core/DynamicGetMemberBinder.cs @@ -0,0 +1,32 @@ +// From SqlLinq by dkackman +// https://github.com/dkackman/SqlLinq/blob/210b594e37f14061424397368ed750ce547c21e7/License.md + +using System.Collections.Generic; +using System.Dynamic; +using System.Linq.Expressions; +using System.Reflection; + +namespace System.Linq.Dynamic.Core +{ + internal class DynamicGetMemberBinder : GetMemberBinder + { + private readonly static PropertyInfo _indexer = typeof(IDictionary).GetProperty("Item"); + + public DynamicGetMemberBinder(string name) + : base(name, true) + { + + } + + public override DynamicMetaObject FallbackGetMember(DynamicMetaObject target, DynamicMetaObject errorSuggestion) + { + var d = target.Value as IDictionary; + if (d == null) + { + throw new InvalidOperationException("Target object is not an ExpandoObject"); + } + + return DynamicMetaObject.Create(d, Expression.MakeIndex(Expression.Constant(d), _indexer, new Expression[] { Expression.Constant(this.Name) })); + } + } +} diff --git a/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs b/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs index 86db98d6..a1bbdfe8 100644 --- a/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs +++ b/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs @@ -1409,27 +1409,25 @@ Expression ParseMemberAccess(Type type, Expression instance) } #endif MemberInfo member = FindPropertyOrField(type, id, instance == null); - if (member == null) - { - if (_textParser.CurrentToken.Id == TokenId.Lambda && _it.Type == type) - { - // This might be an internal variable for use within a lambda expression, so store it as such - _internals.Add(id, _it); - _textParser.NextToken(); + if (member is PropertyInfo property) return Expression.Property(instance, property); + if (member is FieldInfo field) return Expression.Field(instance, field); - return ParseConditionalOperator(); - } + if (type == typeof(object)) return Expression.Dynamic(new DynamicGetMemberBinder(id), type, instance); - throw ParseError(errorPos, Res.UnknownPropertyOrField, id, TypeHelper.GetTypeName(type)); - } + MethodInfo indexerMethod = instance.Type.GetMethod("get_Item", new Type[] { typeof(string) }); + if (indexerMethod != null) return Expression.Call(instance, indexerMethod, Expression.Constant(id)); - var property = member as PropertyInfo; - if (property != null) + + if (_textParser.CurrentToken.Id == TokenId.Lambda && _it.Type == type) { - return Expression.Property(instance, property); + // This might be an internal variable for use within a lambda expression, so store it as such + _internals.Add(id, _it); + _textParser.NextToken(); + + return ParseConditionalOperator(); } - return Expression.Field(instance, (FieldInfo)member); + throw ParseError(errorPos, Res.UnknownPropertyOrField, id, TypeHelper.GetTypeName(type)); } Type FindType(string name) @@ -1758,4 +1756,5 @@ static Exception ParseError(int pos, string format, params object[] args) return new ParseException(string.Format(CultureInfo.CurrentCulture, format, args), pos); } } + } diff --git a/test/System.Linq.Dynamic.Core.Tests/ExpressionTests.cs b/test/System.Linq.Dynamic.Core.Tests/ExpressionTests.cs index 4bc7bf0a..fe438e0a 100644 --- a/test/System.Linq.Dynamic.Core.Tests/ExpressionTests.cs +++ b/test/System.Linq.Dynamic.Core.Tests/ExpressionTests.cs @@ -1213,7 +1213,8 @@ public void ExpressionTests_Select_DynamicObjects() Assert.Equal(new[] { 100, 200 }, result.ToDynamicArray()); } - //[Fact] + [Fact] + [Trait("Issue", "136")] public void ExpressionTests_Select_ExpandoObjects() { //Arrange @@ -1222,7 +1223,7 @@ public void ExpressionTests_Select_ExpandoObjects() a.BlogId = 100; dynamic b = new ExpandoObject(); b.Name = "b"; - b.BlogId = 100; + b.BlogId = 200; var list = new List { a, b }; IQueryable qry = list.AsQueryable(); From a34308d707d723a8ca16a792dcd97f743460c348 Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Tue, 9 Jan 2018 08:11:12 +0100 Subject: [PATCH 2/5] Fix build and some code-reformat --- appveyor.yml | 2 +- src-console/ConsoleAppEF1.1/Program.cs | 2 ++ src-console/ConsoleAppEF2.0/Program.cs | 26 ++++++++++++++++- .../DynamicGetMemberBinder.cs | 18 ++++++------ .../Parser/ExpressionParser.cs | 28 +++++++++++++++---- 5 files changed, 61 insertions(+), 15 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 1888383f..99a6b7a0 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,6 +1,6 @@ os: Visual Studio 2017 -version: 1.0.7.{build} +version: 1.0.8.{build} configuration: - Debug diff --git a/src-console/ConsoleAppEF1.1/Program.cs b/src-console/ConsoleAppEF1.1/Program.cs index dee34406..ec23306f 100644 --- a/src-console/ConsoleAppEF1.1/Program.cs +++ b/src-console/ConsoleAppEF1.1/Program.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using System.Dynamic; +using System.Linq; using System.Linq.Dynamic.Core; using Newtonsoft.Json; diff --git a/src-console/ConsoleAppEF2.0/Program.cs b/src-console/ConsoleAppEF2.0/Program.cs index 4f084fa4..c34921c6 100644 --- a/src-console/ConsoleAppEF2.0/Program.cs +++ b/src-console/ConsoleAppEF2.0/Program.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Dynamic; using System.Linq; using System.Linq.Dynamic.Core; using System.Linq.Dynamic.Core.CustomTypeProviders; @@ -25,9 +26,32 @@ public HashSet GetCustomTypes() } } + private static IEnumerable GetEnumerable() + { + var random = new Random((int)DateTime.Now.Ticks); + + return Enumerable.Range(0, 10).Select(i => new + { + Id = i, + Value = random.Next(), + }); + } + static void Main(string[] args) { - var all = new + IQueryable qry = GetEnumerable().AsQueryable(); + + var result = qry.Select("it").OrderBy("Value"); + try + { + Console.WriteLine("result {0}", JsonConvert.SerializeObject(result, Formatting.Indented)); + } + catch (Exception e) + { + Console.WriteLine(e); + } + + var all = new { test1 = new List { 1, 2, 3 }.ToDynamicList(typeof(int)), test2 = new List { 4, 5, 6 }.ToDynamicList(typeof(int)), diff --git a/src/System.Linq.Dynamic.Core/DynamicGetMemberBinder.cs b/src/System.Linq.Dynamic.Core/DynamicGetMemberBinder.cs index c2f31131..0054146a 100644 --- a/src/System.Linq.Dynamic.Core/DynamicGetMemberBinder.cs +++ b/src/System.Linq.Dynamic.Core/DynamicGetMemberBinder.cs @@ -1,6 +1,4 @@ -// From SqlLinq by dkackman -// https://github.com/dkackman/SqlLinq/blob/210b594e37f14061424397368ed750ce547c21e7/License.md - +#if !NET35 using System.Collections.Generic; using System.Dynamic; using System.Linq.Expressions; @@ -8,25 +6,29 @@ namespace System.Linq.Dynamic.Core { + /// + /// Based on From SqlLinq by dkackman. https://github.com/dkackman/SqlLinq/blob/210b594e37f14061424397368ed750ce547c21e7/License.md + /// + /// internal class DynamicGetMemberBinder : GetMemberBinder { - private readonly static PropertyInfo _indexer = typeof(IDictionary).GetProperty("Item"); + private static readonly PropertyInfo Indexer = typeof(IDictionary).GetProperty("Item"); public DynamicGetMemberBinder(string name) : base(name, true) { - } public override DynamicMetaObject FallbackGetMember(DynamicMetaObject target, DynamicMetaObject errorSuggestion) { - var d = target.Value as IDictionary; - if (d == null) + IDictionary dictionary = target.Value as IDictionary; + if (dictionary == null) { throw new InvalidOperationException("Target object is not an ExpandoObject"); } - return DynamicMetaObject.Create(d, Expression.MakeIndex(Expression.Constant(d), _indexer, new Expression[] { Expression.Constant(this.Name) })); + return DynamicMetaObject.Create(dictionary, Expression.MakeIndex(Expression.Constant(dictionary), Indexer, new Expression[] { Expression.Constant(Name) })); } } } +#endif diff --git a/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs b/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs index a1bbdfe8..9ccd357d 100644 --- a/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs +++ b/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs @@ -1383,10 +1383,14 @@ Expression ParseMemberAccess(Type type, Expression instance) case 1: MethodInfo method = (MethodInfo)mb; if (!_predefinedTypesHelper.IsPredefinedType(method.DeclaringType) && !(method.IsPublic && _predefinedTypesHelper.IsPredefinedType(method.ReturnType))) + { throw ParseError(errorPos, Res.MethodsAreInaccessible, TypeHelper.GetTypeName(method.DeclaringType)); + } if (method.ReturnType == typeof(void)) + { throw ParseError(errorPos, Res.MethodIsVoid, id, TypeHelper.GetTypeName(method.DeclaringType)); + } return Expression.Call(instance, method, args); @@ -1409,14 +1413,28 @@ Expression ParseMemberAccess(Type type, Expression instance) } #endif MemberInfo member = FindPropertyOrField(type, id, instance == null); - if (member is PropertyInfo property) return Expression.Property(instance, property); - if (member is FieldInfo field) return Expression.Field(instance, field); + if (member is PropertyInfo property) + { + return Expression.Property(instance, property); + } - if (type == typeof(object)) return Expression.Dynamic(new DynamicGetMemberBinder(id), type, instance); + if (member is FieldInfo field) + { + return Expression.Field(instance, field); + } - MethodInfo indexerMethod = instance.Type.GetMethod("get_Item", new Type[] { typeof(string) }); - if (indexerMethod != null) return Expression.Call(instance, indexerMethod, Expression.Constant(id)); +#if !NET35 && !UAP10_0 && !NETSTANDARD1_3 + if (type == typeof(object)) + { + return Expression.Dynamic(new DynamicGetMemberBinder(id), type, instance); + } +#endif + MethodInfo indexerMethod = instance.Type.GetMethod("get_Item", new[] { typeof(string) }); + if (indexerMethod != null) + { + return Expression.Call(instance, indexerMethod, Expression.Constant(id)); + } if (_textParser.CurrentToken.Id == TokenId.Lambda && _it.Type == type) { From 80a1eaa5cc00be3129d8c689edc5e1b22a11ffc7 Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Tue, 9 Jan 2018 10:43:02 +0100 Subject: [PATCH 3/5] Exclude failing test --- test/System.Linq.Dynamic.Core.Tests/ExpressionTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/System.Linq.Dynamic.Core.Tests/ExpressionTests.cs b/test/System.Linq.Dynamic.Core.Tests/ExpressionTests.cs index fe438e0a..5f4b32f8 100644 --- a/test/System.Linq.Dynamic.Core.Tests/ExpressionTests.cs +++ b/test/System.Linq.Dynamic.Core.Tests/ExpressionTests.cs @@ -1213,8 +1213,8 @@ public void ExpressionTests_Select_DynamicObjects() Assert.Equal(new[] { 100, 200 }, result.ToDynamicArray()); } - [Fact] - [Trait("Issue", "136")] + //[Fact] + //[Trait("Issue", "136")] public void ExpressionTests_Select_ExpandoObjects() { //Arrange From 9d719dcc9bce1ec37930d0269a2beb43249846d7 Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Tue, 9 Jan 2018 18:02:24 +0100 Subject: [PATCH 4/5] Refactored DynamicClass.cs --- src/System.Linq.Dynamic.Core/DynamicClass.cs | 198 +++--------------- .../DynamicClass.net35.cs | 58 +++++ .../DynamicClass.uap.cs | 109 ++++++++++ .../Parser/ExpressionParser.cs | 39 ++-- .../ExpressionTests.cs | 12 +- 5 files changed, 218 insertions(+), 198 deletions(-) create mode 100644 src/System.Linq.Dynamic.Core/DynamicClass.net35.cs create mode 100644 src/System.Linq.Dynamic.Core/DynamicClass.uap.cs diff --git a/src/System.Linq.Dynamic.Core/DynamicClass.cs b/src/System.Linq.Dynamic.Core/DynamicClass.cs index 0e5ebf6a..63c231b7 100644 --- a/src/System.Linq.Dynamic.Core/DynamicClass.cs +++ b/src/System.Linq.Dynamic.Core/DynamicClass.cs @@ -1,164 +1,7 @@ -using System.Collections; +#if !UAP10_0 && !NET35 using System.Collections.Generic; -using System.Reflection; - -#if UAP10_0 -using System.Dynamic; - -namespace System.Linq.Dynamic.Core -{ - /// - /// Provides a base class for dynamic objects for UAP10_0. - /// - public class DynamicClass : DynamicObject - { - readonly Dictionary _properties = new Dictionary(); - - /// - /// Initializes a new instance of the class. - /// - /// The propertylist. - public DynamicClass(params KeyValuePair[] propertylist) - { - foreach (var kvp in propertylist) - { - _properties.Add(kvp.Key, kvp.Value); - } - } - - /// - /// Gets or sets the with the specified name. - /// - /// - /// The . - /// - /// The name. - /// Value from the property. - public object this[string name] - { - get - { - object result; - if (_properties.TryGetValue(name, out result)) - return result; - - return null; - } - set - { - if (_properties.ContainsKey(name)) - _properties[name] = value; - else - _properties.Add(name, value); - } - } - - /// - /// Returns the enumeration of all dynamic member names. - /// - /// - /// A sequence that contains dynamic member names. - /// - public override IEnumerable GetDynamicMemberNames() - { - return _properties.Keys; - } - - /// - /// Provides the implementation for operations that get member values. Classes derived from the class can override this method to specify dynamic behavior for operations such as getting a value for a property. - /// - /// Provides information about the object that called the dynamic operation. The binder.Name property provides the name of the member on which the dynamic operation is performed. For example, for the Console.WriteLine(sampleObject.SampleProperty) statement, where sampleObject is an instance of the class derived from the class, binder.Name returns "SampleProperty". The binder.IgnoreCase property specifies whether the member name is case-sensitive. - /// The result of the get operation. For example, if the method is called for a property, you can assign the property value to . - /// - /// true if the operation is successful; otherwise, false. If this method returns false, the run-time binder of the language determines the behavior. (In most cases, a run-time exception is thrown.) - /// - public override bool TryGetMember(GetMemberBinder binder, out object result) - { - var name = binder.Name; - _properties.TryGetValue(name, out result); - - return true; - } - - /// - /// Provides the implementation for operations that set member values. Classes derived from the class can override this method to specify dynamic behavior for operations such as setting a value for a property. - /// - /// Provides information about the object that called the dynamic operation. The binder.Name property provides the name of the member to which the value is being assigned. For example, for the statement sampleObject.SampleProperty = "Test", where sampleObject is an instance of the class derived from the class, binder.Name returns "SampleProperty". The binder.IgnoreCase property specifies whether the member name is case-sensitive. - /// The value to set to the member. For example, for sampleObject.SampleProperty = "Test", where sampleObject is an instance of the class derived from the class, the is "Test". - /// - /// true if the operation is successful; otherwise, false. If this method returns false, the run-time binder of the language determines the behavior. (In most cases, a language-specific run-time exception is thrown.) - /// - public override bool TrySetMember(SetMemberBinder binder, object value) - { - var name = binder.Name; - if (_properties.ContainsKey(name)) - _properties[name] = value; - else - _properties.Add(name, value); - - return true; - } - } -} -# elif NET35 -namespace System.Linq.Dynamic.Core -{ - /// - /// Provides a base class for dynamic objects for Net 3.5 - /// - public abstract class DynamicClass - { - /// - /// Gets the dynamic property by name. - /// - /// The type. - /// Name of the property. - /// T - public T GetDynamicPropertyValue(string propertyName) - { - var type = GetType(); - var propInfo = type.GetProperty(propertyName); - - return (T)propInfo.GetValue(this, null); - } - - /// - /// Gets the dynamic property value by name. - /// - /// Name of the property. - /// value - public object GetDynamicPropertyValue(string propertyName) - { - return GetDynamicPropertyValue(propertyName); - } - - /// - /// Sets the dynamic property value by name. - /// - /// The type. - /// Name of the property. - /// The value. - public void SetDynamicPropertyValue(string propertyName, T value) - { - var type = GetType(); - var propInfo = type.GetProperty(propertyName); - - propInfo.SetValue(this, value, null); - } - - /// - /// Sets the dynamic property value by name. - /// - /// Name of the property. - /// The value. - public void SetDynamicPropertyValue(string propertyName, object value) - { - SetDynamicPropertyValue(propertyName, value); - } - } -} -#else using System.Dynamic; +using System.Reflection; namespace System.Linq.Dynamic.Core { @@ -177,7 +20,7 @@ public abstract class DynamicClass : DynamicObject { private Dictionary _propertiesDictionary; - private Dictionary _properties + private Dictionary Properties { get { @@ -253,28 +96,31 @@ public void SetDynamicPropertyValue(string propertyName, object value) /// /// Gets or sets the with the specified name. /// - /// - /// The . - /// + /// The . /// The name. /// Value from the property. public object this[string name] { get { - object result; - if (_properties.TryGetValue(name, out result)) + if (Properties.TryGetValue(name, out object result)) + { return result; + } return null; } set { - if (_properties.ContainsKey(name)) - _properties[name] = value; + if (Properties.ContainsKey(name)) + { + Properties[name] = value; + } else - _properties.Add(name, value); + { + Properties.Add(name, value); + } } } @@ -286,7 +132,7 @@ public object this[string name] /// public override IEnumerable GetDynamicMemberNames() { - return _properties.Keys; + return Properties.Keys; } /// @@ -300,7 +146,7 @@ public override IEnumerable GetDynamicMemberNames() public override bool TryGetMember(GetMemberBinder binder, out object result) { string name = binder.Name; - _properties.TryGetValue(name, out result); + Properties.TryGetValue(name, out result); return true; } @@ -316,13 +162,17 @@ public override bool TryGetMember(GetMemberBinder binder, out object result) public override bool TrySetMember(SetMemberBinder binder, object value) { string name = binder.Name; - if (_properties.ContainsKey(name)) - _properties[name] = value; + if (Properties.ContainsKey(name)) + { + Properties[name] = value; + } else - _properties.Add(name, value); + { + Properties.Add(name, value); + } return true; } } } -#endif \ No newline at end of file +#endif diff --git a/src/System.Linq.Dynamic.Core/DynamicClass.net35.cs b/src/System.Linq.Dynamic.Core/DynamicClass.net35.cs new file mode 100644 index 00000000..c8fc91ee --- /dev/null +++ b/src/System.Linq.Dynamic.Core/DynamicClass.net35.cs @@ -0,0 +1,58 @@ +#if NET35 +namespace System.Linq.Dynamic.Core +{ + /// + /// Provides a base class for dynamic objects for Net 3.5 + /// + public abstract class DynamicClass + { + /// + /// Gets the dynamic property by name. + /// + /// The type. + /// Name of the property. + /// T + public T GetDynamicPropertyValue(string propertyName) + { + var type = GetType(); + var propInfo = type.GetProperty(propertyName); + + return (T)propInfo.GetValue(this, null); + } + + /// + /// Gets the dynamic property value by name. + /// + /// Name of the property. + /// value + public object GetDynamicPropertyValue(string propertyName) + { + return GetDynamicPropertyValue(propertyName); + } + + /// + /// Sets the dynamic property value by name. + /// + /// The type. + /// Name of the property. + /// The value. + public void SetDynamicPropertyValue(string propertyName, T value) + { + var type = GetType(); + var propInfo = type.GetProperty(propertyName); + + propInfo.SetValue(this, value, null); + } + + /// + /// Sets the dynamic property value by name. + /// + /// Name of the property. + /// The value. + public void SetDynamicPropertyValue(string propertyName, object value) + { + SetDynamicPropertyValue(propertyName, value); + } + } +} +#endif diff --git a/src/System.Linq.Dynamic.Core/DynamicClass.uap.cs b/src/System.Linq.Dynamic.Core/DynamicClass.uap.cs new file mode 100644 index 00000000..102d9d5d --- /dev/null +++ b/src/System.Linq.Dynamic.Core/DynamicClass.uap.cs @@ -0,0 +1,109 @@ +#if UAP10_0 +using System.Collections.Generic; +using System.Dynamic; + +namespace System.Linq.Dynamic.Core +{ + /// + /// Provides a base class for dynamic objects for UAP10_0. + /// + public class DynamicClass : DynamicObject + { + private readonly Dictionary _properties = new Dictionary(); + + /// + /// Initializes a new instance of the class. + /// + /// The propertylist. + public DynamicClass(params KeyValuePair[] propertylist) + { + foreach (var kvp in propertylist) + { + _properties.Add(kvp.Key, kvp.Value); + } + } + + /// + /// Gets or sets the with the specified name. + /// + /// + /// The . + /// + /// The name. + /// Value from the property. + public object this[string name] + { + get + { + if (_properties.TryGetValue(name, out object result)) + { + return result; + } + + return null; + } + set + { + if (_properties.ContainsKey(name)) + { + _properties[name] = value; + } + else + { + _properties.Add(name, value); + } + } + } + + /// + /// Returns the enumeration of all dynamic member names. + /// + /// + /// A sequence that contains dynamic member names. + /// + public override IEnumerable GetDynamicMemberNames() + { + return _properties.Keys; + } + + /// + /// Provides the implementation for operations that get member values. Classes derived from the class can override this method to specify dynamic behavior for operations such as getting a value for a property. + /// + /// Provides information about the object that called the dynamic operation. The binder.Name property provides the name of the member on which the dynamic operation is performed. For example, for the Console.WriteLine(sampleObject.SampleProperty) statement, where sampleObject is an instance of the class derived from the class, binder.Name returns "SampleProperty". The binder.IgnoreCase property specifies whether the member name is case-sensitive. + /// The result of the get operation. For example, if the method is called for a property, you can assign the property value to . + /// + /// true if the operation is successful; otherwise, false. If this method returns false, the run-time binder of the language determines the behavior. (In most cases, a run-time exception is thrown.) + /// + public override bool TryGetMember(GetMemberBinder binder, out object result) + { + var name = binder.Name; + _properties.TryGetValue(name, out result); + + return true; + } + + /// + /// Provides the implementation for operations that set member values. Classes derived from the class can override this method to specify dynamic behavior for operations such as setting a value for a property. + /// + /// Provides information about the object that called the dynamic operation. The binder.Name property provides the name of the member to which the value is being assigned. For example, for the statement sampleObject.SampleProperty = "Test", where sampleObject is an instance of the class derived from the class, binder.Name returns "SampleProperty". The binder.IgnoreCase property specifies whether the member name is case-sensitive. + /// The value to set to the member. For example, for sampleObject.SampleProperty = "Test", where sampleObject is an instance of the class derived from the class, the is "Test". + /// + /// true if the operation is successful; otherwise, false. If this method returns false, the run-time binder of the language determines the behavior. (In most cases, a language-specific run-time exception is thrown.) + /// + public override bool TrySetMember(SetMemberBinder binder, object value) + { + string name = binder.Name; + if (_properties.ContainsKey(name)) + { + _properties[name] = value; + } + else + { + _properties.Add(name, value); + } + + return true; + } + } +} +#endif diff --git a/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs b/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs index 9ccd357d..b20a96e2 100644 --- a/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs +++ b/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs @@ -1174,33 +1174,33 @@ private Expression CreateNewExpression(List properties, List); + type = typeof(DynamicClass); + Type typeForKeyValuePair = typeof(KeyValuePair); #if NET35 || NET40 ConstructorInfo constructorForKeyValuePair = typeForKeyValuePair.GetConstructors().First(); #else - ConstructorInfo constructorForKeyValuePair = typeForKeyValuePair.GetTypeInfo().DeclaredConstructors.First(); + ConstructorInfo constructorForKeyValuePair = typeForKeyValuePair.GetTypeInfo().DeclaredConstructors.First(); #endif - var arrayIndexParams = new List(); - for (int i = 0; i < expressions.Count; i++) - { - // Just convert the expression always to an object expression. - UnaryExpression boxingExpression = Expression.Convert(expressions[i], typeof(object)); - NewExpression parameter = Expression.New(constructorForKeyValuePair, (Expression)Expression.Constant(properties[i].Name), boxingExpression); + var arrayIndexParams = new List(); + for (int i = 0; i < expressions.Count; i++) + { + // Just convert the expression always to an object expression. + UnaryExpression boxingExpression = Expression.Convert(expressions[i], typeof(object)); + NewExpression parameter = Expression.New(constructorForKeyValuePair, (Expression)Expression.Constant(properties[i].Name), boxingExpression); - arrayIndexParams.Add(parameter); - } + arrayIndexParams.Add(parameter); + } - // Create an expression tree that represents creating and initializing a one-dimensional array of type KeyValuePair. - NewArrayExpression newArrayExpression = Expression.NewArrayInit(typeof(KeyValuePair), arrayIndexParams); + // Create an expression tree that represents creating and initializing a one-dimensional array of type KeyValuePair. + NewArrayExpression newArrayExpression = Expression.NewArrayInit(typeof(KeyValuePair), arrayIndexParams); - // Get the "public DynamicClass(KeyValuePair[] propertylist)" constructor + // Get the "public DynamicClass(KeyValuePair[] propertylist)" constructor #if NET35 || NET40 ConstructorInfo constructor = type.GetConstructors().First(); #else - ConstructorInfo constructor = type.GetTypeInfo().DeclaredConstructors.First(); + ConstructorInfo constructor = type.GetTypeInfo().DeclaredConstructors.First(); #endif - return Expression.New(constructor, newArrayExpression); + return Expression.New(constructor, newArrayExpression); #if !UAP10_0 } @@ -1226,6 +1226,7 @@ private Expression CreateNewExpression(List properties, List()); } - //[Fact] - //[Trait("Issue", "136")] +#if !NETCOREAPP1_1 + [Fact] + [Trait("Issue", "136")] public void ExpressionTests_Select_ExpandoObjects() { - //Arrange + // Arrange dynamic a = new ExpandoObject(); a.Name = "a"; a.BlogId = 100; @@ -1227,12 +1228,13 @@ public void ExpressionTests_Select_ExpandoObjects() var list = new List { a, b }; IQueryable qry = list.AsQueryable(); + // Act var result = qry.Select("it").Select("BlogId"); - //Assert + // Assert Assert.Equal(new[] { 100, 200 }, result.ToDynamicArray()); } - +#endif [Fact] public void ExpressionTests_Shift() { From 879ed968b0b2228faef3e511725cb8cebe00de8a Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Fri, 12 Jan 2018 10:16:29 +0100 Subject: [PATCH 5/5] Refactored some code --- .../DynamicQueryableExtensions.cs | 22 +++++---- .../Parser/ExpressionParser.cs | 46 +++++++++---------- 2 files changed, 34 insertions(+), 34 deletions(-) diff --git a/src/System.Linq.Dynamic.Core/DynamicQueryableExtensions.cs b/src/System.Linq.Dynamic.Core/DynamicQueryableExtensions.cs index cdbcf69d..de1dbe0a 100644 --- a/src/System.Linq.Dynamic.Core/DynamicQueryableExtensions.cs +++ b/src/System.Linq.Dynamic.Core/DynamicQueryableExtensions.cs @@ -477,7 +477,7 @@ public static IQueryable GroupBy([NotNull] this IQueryable source, [NotNull] str LambdaExpression elementLambda = DynamicExpressionParser.ParseLambda(createParameterCtor, source.ElementType, null, resultSelector, args); var optimized = OptimizeExpression(Expression.Call( - typeof(Queryable), "GroupBy", + typeof(Queryable), nameof(Queryable.GroupBy), new[] { source.ElementType, keyLambda.Body.Type, elementLambda.Body.Type }, source.Expression, Expression.Quote(keyLambda), Expression.Quote(elementLambda))); @@ -635,7 +635,7 @@ public static IQueryable GroupJoin([NotNull] this IQueryable outer, [NotNull] IE LambdaExpression resultSelectorLambda = DynamicExpressionParser.ParseLambda(createParameterCtor, parameters, null, resultSelector, args); return outer.Provider.CreateQuery(Expression.Call( - typeof(Queryable), "GroupJoin", + typeof(Queryable), nameof(Queryable.GroupJoin), new[] { outer.ElementType, innerType, outerSelectorLambda.Body.Type, resultSelectorLambda.Body.Type }, outer.Expression, Expression.Constant(inner), @@ -1146,9 +1146,13 @@ private static IQueryable SelectManyInternal(IQueryable source, Type resultType, // SelectMany assumes that lambda.Body.Type is a generic type and throws an exception on // lambda.Body.Type.GetGenericArguments()[0] when used over an array as GetGenericArguments() returns an empty array. if (lambda.Body.Type.IsArray) + { resultType = lambda.Body.Type.GetElementType(); + } else + { resultType = lambda.Body.Type.GetGenericArguments()[0]; + } } //we have to adjust to lambda to return an IEnumerable instead of whatever the actual property is. @@ -1158,7 +1162,7 @@ private static IQueryable SelectManyInternal(IQueryable source, Type resultType, lambda = Expression.Lambda(delegateType, lambda.Body, lambda.Parameters); var optimized = OptimizeExpression(Expression.Call( - typeof(Queryable), "SelectMany", + typeof(Queryable), nameof(Queryable.SelectMany), new[] { source.ElementType, resultType }, source.Expression, Expression.Quote(lambda)) ); @@ -1196,7 +1200,7 @@ public static IQueryable SelectMany([NotNull] this IQueryable lambda = Expression.Lambda(delegateType, lambda.Body, lambda.Parameters); var optimized = OptimizeExpression(Expression.Call( - typeof(Queryable), "SelectMany", + typeof(Queryable), nameof(Queryable.SelectMany), new[] { source.ElementType, typeof(TResult) }, source.Expression, Expression.Quote(lambda)) ); @@ -1285,7 +1289,7 @@ public static IQueryable SelectMany([NotNull] this IQueryable source, [NotNull] Type resultLambdaResultType = resultSelectLambda.Body.Type; var optimized = OptimizeExpression(Expression.Call( - typeof(Queryable), "SelectMany", + typeof(Queryable), nameof(Queryable.SelectMany), new[] { source.ElementType, sourceLambdaResultType, resultLambdaResultType }, source.Expression, Expression.Quote(sourceSelectLambda), Expression.Quote(resultSelectLambda)) ); @@ -1309,7 +1313,7 @@ public static dynamic Single([NotNull] this IQueryable source) { Check.NotNull(source, nameof(source)); - var optimized = OptimizeExpression(Expression.Call(typeof(Queryable), "Single", new[] { source.ElementType }, source.Expression)); + var optimized = OptimizeExpression(Expression.Call(typeof(Queryable), nameof(Queryable.Single), new[] { source.ElementType }, source.Expression)); return source.Provider.Execute(optimized); } @@ -1370,7 +1374,7 @@ public static dynamic SingleOrDefault([NotNull] this IQueryable source) { Check.NotNull(source, nameof(source)); - var optimized = OptimizeExpression(Expression.Call(typeof(Queryable), "SingleOrDefault", new[] { source.ElementType }, source.Expression)); + var optimized = OptimizeExpression(Expression.Call(typeof(Queryable), nameof(Queryable.SingleOrDefault), new[] { source.ElementType }, source.Expression)); return source.Provider.Execute(optimized); } @@ -1478,7 +1482,7 @@ public static object Sum([NotNull] this IQueryable source) { Check.NotNull(source, nameof(source)); - var optimized = OptimizeExpression(Expression.Call(typeof(Queryable), "Sum", null, source.Expression)); + var optimized = OptimizeExpression(Expression.Call(typeof(Queryable), nameof(Queryable.Sum), null, source.Expression)); return source.Provider.Execute(optimized); } #endregion Sum @@ -1670,7 +1674,7 @@ public static IQueryable Where([NotNull] this IQueryable source, [NotNull] Lambd Check.NotNull(source, nameof(source)); Check.NotNull(lambda, nameof(lambda)); - var optimized = OptimizeExpression(Expression.Call(typeof(Queryable), "Where", new[] { source.ElementType }, source.Expression, Expression.Quote(lambda))); + var optimized = OptimizeExpression(Expression.Call(typeof(Queryable), nameof(Queryable.Where), new[] { source.ElementType }, source.Expression, Expression.Quote(lambda))); return source.Provider.CreateQuery(optimized); } #endregion diff --git a/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs b/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs index b20a96e2..c8ccf909 100644 --- a/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs +++ b/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs @@ -349,9 +349,9 @@ Expression ParseIn() var args = new[] { left }; - if (MethodFinder.FindMethod(typeof(IEnumerableSignatures), "Contains", false, args, out MethodBase containsSignature) != 1) + if (MethodFinder.FindMethod(typeof(IEnumerableSignatures), nameof(IEnumerableSignatures.Contains), false, args, out MethodBase containsSignature) != 1) { - throw ParseError(op.Pos, Res.NoApplicableAggregate, "Contains"); + throw ParseError(op.Pos, Res.NoApplicableAggregate, nameof(IEnumerableSignatures.Contains)); } var typeArgs = new[] { left.Type }; @@ -1153,14 +1153,10 @@ private Expression CreateArrayInitializerExpression(List expressions if (newType != null) { - return Expression.NewArrayInit( - newType, - expressions.Select(expression => ExpressionPromoter.Promote(expression, newType, true, true))); + return Expression.NewArrayInit(newType, expressions.Select(expression => ExpressionPromoter.Promote(expression, newType, true, true))); } - return Expression.NewArrayInit( - expressions.All(expression => expression.Type == expressions[0].Type) ? expressions[0].Type : typeof(object), - expressions); + return Expression.NewArrayInit(expressions.All(expression => expression.Type == expressions[0].Type) ? expressions[0].Type : typeof(object), expressions); } private Expression CreateNewExpression(List properties, List expressions, Type newType) @@ -1174,33 +1170,33 @@ private Expression CreateNewExpression(List properties, List); + type = typeof(DynamicClass); + Type typeForKeyValuePair = typeof(KeyValuePair); #if NET35 || NET40 ConstructorInfo constructorForKeyValuePair = typeForKeyValuePair.GetConstructors().First(); #else - ConstructorInfo constructorForKeyValuePair = typeForKeyValuePair.GetTypeInfo().DeclaredConstructors.First(); + ConstructorInfo constructorForKeyValuePair = typeForKeyValuePair.GetTypeInfo().DeclaredConstructors.First(); #endif - var arrayIndexParams = new List(); - for (int i = 0; i < expressions.Count; i++) - { - // Just convert the expression always to an object expression. - UnaryExpression boxingExpression = Expression.Convert(expressions[i], typeof(object)); - NewExpression parameter = Expression.New(constructorForKeyValuePair, (Expression)Expression.Constant(properties[i].Name), boxingExpression); + var arrayIndexParams = new List(); + for (int i = 0; i < expressions.Count; i++) + { + // Just convert the expression always to an object expression. + UnaryExpression boxingExpression = Expression.Convert(expressions[i], typeof(object)); + NewExpression parameter = Expression.New(constructorForKeyValuePair, (Expression)Expression.Constant(properties[i].Name), boxingExpression); - arrayIndexParams.Add(parameter); - } + arrayIndexParams.Add(parameter); + } - // Create an expression tree that represents creating and initializing a one-dimensional array of type KeyValuePair. - NewArrayExpression newArrayExpression = Expression.NewArrayInit(typeof(KeyValuePair), arrayIndexParams); + // Create an expression tree that represents creating and initializing a one-dimensional array of type KeyValuePair. + NewArrayExpression newArrayExpression = Expression.NewArrayInit(typeof(KeyValuePair), arrayIndexParams); - // Get the "public DynamicClass(KeyValuePair[] propertylist)" constructor + // Get the "public DynamicClass(KeyValuePair[] propertylist)" constructor #if NET35 || NET40 ConstructorInfo constructor = type.GetConstructors().First(); #else - ConstructorInfo constructor = type.GetTypeInfo().DeclaredConstructors.First(); + ConstructorInfo constructor = type.GetTypeInfo().DeclaredConstructors.First(); #endif - return Expression.New(constructor, newArrayExpression); + return Expression.New(constructor, newArrayExpression); #if !UAP10_0 } @@ -1235,7 +1231,7 @@ Expression ParseLambdaInvocation(LambdaExpression lambda) int errorPos = _textParser.CurrentToken.Pos; _textParser.NextToken(); Expression[] args = ParseArgumentList(); - if (MethodFinder.FindMethod(lambda.Type, "Invoke", false, args, out MethodBase _) != 1) + if (MethodFinder.FindMethod(lambda.Type, nameof(Expression.Invoke), false, args, out MethodBase _) != 1) { throw ParseError(errorPos, Res.ArgsIncompatibleWithLambda); }