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
233 lines
10 KiB
/* 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 {
|
|
/// <summary>
|
|
/// An implementation of IQueryProvider for querying a MongoDB collection.
|
|
/// </summary>
|
|
public class MongoQueryProvider : IQueryProvider {
|
|
#region private static fields
|
|
private static Dictionary<Type, Func<MongoQueryProvider, Expression, IQueryable>> createQueryDelegates = new Dictionary<Type, Func<MongoQueryProvider, Expression, IQueryable>>();
|
|
private static MethodInfo createQueryGenericMethodDefinition;
|
|
private static Dictionary<Type, Func<MongoQueryProvider, Expression, object>> executeDelegates = new Dictionary<Type, Func<MongoQueryProvider, Expression, object>>();
|
|
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
|
|
/// <summary>
|
|
/// Initializes a new instance of the MongoQueryProvider class.
|
|
/// </summary>
|
|
public MongoQueryProvider(
|
|
MongoCollection collection
|
|
) {
|
|
if (collection == null) {
|
|
throw new ArgumentNullException("collection");
|
|
}
|
|
this.collection = collection;
|
|
}
|
|
#endregion
|
|
|
|
#region private static methods
|
|
private static Func<MongoQueryProvider, Expression, IQueryable> GetCreateQueryDelegate(
|
|
Type type
|
|
) {
|
|
lock (staticLock) {
|
|
Func<MongoQueryProvider, Expression, IQueryable> createQueryDelegate;
|
|
if (!createQueryDelegates.TryGetValue(type, out createQueryDelegate)) {
|
|
var createQueryMethodInfo = createQueryGenericMethodDefinition.MakeGenericMethod(type);
|
|
|
|
// lambdaExpression = (provider, expression) => (IQueryable) provider.CreateQuery<T>(expression)
|
|
var providerParameter = Expression.Parameter(typeof(MongoQueryProvider), "provider");
|
|
var expressionParameter = Expression.Parameter(typeof(Expression), "expression");
|
|
var lambdaExpression = Expression.Lambda<Func<MongoQueryProvider, Expression, IQueryable>>(
|
|
Expression.Convert(
|
|
Expression.Call(providerParameter, createQueryMethodInfo, expressionParameter),
|
|
typeof(IQueryable)
|
|
),
|
|
providerParameter,
|
|
expressionParameter
|
|
);
|
|
createQueryDelegate = lambdaExpression.Compile();
|
|
createQueryDelegates.Add(type, createQueryDelegate);
|
|
}
|
|
return createQueryDelegate;
|
|
}
|
|
}
|
|
|
|
private static Func<MongoQueryProvider, Expression, object> GetExecuteDelegate(
|
|
Type type
|
|
) {
|
|
lock (staticLock) {
|
|
Func<MongoQueryProvider, Expression, object> executeDelegate;
|
|
if (!executeDelegates.TryGetValue(type, out executeDelegate)) {
|
|
var executeMethodInfo = executeGenericMethodDefinition.MakeGenericMethod(type);
|
|
|
|
// lambdaExpression = (provider, expression) => (object) provider.Execute<T>(expression)
|
|
var providerParameter = Expression.Parameter(typeof(MongoQueryProvider), "provider");
|
|
var expressionParameter = Expression.Parameter(typeof(Expression), "expression");
|
|
var lambdaExpression = Expression.Lambda<Func<MongoQueryProvider, Expression, object>>(
|
|
Expression.Convert(
|
|
Expression.Call(providerParameter, executeMethodInfo, expressionParameter),
|
|
typeof(object)
|
|
),
|
|
providerParameter,
|
|
expressionParameter
|
|
);
|
|
executeDelegate = lambdaExpression.Compile();
|
|
executeDelegates.Add(type, executeDelegate);
|
|
}
|
|
return executeDelegate;
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region public methods
|
|
/// <summary>
|
|
/// Creates a new instance of MongoQueryable{{T}} for this provider.
|
|
/// </summary>
|
|
/// <typeparam name="T">The type of the returned elements.</typeparam>
|
|
/// <param name="expression">The query expression.</param>
|
|
/// <returns>A new instance of MongoQueryable{{T}}.</returns>
|
|
public IQueryable<T> CreateQuery<T>(
|
|
Expression expression
|
|
) {
|
|
if (expression == null) {
|
|
throw new ArgumentNullException("expression");
|
|
}
|
|
if (!typeof(IQueryable<T>).IsAssignableFrom(expression.Type)) {
|
|
throw new ArgumentOutOfRangeException("expression");
|
|
}
|
|
return new MongoQueryable<T>(this, expression);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a new instance MongoQueryable{{T}} for this provider. Calls the generic CreateQuery{{T}}
|
|
/// to actually create the new MongoQueryable{{T}} instance.
|
|
/// </summary>
|
|
/// <param name="expression">The query expression.</param>
|
|
/// <returns>A new instance of MongoQueryable{{T}}.</returns>
|
|
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;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Executes a query.
|
|
/// </summary>
|
|
/// <typeparam name="TResult">The type of the result.</typeparam>
|
|
/// <param name="expression">The query expression.</param>
|
|
/// <returns>The result of the query.</returns>
|
|
public TResult Execute<TResult>(
|
|
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();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Executes a query. Calls the generic method Execute{{T}} to actually execute the query.
|
|
/// </summary>
|
|
/// <param name="expression">The query expression.</param>
|
|
/// <returns>The result of the query.</returns>
|
|
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;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets an enumerator by executing the query.
|
|
/// </summary>
|
|
/// <typeparam name="T">Type element type.</typeparam>
|
|
/// <param name="expression">The LINQ expression.</param>
|
|
/// <returns>An enumerator for the results of the query.</returns>
|
|
public IEnumerator<T> GetEnumerator<T>(
|
|
Expression expression
|
|
) {
|
|
if (expression == null) {
|
|
throw new ArgumentNullException("expression");
|
|
}
|
|
if (!typeof(IEnumerable<T>).IsAssignableFrom(expression.Type)) {
|
|
throw new ArgumentException("Argument expression is not valid.");
|
|
}
|
|
var translatedQuery = MongoLinqTranslator.Translate(collection, expression);
|
|
return translatedQuery.GetEnumerator<T>();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets a string representation of the LINQ expression translated to a MongoDB query.
|
|
/// </summary>
|
|
/// <param name="expression">The LINQ expression.</param>
|
|
/// <returns>A string.</returns>
|
|
public string GetQueryText(
|
|
Expression expression
|
|
) {
|
|
var translatedQuery = MongoLinqTranslator.Translate(collection, expression);
|
|
return translatedQuery.ToString();
|
|
}
|
|
#endregion
|
|
}
|
|
}
|