QueryableEx.cs 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. // Licensed to the .NET Foundation under one or more agreements.
  2. // The .NET Foundation licenses this file to you under the Apache 2.0 License.
  3. // See the LICENSE file in the project root for more information.
  4. using System.Collections.Generic;
  5. using System.ComponentModel;
  6. using System.Globalization;
  7. using System.Linq.Expressions;
  8. using System.Reflection;
  9. namespace System.Linq
  10. {
  11. /// <summary>
  12. /// Provides a set of additional static methods that allow querying enumerable sequences.
  13. /// </summary>
  14. public static partial class QueryableEx
  15. {
  16. /// <summary>
  17. /// Gets the local Queryable provider.
  18. /// </summary>
  19. public static IQueryProvider Provider
  20. {
  21. get
  22. {
  23. return new QueryProviderShim();
  24. }
  25. }
  26. class QueryProviderShim : IQueryProvider
  27. {
  28. public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
  29. {
  30. var provider = new TElement[0].AsQueryable().Provider;
  31. var res = Redir(expression);
  32. return provider.CreateQuery<TElement>(res);
  33. }
  34. public IQueryable CreateQuery(Expression expression)
  35. {
  36. return CreateQuery<object>(expression);
  37. }
  38. public TResult Execute<TResult>(Expression expression)
  39. {
  40. var provider = new TResult[0].AsQueryable().Provider;
  41. var res = Redir(expression);
  42. return provider.Execute<TResult>(res);
  43. }
  44. public object Execute(Expression expression)
  45. {
  46. return Execute<object>(expression);
  47. }
  48. private static Expression Redir(Expression expression)
  49. {
  50. if (expression is MethodCallExpression mce && mce.Method.DeclaringType == typeof(QueryableEx))
  51. {
  52. if (mce.Arguments.Count >= 1 && typeof(IQueryProvider).IsAssignableFrom(mce.Arguments[0].Type))
  53. {
  54. if (mce.Arguments[0] is ConstantExpression ce)
  55. {
  56. if (ce.Value is QueryProviderShim)
  57. {
  58. var targetType = typeof(QueryableEx);
  59. var method = mce.Method;
  60. var methods = GetMethods(targetType);
  61. var arguments = mce.Arguments.Skip(1).ToList();
  62. //
  63. // From all the operators with the method's name, find the one that matches all arguments.
  64. //
  65. var typeArgs = method.IsGenericMethod ? method.GetGenericArguments() : null;
  66. var targetMethod = methods[method.Name].FirstOrDefault(candidateMethod => ArgsMatch(candidateMethod, arguments, typeArgs));
  67. if (targetMethod == null)
  68. throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, "There is no method '{0}' on type '{1}' that matches the specified arguments", method.Name, targetType.Name));
  69. //
  70. // Restore generic arguments.
  71. //
  72. if (typeArgs != null)
  73. targetMethod = targetMethod.MakeGenericMethod(typeArgs);
  74. //
  75. // Finally, we need to deal with mismatches on Expression<Func<...>> versus Func<...>.
  76. //
  77. var parameters = targetMethod.GetParameters();
  78. for (int i = 0, n = parameters.Length; i < n; i++)
  79. {
  80. arguments[i] = Unquote(arguments[i]);
  81. }
  82. //
  83. // Emit a new call to the discovered target method.
  84. //
  85. return Expression.Call(null, targetMethod, arguments);
  86. }
  87. }
  88. }
  89. }
  90. return expression;
  91. }
  92. private static ILookup<string, MethodInfo> GetMethods(Type type)
  93. {
  94. return type.GetMethods(BindingFlags.Static | BindingFlags.Public).ToLookup(m => m.Name);
  95. }
  96. private static bool ArgsMatch(MethodInfo method, IList<Expression> arguments, Type[] typeArgs)
  97. {
  98. //
  99. // Number of parameters should match. Notice we've sanitized IQueryProvider "this"
  100. // parameters first (see Redir).
  101. //
  102. var parameters = method.GetParameters();
  103. if (parameters.Length != arguments.Count)
  104. return false;
  105. //
  106. // Genericity should match too.
  107. //
  108. if (!method.IsGenericMethod && typeArgs != null && typeArgs.Length > 0)
  109. return false;
  110. //
  111. // Reconstruct the generic method if needed.
  112. //
  113. if (method.IsGenericMethodDefinition)
  114. {
  115. if (typeArgs == null)
  116. return false;
  117. if (method.GetGenericArguments().Length != typeArgs.Length)
  118. return false;
  119. var result = method.MakeGenericMethod(typeArgs);
  120. parameters = result.GetParameters();
  121. }
  122. //
  123. // Check compatibility for the parameter types.
  124. //
  125. for (int i = 0, n = arguments.Count; i < n; i++)
  126. {
  127. var parameterType = parameters[i].ParameterType;
  128. var argument = arguments[i];
  129. //
  130. // For operators that take a function (like Where, Select), we'll be faced
  131. // with a quoted argument and a discrepancy between Expression<Func<...>>
  132. // and the underlying Func<...>.
  133. //
  134. if (!parameterType.IsAssignableFrom(argument.Type))
  135. {
  136. argument = Unquote(argument);
  137. if (!parameterType.IsAssignableFrom(argument.Type))
  138. return false;
  139. }
  140. }
  141. return true;
  142. }
  143. private static Expression Unquote(Expression expression)
  144. {
  145. //
  146. // Get rid of all outer quotes around an expression.
  147. //
  148. while (expression.NodeType == ExpressionType.Quote)
  149. expression = ((UnaryExpression)expression).Operand;
  150. return expression;
  151. }
  152. }
  153. internal static Expression GetSourceExpression<TSource>(IEnumerable<TSource> source)
  154. {
  155. if (source is IQueryable<TSource> q)
  156. return q.Expression;
  157. return Expression.Constant(source, typeof(IEnumerable<TSource>));
  158. }
  159. internal static Expression GetSourceExpression<TSource>(IEnumerable<TSource>[] sources)
  160. {
  161. return Expression.NewArrayInit(
  162. typeof(IEnumerable<TSource>),
  163. sources.Select(source => GetSourceExpression(source))
  164. );
  165. }
  166. internal static MethodInfo InfoOf<R>(Expression<Func<R>> f)
  167. {
  168. return ((MethodCallExpression)f.Body).Method;
  169. }
  170. }
  171. }