-
-
Notifications
You must be signed in to change notification settings - Fork 35
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Bits and pieces to create a custom LINQ engine
Kickstarts #87 though a ton more work is needed
- Loading branch information
Showing
5 changed files
with
224 additions
and
0 deletions.
There are no files selected for viewing
21 changes: 21 additions & 0 deletions
21
src/MongoFramework/Infrastructure/Querying/CallConverters/WhereConverter.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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]) } | ||
}; | ||
} | ||
} | ||
} |
59 changes: 59 additions & 0 deletions
59
src/MongoFramework/Infrastructure/Querying/ExpressionHelper.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<Func<T1>> Stub<T1>() | ||
{ | ||
return null; | ||
} | ||
public static Expression<Func<T1, T2>> Stub<T1, T2>() | ||
{ | ||
return null; | ||
} | ||
public static Expression<Func<T1, T2, T3>> Stub<T1, T2, T3>() | ||
{ | ||
return null; | ||
} | ||
public static Expression<Func<T1, T2, T3, T4>> Stub<T1, T2, T3, T4>() | ||
{ | ||
return null; | ||
} | ||
|
||
public static MethodInfo GetGenericMethodInfo(Expression<Action> 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; | ||
} | ||
} | ||
} |
13 changes: 13 additions & 0 deletions
13
src/MongoFramework/Infrastructure/Querying/ICallConverter.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
} | ||
} |
59 changes: 59 additions & 0 deletions
59
src/MongoFramework/Infrastructure/Querying/QueryMapping.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<MethodInfo, ICallConverter> CallConverters { get; } = new ConcurrentDictionary<MethodInfo, ICallConverter>(); | ||
|
||
static QueryMapping() | ||
{ | ||
AddConverter(WhereConverter.Instance, () => Queryable.Where(default, ExpressionHelper.Stub<object, bool>())); | ||
} | ||
|
||
public static void AddConverter(ICallConverter callConverter, params Expression<Action>[] methodExpressions) | ||
{ | ||
foreach (var expression in methodExpressions) | ||
{ | ||
var methodInfo = ExpressionHelper.GetGenericMethodInfo(expression); | ||
CallConverters.TryAdd(methodInfo, callConverter); | ||
} | ||
} | ||
|
||
public static IEnumerable<BsonDocument> FromExpression(Expression expression) | ||
{ | ||
var currentExpression = expression; | ||
var stages = new Stack<BsonDocument>(); | ||
|
||
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; | ||
} | ||
} | ||
} |
72 changes: 72 additions & 0 deletions
72
tests/MongoFramework.Tests/Infrastructure/Querying/QueryMappingTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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()); | ||
} | ||
} | ||
} |