/* 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
}
}