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/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/DynamicGetMemberBinder.cs b/src/System.Linq.Dynamic.Core/DynamicGetMemberBinder.cs new file mode 100644 index 00000000..0054146a --- /dev/null +++ b/src/System.Linq.Dynamic.Core/DynamicGetMemberBinder.cs @@ -0,0 +1,34 @@ +#if !NET35 +using System.Collections.Generic; +using System.Dynamic; +using System.Linq.Expressions; +using System.Reflection; + +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 static readonly PropertyInfo Indexer = typeof(IDictionary).GetProperty("Item"); + + public DynamicGetMemberBinder(string name) + : base(name, true) + { + } + + public override DynamicMetaObject FallbackGetMember(DynamicMetaObject target, DynamicMetaObject errorSuggestion) + { + IDictionary dictionary = target.Value as IDictionary; + if (dictionary == null) + { + throw new InvalidOperationException("Target object is not an ExpandoObject"); + } + + return DynamicMetaObject.Create(dictionary, Expression.MakeIndex(Expression.Constant(dictionary), Indexer, new Expression[] { Expression.Constant(Name) })); + } + } +} +#endif 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 86db98d6..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) @@ -1226,6 +1222,7 @@ private Expression CreateNewExpression(List properties, List()); } - //[Fact] +#if !NETCOREAPP1_1 + [Fact] + [Trait("Issue", "136")] public void ExpressionTests_Select_ExpandoObjects() { - //Arrange + // Arrange dynamic a = new ExpandoObject(); a.Name = "a"; 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(); + // Act var result = qry.Select("it").Select("BlogId"); - //Assert + // Assert Assert.Equal(new[] { 100, 200 }, result.ToDynamicArray()); } - +#endif [Fact] public void ExpressionTests_Shift() {