Created
January 4, 2018 10:30
-
-
Save bjcull/c53842df5056300859181fdf10cacab1 to your computer and use it in GitHub Desktop.
See https://github.com/bjcull/LinqExpander for full code
This file contains hidden or 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
public static class AsExpandableExtension | |
{ | |
public static IQueryable<T> AsExpandable<T>(this IQueryable<T> source) | |
{ | |
if (source is ExpandableQuery<T>) | |
{ | |
return (ExpandableQuery<T>)source; | |
} | |
return new ExtendableQueryProvider(source.Provider).CreateQuery<T>(source.Expression); | |
} | |
} |
This file contains hidden or 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
internal class ExtendableQueryProvider : IAsyncQueryProvider | |
{ | |
private readonly IQueryProvider _underlyingQueryProvider; | |
private ExtendableQueryProvider() | |
{ | |
} | |
internal ExtendableQueryProvider(IQueryProvider underlyingQueryProvider) | |
{ | |
_underlyingQueryProvider = underlyingQueryProvider; | |
} | |
public IQueryable<TElement> CreateQuery<TElement>(Expression expression) | |
{ | |
return new ExpandableQuery<TElement>(this, expression); | |
} | |
public IQueryable CreateQuery(Expression expression) | |
{ | |
Type elementType = expression.Type.GetElementType(); | |
try | |
{ | |
return (IQueryable)Activator.CreateInstance(typeof(ExpandableQuery<>).MakeGenericType(elementType), new object[] { this, expression }); | |
} | |
catch (System.Reflection.TargetInvocationException tie) | |
{ | |
throw tie.InnerException; | |
} | |
} | |
internal IEnumerable<T> ExecuteQuery<T>(Expression expression) | |
{ | |
return _underlyingQueryProvider.CreateQuery<T>(Visit(expression)).AsEnumerable(); | |
} | |
public TResult Execute<TResult>(Expression expression) | |
{ | |
return _underlyingQueryProvider.Execute<TResult>(Visit(expression)); | |
} | |
public object Execute(Expression expression) | |
{ | |
return _underlyingQueryProvider.Execute(Visit(expression)); | |
} | |
public IAsyncEnumerable<TResult> ExecuteAsync<TResult>(Expression expression) | |
{ | |
return ((IAsyncQueryProvider)_underlyingQueryProvider).ExecuteAsync<TResult>(Visit(expression)); | |
} | |
public Task<TResult> ExecuteAsync<TResult>(Expression expression, CancellationToken cancellationToken) | |
{ | |
return ((IAsyncQueryProvider)_underlyingQueryProvider).ExecuteAsync<TResult>(Visit(expression), cancellationToken); | |
} | |
private Expression Visit(Expression exp) | |
{ | |
ExpandableVisitor vstr = new ExpandableVisitor(_underlyingQueryProvider); | |
Expression visitedExp = vstr.Visit(exp); | |
return visitedExp; | |
} | |
} |
This file contains hidden or 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
internal class ExpandableVisitor : ExpressionVisitor | |
{ | |
private readonly IQueryProvider _provider; | |
private readonly Dictionary<ParameterExpression, Expression> _replacements = new Dictionary<ParameterExpression, Expression>(); | |
internal ExpandableVisitor(IQueryProvider provider) | |
{ | |
_provider = provider; | |
} | |
protected override Expression VisitMethodCall(MethodCallExpression node) | |
{ | |
bool expandNode = node.Method.GetCustomAttributes(typeof(ExpandQueryableAttribute), false).Any(); | |
if (expandNode && node.Method.IsStatic) | |
{ | |
object[] args = new object[node.Arguments.Count]; | |
args[0] = _provider.CreateQuery(node.Arguments[0]); | |
for (int i = 1; i < node.Arguments.Count; i++) | |
{ | |
Expression arg = node.Arguments[i]; | |
args[i] = (arg.NodeType == ExpressionType.Constant) ? ((ConstantExpression)arg).Value : arg; | |
} | |
return Visit(((IQueryable)node.Method.Invoke(null, args)).Expression); | |
} | |
var replaceNodeAttribute = node.Method.GetCustomAttributes(typeof(ReplaceWithExpressionAttribute), false).Cast<ReplaceWithExpressionAttribute>().FirstOrDefault(); | |
if (replaceNodeAttribute != null && node.Method.IsStatic) | |
{ | |
if (!string.IsNullOrEmpty(replaceNodeAttribute.MethodName)) | |
{ | |
var methods = node.Method.DeclaringType.GetRuntimeMethods(); | |
var replaceWith = methods.First(x => x.Name == replaceNodeAttribute.MethodName).Invoke(null, null); | |
if (replaceWith is LambdaExpression) | |
{ | |
RegisterReplacementParameters(node.Arguments.ToArray(), replaceWith as LambdaExpression); | |
return Visit((replaceWith as LambdaExpression).Body); | |
} | |
} | |
if (!string.IsNullOrEmpty(replaceNodeAttribute.PropertyName)) | |
{ | |
var properties = node.Method.DeclaringType.GetRuntimeProperties(); | |
var replaceWith = properties.First(x => x.Name == replaceNodeAttribute.PropertyName).GetValue(null); | |
if (replaceWith is LambdaExpression) | |
{ | |
RegisterReplacementParameters(node.Arguments.ToArray(), replaceWith as LambdaExpression); | |
return Visit((replaceWith as LambdaExpression).Body); | |
} | |
} | |
} | |
return base.VisitMethodCall(node); | |
} | |
protected override Expression VisitParameter(ParameterExpression node) | |
{ | |
Expression replacement; | |
if (_replacements.TryGetValue(node, out replacement)) | |
return Visit(replacement); | |
return base.VisitParameter(node); | |
} | |
private void RegisterReplacementParameters(Expression[] parameterValues, LambdaExpression expressionToVisit) | |
{ | |
if (parameterValues.Length != expressionToVisit.Parameters.Count) | |
throw new ArgumentException(string.Format("The parameter values count ({0}) does not match the expression parameter count ({1})", parameterValues.Length, expressionToVisit.Parameters.Count)); | |
foreach (var x in expressionToVisit.Parameters.Select((p, idx) => new { Index = idx, Parameter = p })) | |
{ | |
if (_replacements.ContainsKey(x.Parameter)) | |
{ | |
throw new Exception("Parameter already registered, this shouldn't happen."); | |
} | |
_replacements.Add(x.Parameter, parameterValues[x.Index]); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment