diff --git a/src/MongoFramework/Infrastructure/Querying/CallConverters/WhereConverter.cs b/src/MongoFramework/Infrastructure/Querying/CallConverters/WhereConverter.cs new file mode 100644 index 00000000..a2c11e0c --- /dev/null +++ b/src/MongoFramework/Infrastructure/Querying/CallConverters/WhereConverter.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq.Expressions; +using System.Text; +using MongoDB.Bson; + +namespace MongoFramework.Infrastructure.Querying.CallConverters +{ + public class WhereConverter : ICallConverter + { + public static ICallConverter Instance { get; } = new WhereConverter(); + + public BsonDocument Convert(MethodCallExpression expression) + { + return new BsonDocument + { + { "$match", ExpressionHelper.Where(expression.Arguments[1]) } + }; + } + } +} diff --git a/src/MongoFramework/Infrastructure/Querying/ExpressionHelper.cs b/src/MongoFramework/Infrastructure/Querying/ExpressionHelper.cs new file mode 100644 index 00000000..3f5d3bef --- /dev/null +++ b/src/MongoFramework/Infrastructure/Querying/ExpressionHelper.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.Linq.Expressions; +using System.Reflection; +using System.Text; +using MongoDB.Bson; +using MongoFramework.Infrastructure.Mapping; + +namespace MongoFramework.Infrastructure.Querying +{ + public static class ExpressionHelper + { + public static Expression> Stub() + { + return null; + } + public static Expression> Stub() + { + return null; + } + public static Expression> Stub() + { + return null; + } + public static Expression> Stub() + { + return null; + } + + public static MethodInfo GetGenericMethodInfo(Expression expression) + { + if (expression.Body.NodeType == ExpressionType.Call) + { + var methodInfo = ((MethodCallExpression)expression.Body).Method; + return methodInfo.GetGenericMethodDefinition(); + } + + throw new InvalidOperationException("The provided expression does not call a method"); + } + + public static BsonDocument Where(LambdaExpression expression) + { + if (expression.ReturnType != typeof(bool)) + { + throw new ArgumentException("Expression must return a boolean"); + } + + var incomingType = expression.Parameters[0].Type; + + if (EntityMapping.IsRegistered(incomingType)) + { + var definition = EntityMapping.GetOrCreateDefinition(incomingType); + + + } + return null; + } + } +} diff --git a/src/MongoFramework/Infrastructure/Querying/ICallConverter.cs b/src/MongoFramework/Infrastructure/Querying/ICallConverter.cs new file mode 100644 index 00000000..5b4918ef --- /dev/null +++ b/src/MongoFramework/Infrastructure/Querying/ICallConverter.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq.Expressions; +using System.Text; +using MongoDB.Bson; + +namespace MongoFramework.Infrastructure.Querying +{ + public interface ICallConverter + { + BsonDocument Convert(MethodCallExpression expression); + } +} diff --git a/src/MongoFramework/Infrastructure/Querying/QueryMapping.cs b/src/MongoFramework/Infrastructure/Querying/QueryMapping.cs new file mode 100644 index 00000000..1cd84bc2 --- /dev/null +++ b/src/MongoFramework/Infrastructure/Querying/QueryMapping.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using System.Text; +using MongoDB.Bson; +using MongoFramework.Infrastructure.Querying.CallConverters; + +namespace MongoFramework.Infrastructure.Querying +{ + public static class QueryMapping + { + public static ConcurrentDictionary CallConverters { get; } = new ConcurrentDictionary(); + + static QueryMapping() + { + AddConverter(WhereConverter.Instance, () => Queryable.Where(default, ExpressionHelper.Stub())); + } + + public static void AddConverter(ICallConverter callConverter, params Expression[] methodExpressions) + { + foreach (var expression in methodExpressions) + { + var methodInfo = ExpressionHelper.GetGenericMethodInfo(expression); + CallConverters.TryAdd(methodInfo, callConverter); + } + } + + public static IEnumerable FromExpression(Expression expression) + { + var currentExpression = expression; + var stages = new Stack(); + + while (currentExpression is MethodCallExpression callExpression) + { + var methodDefinition = callExpression.Method; + if (methodDefinition.IsGenericMethod) + { + methodDefinition = methodDefinition.GetGenericMethodDefinition(); + } + + if (CallConverters.TryGetValue(methodDefinition, out var converter)) + { + var queryStage = converter.Convert(callExpression); + stages.Push(queryStage); + currentExpression = callExpression.Arguments[0]; + } + else + { + throw new InvalidOperationException($"No converter has been configured for {callExpression.Method}"); + } + } + + return stages; + } + } +} diff --git a/tests/MongoFramework.Tests/Infrastructure/Querying/QueryMappingTests.cs b/tests/MongoFramework.Tests/Infrastructure/Querying/QueryMappingTests.cs new file mode 100644 index 00000000..ce1ee733 --- /dev/null +++ b/tests/MongoFramework.Tests/Infrastructure/Querying/QueryMappingTests.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using MongoFramework.Infrastructure.Querying; + +namespace MongoFramework.Tests.Infrastructure.Querying +{ + [TestClass] + public class QueryMappingTests : TestBase + { + private class QueryTestModel + { + public string Id { get; set; } + public int SomeNumberField { get; set; } + public string AnotherStringField { get; set; } + public NestedQueryTestModel NestedModel { get; set; } + } + + private class NestedQueryTestModel + { + public string Name { get; set; } + public int Number { get; set; } + } + + [TestMethod] + public void EmptyQueryableHasNoStages() + { + var queryable = Queryable.AsQueryable(new[] { + new QueryTestModel() + }); + + var stages = QueryMapping.FromExpression(queryable.Expression); + Assert.AreEqual(0, stages.Count()); + } + + [TestMethod] + public void Queryable_Where() + { + var queryable = Queryable.AsQueryable(new[] { + new QueryTestModel() + }).Where(q => q.Id == ""); + + var stages = QueryMapping.FromExpression(queryable.Expression); + Assert.AreEqual(1, stages.Count()); + } + + [TestMethod] + public void Queryable_Where_OrderBy() + { + var queryable = Queryable.AsQueryable(new[] { + new QueryTestModel() + }).Where(q => q.Id == "").OrderBy(q => q.Id); + + var stages = QueryMapping.FromExpression(queryable.Expression); + Assert.AreEqual(2, stages.Count()); + } + + [TestMethod] + public void Queryable_Where_OrderBy_Select() + { + var queryable = Queryable.AsQueryable(new[] { + new QueryTestModel() + }).Where(q => q.Id == "").OrderBy(q => q.Id).Select(q => q.SomeNumberField); + + var stages = QueryMapping.FromExpression(queryable.Expression); + Assert.AreEqual(3, stages.Count()); + } + } +}