/* Copyright 2010-2011 10gen Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Text; using System.Threading; using System.Reflection; namespace MongoDB.Driver.Linq { /// /// An implementation of IQueryProvider for querying a MongoDB collection. /// public class MongoQueryProvider : IQueryProvider { #region private static fields private static Dictionary> createQueryDelegates = new Dictionary>(); private static MethodInfo createQueryGenericMethodDefinition; private static Dictionary> executeDelegates = new Dictionary>(); private static MethodInfo executeGenericMethodDefinition; private static object staticLock = new object(); #endregion #region private fields private MongoCollection collection; #endregion #region static constructor static MongoQueryProvider() { foreach (var methodInfo in typeof(MongoQueryProvider).GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly)) { if (methodInfo.Name == "CreateQuery" && methodInfo.IsGenericMethodDefinition) { createQueryGenericMethodDefinition = methodInfo; } if (methodInfo.Name == "Execute" && methodInfo.IsGenericMethodDefinition) { executeGenericMethodDefinition = methodInfo; } } } #endregion #region constructors /// /// Initializes a new instance of the MongoQueryProvider class. /// public MongoQueryProvider( MongoCollection collection ) { if (collection == null) { throw new ArgumentNullException("collection"); } this.collection = collection; } #endregion #region private static methods private static Func GetCreateQueryDelegate( Type type ) { lock (staticLock) { Func createQueryDelegate; if (!createQueryDelegates.TryGetValue(type, out createQueryDelegate)) { var createQueryMethodInfo = createQueryGenericMethodDefinition.MakeGenericMethod(type); // lambdaExpression = (provider, expression) => (IQueryable) provider.CreateQuery(expression) var providerParameter = Expression.Parameter(typeof(MongoQueryProvider), "provider"); var expressionParameter = Expression.Parameter(typeof(Expression), "expression"); var lambdaExpression = Expression.Lambda>( Expression.Convert( Expression.Call(providerParameter, createQueryMethodInfo, expressionParameter), typeof(IQueryable) ), providerParameter, expressionParameter ); createQueryDelegate = lambdaExpression.Compile(); createQueryDelegates.Add(type, createQueryDelegate); } return createQueryDelegate; } } private static Func GetExecuteDelegate( Type type ) { lock (staticLock) { Func executeDelegate; if (!executeDelegates.TryGetValue(type, out executeDelegate)) { var executeMethodInfo = executeGenericMethodDefinition.MakeGenericMethod(type); // lambdaExpression = (provider, expression) => (object) provider.Execute(expression) var providerParameter = Expression.Parameter(typeof(MongoQueryProvider), "provider"); var expressionParameter = Expression.Parameter(typeof(Expression), "expression"); var lambdaExpression = Expression.Lambda>( Expression.Convert( Expression.Call(providerParameter, executeMethodInfo, expressionParameter), typeof(object) ), providerParameter, expressionParameter ); executeDelegate = lambdaExpression.Compile(); executeDelegates.Add(type, executeDelegate); } return executeDelegate; } } #endregion #region public methods /// /// Creates a new instance of MongoQueryable{{T}} for this provider. /// /// The type of the returned elements. /// The query expression. /// A new instance of MongoQueryable{{T}}. public IQueryable CreateQuery( Expression expression ) { if (expression == null) { throw new ArgumentNullException("expression"); } if (!typeof(IQueryable).IsAssignableFrom(expression.Type)) { throw new ArgumentOutOfRangeException("expression"); } return new MongoQueryable(this, expression); } /// /// Creates a new instance MongoQueryable{{T}} for this provider. Calls the generic CreateQuery{{T}} /// to actually create the new MongoQueryable{{T}} instance. /// /// The query expression. /// A new instance of MongoQueryable{{T}}. public IQueryable CreateQuery( Expression expression ) { if (expression == null) { throw new ArgumentNullException("expression"); } try { var elementType = TypeSystem.GetElementType(expression.Type); var createQueryDelegate = GetCreateQueryDelegate(elementType); return createQueryDelegate(this, expression); } catch (TargetInvocationException ex) { throw ex.InnerException; } } /// /// Executes a query. /// /// The type of the result. /// The query expression. /// The result of the query. public TResult Execute( Expression expression ) { if (expression == null) { throw new ArgumentNullException("expression"); } if (!typeof(TResult).IsAssignableFrom(expression.Type)) { throw new ArgumentException("Argument expression is not valid."); } var translatedQuery = MongoLinqTranslator.Translate(collection, expression); return (TResult) translatedQuery.Execute(); } /// /// Executes a query. Calls the generic method Execute{{T}} to actually execute the query. /// /// The query expression. /// The result of the query. public object Execute( Expression expression ) { if (expression == null) { throw new ArgumentNullException("expression"); } try { var resultType = expression.Type; var executeDelegate = GetExecuteDelegate(resultType); return executeDelegate(this, expression); } catch (TargetInvocationException ex) { throw ex.InnerException; } } /// /// Gets an enumerator by executing the query. /// /// Type element type. /// The LINQ expression. /// An enumerator for the results of the query. public IEnumerator GetEnumerator( Expression expression ) { if (expression == null) { throw new ArgumentNullException("expression"); } if (!typeof(IEnumerable).IsAssignableFrom(expression.Type)) { throw new ArgumentException("Argument expression is not valid."); } var translatedQuery = MongoLinqTranslator.Translate(collection, expression); return translatedQuery.GetEnumerator(); } /// /// Gets a string representation of the LINQ expression translated to a MongoDB query. /// /// The LINQ expression. /// A string. public string GetQueryText( Expression expression ) { var translatedQuery = MongoLinqTranslator.Translate(collection, expression); return translatedQuery.ToString(); } #endregion } }