Skip to content

Instantly share code, notes, and snippets.

@bjcull
Created January 4, 2018 10:30
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save bjcull/c53842df5056300859181fdf10cacab1 to your computer and use it in GitHub Desktop.
Save bjcull/c53842df5056300859181fdf10cacab1 to your computer and use it in GitHub Desktop.
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);
}
}
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;
}
}
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