This repository has been archived by the owner on May 7, 2020. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 37
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #14 from osoykan/dev
dev to master
- Loading branch information
Showing
56 changed files
with
1,367 additions
and
1,470 deletions.
There are no files selected for viewing
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
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,119 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq.Expressions; | ||
|
||
namespace Stove.Dapper.Dapper.Expressions | ||
{ | ||
/// <summary> | ||
/// Reference | ||
/// From:http://blogs.msdn.com/b/mattwar/archive/2007/08/01/linq-building-an-iqueryable-provider-part-iii.aspx | ||
/// </summary> | ||
internal class Evaluator | ||
{ | ||
public static Expression PartialEval(Expression exp, Func<Expression, bool> canBeEval) | ||
{ | ||
return new SubtreeEvaluator(new Nominator(canBeEval).Nominate(exp)).Eval(exp); | ||
} | ||
|
||
public static Expression PartialEval(Expression exp) | ||
{ | ||
return PartialEval(exp, CanBeEvaluatedLocally); | ||
} | ||
|
||
private static bool CanBeEvaluatedLocally(Expression exp) | ||
{ | ||
return exp.NodeType != ExpressionType.Parameter; | ||
} | ||
|
||
private class SubtreeEvaluator : ExpressionVisitor | ||
{ | ||
private readonly HashSet<Expression> _candidates; | ||
|
||
internal SubtreeEvaluator(HashSet<Expression> candidates) | ||
{ | ||
_candidates = candidates; | ||
} | ||
|
||
internal Expression Eval(Expression exp) | ||
{ | ||
return Visit(exp); | ||
} | ||
|
||
public override Expression Visit(Expression exp) | ||
{ | ||
if (exp == null) | ||
{ | ||
return null; | ||
} | ||
|
||
if (_candidates.Contains(exp)) | ||
{ | ||
return Evaluate(exp); | ||
} | ||
|
||
return base.Visit(exp); | ||
} | ||
|
||
private Expression Evaluate(Expression exp) | ||
{ | ||
if (exp.NodeType == ExpressionType.Constant) | ||
{ | ||
return exp; | ||
} | ||
|
||
LambdaExpression lambda = Expression.Lambda(exp); | ||
Delegate del = lambda.Compile(); | ||
|
||
return Expression.Constant(del.DynamicInvoke(null), exp.Type); | ||
} | ||
} | ||
|
||
private class Nominator : ExpressionVisitor | ||
{ | ||
private readonly Func<Expression, bool> _canBeEval; | ||
private HashSet<Expression> _candidates; | ||
private bool _cannotBeEval; | ||
|
||
internal Nominator(Func<Expression, bool> canBeEval) | ||
{ | ||
_canBeEval = canBeEval; | ||
} | ||
|
||
internal HashSet<Expression> Nominate(Expression exp) | ||
{ | ||
_candidates = new HashSet<Expression>(); | ||
Visit(exp); | ||
return _candidates; | ||
} | ||
|
||
public override Expression Visit(Expression exp) | ||
{ | ||
if (exp == null) | ||
{ | ||
return null; | ||
} | ||
|
||
bool saveCannotBeEval = _cannotBeEval; | ||
_cannotBeEval = false; | ||
|
||
base.Visit(exp); | ||
|
||
if (!_cannotBeEval) | ||
{ | ||
if (_canBeEval(exp)) | ||
{ | ||
_candidates.Add(exp); | ||
} | ||
else | ||
{ | ||
_cannotBeEval = true; | ||
} | ||
} | ||
|
||
_cannotBeEval |= saveCannotBeEval; | ||
|
||
return exp; | ||
} | ||
} | ||
} | ||
} |
31 changes: 31 additions & 0 deletions
31
src/Stove.Dapper/Dapper/Expressions/DapperExpressionExtensions.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,31 @@ | ||
using System; | ||
using System.Linq.Expressions; | ||
|
||
using DapperExtensions; | ||
|
||
using JetBrains.Annotations; | ||
|
||
using Stove.Domain.Entities; | ||
|
||
namespace Stove.Dapper.Dapper.Expressions | ||
{ | ||
/// <summary> | ||
/// http://stackoverflow.com/questions/15154783/pulling-apart-expressionfunct-object | ||
/// http://stackoverflow.com/questions/16083895/grouping-lambda-expressions-by-operators-and-using-them-with-dapperextensions-p | ||
/// http://blogs.msdn.com/b/mattwar/archive/2007/07/31/linq-building-an-iqueryable-provider-part-ii.aspx | ||
/// http://msdn.microsoft.com/en-us/library/bb546136(v=vs.110).aspx | ||
/// http://stackoverflow.com/questions/14437239/change-a-linq-expression-predicate-from-one-type-to-another/14439071#14439071 | ||
/// </summary> | ||
internal static class DapperExpressionExtensions | ||
{ | ||
public static IPredicate ToPredicateGroup<TEntity, TPrimaryKey>([NotNull] this Expression<Func<TEntity, bool>> expression) where TEntity : class, IEntity<TPrimaryKey> | ||
{ | ||
Check.NotNull(expression, nameof(expression)); | ||
|
||
var dev = new DapperExpressionVisitor<TEntity, TPrimaryKey>(); | ||
IPredicate pg = dev.Process(expression); | ||
|
||
return pg; | ||
} | ||
} | ||
} |
222 changes: 222 additions & 0 deletions
222
src/Stove.Dapper/Dapper/Expressions/DapperExpressionVisitor.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,222 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Linq.Expressions; | ||
using System.Reflection; | ||
|
||
using DapperExtensions; | ||
|
||
using Stove.Domain.Entities; | ||
|
||
namespace Stove.Dapper.Dapper.Expressions | ||
{ | ||
/// <summary> | ||
/// This class converts an Expression{Func{TEntity, bool}} into an IPredicate group that can be used with | ||
/// DapperExtension's predicate system | ||
/// </summary> | ||
/// <typeparam name="TEntity">The type of the entity.</typeparam> | ||
/// <typeparam name="TPrimaryKey">The type of the primary key.</typeparam> | ||
/// <seealso cref="System.Linq.Expressions.ExpressionVisitor" /> | ||
internal class DapperExpressionVisitor<TEntity, TPrimaryKey> : ExpressionVisitor where TEntity : class, IEntity<TPrimaryKey> | ||
{ | ||
private PredicateGroup _pg; | ||
private Expression _processedProperty; | ||
private bool _unarySpecified; | ||
|
||
public DapperExpressionVisitor() | ||
{ | ||
Expressions = new HashSet<Expression>(); | ||
} | ||
|
||
/// <summary> | ||
/// Holds BinaryExpressions | ||
/// </summary> | ||
public HashSet<Expression> Expressions { get; } | ||
|
||
public IPredicate Process(Expression exp) | ||
{ | ||
_pg = new PredicateGroup { Predicates = new List<IPredicate>() }; | ||
Visit(Evaluator.PartialEval(exp)); | ||
|
||
// the 1st expression determines root group operator | ||
if (Expressions.Any()) | ||
{ | ||
_pg.Operator = Expressions.First().NodeType == ExpressionType.OrElse ? GroupOperator.Or : GroupOperator.And; | ||
} | ||
|
||
return _pg.Predicates.Count == 1 ? _pg.Predicates[0] : _pg; | ||
} | ||
|
||
private static PredicateGroup GetLastPredicateGroup(PredicateGroup grp) | ||
{ | ||
IList<IPredicate> groups = grp.Predicates; | ||
|
||
if (!groups.Any()) | ||
{ | ||
return grp; | ||
} | ||
|
||
IPredicate last = groups.Last(); | ||
|
||
if (last is PredicateGroup) | ||
{ | ||
return GetLastPredicateGroup(last as PredicateGroup); | ||
} | ||
|
||
return grp; | ||
} | ||
|
||
private IFieldPredicate GetLastField() | ||
{ | ||
PredicateGroup lastGrp = GetLastPredicateGroup(_pg); | ||
|
||
IPredicate last = lastGrp.Predicates.Last(); | ||
|
||
return last as IFieldPredicate; | ||
} | ||
|
||
private static Operator DetermineOperator(Expression binaryExpression) | ||
{ | ||
switch (binaryExpression.NodeType) | ||
{ | ||
case ExpressionType.Equal: | ||
return Operator.Eq; | ||
case ExpressionType.GreaterThan: | ||
return Operator.Gt; | ||
case ExpressionType.GreaterThanOrEqual: | ||
return Operator.Ge; | ||
case ExpressionType.LessThan: | ||
return Operator.Lt; | ||
case ExpressionType.LessThanOrEqual: | ||
return Operator.Le; | ||
default: | ||
return Operator.Eq; | ||
} | ||
} | ||
|
||
private void AddField(MemberExpression exp, Operator op = Operator.Eq, object value = null, bool not = false) | ||
{ | ||
PredicateGroup pg = GetLastPredicateGroup(_pg); | ||
|
||
// need convert from Expression<Func<T, bool>> to Expression<Func<T, object>> as this is what Predicates.Field() requires | ||
Expression<Func<TEntity, object>> fieldExp = Expression.Lambda<Func<TEntity, object>>(Expression.Convert(exp, typeof(object)), exp.Expression as ParameterExpression); | ||
|
||
IFieldPredicate field = Predicates.Field(fieldExp, op, value, not); | ||
pg.Predicates.Add(field); | ||
} | ||
|
||
protected override Expression VisitBinary(BinaryExpression node) | ||
{ | ||
Expressions.Add(node); | ||
|
||
ExpressionType nt = node.NodeType; | ||
|
||
if (nt == ExpressionType.OrElse || nt == ExpressionType.AndAlso) | ||
{ | ||
var pg = new PredicateGroup | ||
{ | ||
Predicates = new List<IPredicate>(), | ||
Operator = nt == ExpressionType.OrElse ? GroupOperator.Or : GroupOperator.And | ||
}; | ||
|
||
_pg.Predicates.Add(pg); | ||
} | ||
|
||
Visit(node.Left); | ||
|
||
if (node.Left is MemberExpression) | ||
{ | ||
IFieldPredicate field = GetLastField(); | ||
field.Operator = DetermineOperator(node); | ||
|
||
if (nt == ExpressionType.NotEqual) | ||
{ | ||
field.Not = true; | ||
} | ||
} | ||
|
||
Visit(node.Right); | ||
|
||
return node; | ||
} | ||
|
||
protected override Expression VisitMember(MemberExpression node) | ||
{ | ||
if (node.Member.MemberType != MemberTypes.Property || node.Expression.Type != typeof(TEntity)) | ||
{ | ||
throw new NotSupportedException($"The member '{node}' is not supported"); | ||
} | ||
|
||
// skip if prop is part of a VisitMethodCall | ||
if (_processedProperty != null && _processedProperty == node) | ||
{ | ||
_processedProperty = null; | ||
return node; | ||
} | ||
|
||
AddField(node); | ||
|
||
return node; | ||
} | ||
|
||
protected override Expression VisitConstant(ConstantExpression node) | ||
{ | ||
IFieldPredicate field = GetLastField(); | ||
field.Value = node.Value; | ||
|
||
return node; | ||
} | ||
|
||
protected override Expression VisitMethodCall(MethodCallExpression node) | ||
{ | ||
if (node.Type == typeof(bool) && node.Method.DeclaringType == typeof(string)) | ||
{ | ||
object arg = ((ConstantExpression)node.Arguments[0]).Value; | ||
var op = Operator.Like; | ||
|
||
switch (node.Method.Name.ToLowerInvariant()) | ||
{ | ||
case "startswith": | ||
arg = arg + "%"; | ||
break; | ||
case "endswith": | ||
arg = "%" + arg; | ||
break; | ||
case "contains": | ||
arg = "%" + arg + "%"; | ||
break; | ||
case "equals": | ||
op = Operator.Eq; | ||
break; | ||
default: | ||
throw new NotSupportedException($"The method '{node}' is not supported"); | ||
} | ||
|
||
// this is a PropertyExpression but as it's internal, to use, we cast to the base MemberExpression instead (see http://social.msdn.microsoft.com/Forums/en-US/ab528f6a-a60e-4af6-bf31-d58e3f373356/resolving-propertyexpressions-and-fieldexpressions-in-a-custom-linq-provider) | ||
_processedProperty = node.Object; | ||
var me = _processedProperty as MemberExpression; | ||
|
||
AddField(me, op, arg, _unarySpecified); | ||
|
||
// reset if applicable | ||
_unarySpecified = false; | ||
|
||
return node; | ||
} | ||
|
||
throw new NotSupportedException($"The method '{node}' is not supported"); | ||
} | ||
|
||
protected override Expression VisitUnary(UnaryExpression node) | ||
{ | ||
if (node.NodeType != ExpressionType.Not) | ||
{ | ||
throw new NotSupportedException($"The unary operator '{node.NodeType}' is not supported"); | ||
} | ||
|
||
_unarySpecified = true; | ||
|
||
return base.VisitUnary(node); // returning base because we want to continue further processing - ie subsequent call to VisitMethodCall | ||
} | ||
} | ||
} |
Oops, something went wrong.