You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

233 lines
10 KiB

  1. /* Copyright 2010-2011 10gen Inc.
  2. *
  3. * Licensed under the Apache License, Version 2.0 (the "License");
  4. * you may not use this file except in compliance with the License.
  5. * You may obtain a copy of the License at
  6. *
  7. * http://www.apache.org/licenses/LICENSE-2.0
  8. *
  9. * Unless required by applicable law or agreed to in writing, software
  10. * distributed under the License is distributed on an "AS IS" BASIS,
  11. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. * See the License for the specific language governing permissions and
  13. * limitations under the License.
  14. */
  15. using System;
  16. using System.Collections.Generic;
  17. using System.Linq;
  18. using System.Linq.Expressions;
  19. using System.Text;
  20. using System.Threading;
  21. using System.Reflection;
  22. namespace MongoDB.Driver.Linq {
  23. /// <summary>
  24. /// An implementation of IQueryProvider for querying a MongoDB collection.
  25. /// </summary>
  26. public class MongoQueryProvider : IQueryProvider {
  27. #region private static fields
  28. private static Dictionary<Type, Func<MongoQueryProvider, Expression, IQueryable>> createQueryDelegates = new Dictionary<Type, Func<MongoQueryProvider, Expression, IQueryable>>();
  29. private static MethodInfo createQueryGenericMethodDefinition;
  30. private static Dictionary<Type, Func<MongoQueryProvider, Expression, object>> executeDelegates = new Dictionary<Type, Func<MongoQueryProvider, Expression, object>>();
  31. private static MethodInfo executeGenericMethodDefinition;
  32. private static object staticLock = new object();
  33. #endregion
  34. #region private fields
  35. private MongoCollection collection;
  36. #endregion
  37. #region static constructor
  38. static MongoQueryProvider() {
  39. foreach (var methodInfo in typeof(MongoQueryProvider).GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly)) {
  40. if (methodInfo.Name == "CreateQuery" && methodInfo.IsGenericMethodDefinition) {
  41. createQueryGenericMethodDefinition = methodInfo;
  42. }
  43. if (methodInfo.Name == "Execute" && methodInfo.IsGenericMethodDefinition) {
  44. executeGenericMethodDefinition = methodInfo;
  45. }
  46. }
  47. }
  48. #endregion
  49. #region constructors
  50. /// <summary>
  51. /// Initializes a new instance of the MongoQueryProvider class.
  52. /// </summary>
  53. public MongoQueryProvider(
  54. MongoCollection collection
  55. ) {
  56. if (collection == null) {
  57. throw new ArgumentNullException("collection");
  58. }
  59. this.collection = collection;
  60. }
  61. #endregion
  62. #region private static methods
  63. private static Func<MongoQueryProvider, Expression, IQueryable> GetCreateQueryDelegate(
  64. Type type
  65. ) {
  66. lock (staticLock) {
  67. Func<MongoQueryProvider, Expression, IQueryable> createQueryDelegate;
  68. if (!createQueryDelegates.TryGetValue(type, out createQueryDelegate)) {
  69. var createQueryMethodInfo = createQueryGenericMethodDefinition.MakeGenericMethod(type);
  70. // lambdaExpression = (provider, expression) => (IQueryable) provider.CreateQuery<T>(expression)
  71. var providerParameter = Expression.Parameter(typeof(MongoQueryProvider), "provider");
  72. var expressionParameter = Expression.Parameter(typeof(Expression), "expression");
  73. var lambdaExpression = Expression.Lambda<Func<MongoQueryProvider, Expression, IQueryable>>(
  74. Expression.Convert(
  75. Expression.Call(providerParameter, createQueryMethodInfo, expressionParameter),
  76. typeof(IQueryable)
  77. ),
  78. providerParameter,
  79. expressionParameter
  80. );
  81. createQueryDelegate = lambdaExpression.Compile();
  82. createQueryDelegates.Add(type, createQueryDelegate);
  83. }
  84. return createQueryDelegate;
  85. }
  86. }
  87. private static Func<MongoQueryProvider, Expression, object> GetExecuteDelegate(
  88. Type type
  89. ) {
  90. lock (staticLock) {
  91. Func<MongoQueryProvider, Expression, object> executeDelegate;
  92. if (!executeDelegates.TryGetValue(type, out executeDelegate)) {
  93. var executeMethodInfo = executeGenericMethodDefinition.MakeGenericMethod(type);
  94. // lambdaExpression = (provider, expression) => (object) provider.Execute<T>(expression)
  95. var providerParameter = Expression.Parameter(typeof(MongoQueryProvider), "provider");
  96. var expressionParameter = Expression.Parameter(typeof(Expression), "expression");
  97. var lambdaExpression = Expression.Lambda<Func<MongoQueryProvider, Expression, object>>(
  98. Expression.Convert(
  99. Expression.Call(providerParameter, executeMethodInfo, expressionParameter),
  100. typeof(object)
  101. ),
  102. providerParameter,
  103. expressionParameter
  104. );
  105. executeDelegate = lambdaExpression.Compile();
  106. executeDelegates.Add(type, executeDelegate);
  107. }
  108. return executeDelegate;
  109. }
  110. }
  111. #endregion
  112. #region public methods
  113. /// <summary>
  114. /// Creates a new instance of MongoQueryable{{T}} for this provider.
  115. /// </summary>
  116. /// <typeparam name="T">The type of the returned elements.</typeparam>
  117. /// <param name="expression">The query expression.</param>
  118. /// <returns>A new instance of MongoQueryable{{T}}.</returns>
  119. public IQueryable<T> CreateQuery<T>(
  120. Expression expression
  121. ) {
  122. if (expression == null) {
  123. throw new ArgumentNullException("expression");
  124. }
  125. if (!typeof(IQueryable<T>).IsAssignableFrom(expression.Type)) {
  126. throw new ArgumentOutOfRangeException("expression");
  127. }
  128. return new MongoQueryable<T>(this, expression);
  129. }
  130. /// <summary>
  131. /// Creates a new instance MongoQueryable{{T}} for this provider. Calls the generic CreateQuery{{T}}
  132. /// to actually create the new MongoQueryable{{T}} instance.
  133. /// </summary>
  134. /// <param name="expression">The query expression.</param>
  135. /// <returns>A new instance of MongoQueryable{{T}}.</returns>
  136. public IQueryable CreateQuery(
  137. Expression expression
  138. ) {
  139. if (expression == null) {
  140. throw new ArgumentNullException("expression");
  141. }
  142. try {
  143. var elementType = TypeSystem.GetElementType(expression.Type);
  144. var createQueryDelegate = GetCreateQueryDelegate(elementType);
  145. return createQueryDelegate(this, expression);
  146. } catch (TargetInvocationException ex) {
  147. throw ex.InnerException;
  148. }
  149. }
  150. /// <summary>
  151. /// Executes a query.
  152. /// </summary>
  153. /// <typeparam name="TResult">The type of the result.</typeparam>
  154. /// <param name="expression">The query expression.</param>
  155. /// <returns>The result of the query.</returns>
  156. public TResult Execute<TResult>(
  157. Expression expression
  158. ) {
  159. if (expression == null) {
  160. throw new ArgumentNullException("expression");
  161. }
  162. if (!typeof(TResult).IsAssignableFrom(expression.Type)) {
  163. throw new ArgumentException("Argument expression is not valid.");
  164. }
  165. var translatedQuery = MongoLinqTranslator.Translate(collection, expression);
  166. return (TResult) translatedQuery.Execute();
  167. }
  168. /// <summary>
  169. /// Executes a query. Calls the generic method Execute{{T}} to actually execute the query.
  170. /// </summary>
  171. /// <param name="expression">The query expression.</param>
  172. /// <returns>The result of the query.</returns>
  173. public object Execute(
  174. Expression expression
  175. ) {
  176. if (expression == null) {
  177. throw new ArgumentNullException("expression");
  178. }
  179. try {
  180. var resultType = expression.Type;
  181. var executeDelegate = GetExecuteDelegate(resultType);
  182. return executeDelegate(this, expression);
  183. } catch (TargetInvocationException ex) {
  184. throw ex.InnerException;
  185. }
  186. }
  187. /// <summary>
  188. /// Gets an enumerator by executing the query.
  189. /// </summary>
  190. /// <typeparam name="T">Type element type.</typeparam>
  191. /// <param name="expression">The LINQ expression.</param>
  192. /// <returns>An enumerator for the results of the query.</returns>
  193. public IEnumerator<T> GetEnumerator<T>(
  194. Expression expression
  195. ) {
  196. if (expression == null) {
  197. throw new ArgumentNullException("expression");
  198. }
  199. if (!typeof(IEnumerable<T>).IsAssignableFrom(expression.Type)) {
  200. throw new ArgumentException("Argument expression is not valid.");
  201. }
  202. var translatedQuery = MongoLinqTranslator.Translate(collection, expression);
  203. return translatedQuery.GetEnumerator<T>();
  204. }
  205. /// <summary>
  206. /// Gets a string representation of the LINQ expression translated to a MongoDB query.
  207. /// </summary>
  208. /// <param name="expression">The LINQ expression.</param>
  209. /// <returns>A string.</returns>
  210. public string GetQueryText(
  211. Expression expression
  212. ) {
  213. var translatedQuery = MongoLinqTranslator.Translate(collection, expression);
  214. return translatedQuery.ToString();
  215. }
  216. #endregion
  217. }
  218. }