Skip to content

Commit

Permalink
Bits and pieces to create a custom LINQ engine
Browse files Browse the repository at this point in the history
Kickstarts #87 though a ton more work is needed
  • Loading branch information
Turnerj committed Dec 17, 2020
1 parent 1c66247 commit e08df5e
Show file tree
Hide file tree
Showing 5 changed files with 224 additions and 0 deletions.
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 src/MongoFramework/Infrastructure/Querying/ExpressionHelper.cs
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 src/MongoFramework/Infrastructure/Querying/ICallConverter.cs
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 src/MongoFramework/Infrastructure/Querying/QueryMapping.cs
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;
}
}
}
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());
}
}
}

0 comments on commit e08df5e

Please sign in to comment.