patterncsharpMinor
LINQ Provider: Supporting Projections
Viewed 0 times
projectionslinqprovidersupporting
Problem
Up until recently, my LINQ-to-Sage provider didn't support projections, so the client code had to explicitly "transfer" to LINQ-to-Objects, like this:
Now, with a bit of help from Stack Overflow, I was able to modify my
Or even this:
Under the hood it's still LINQ-to-Objects handling it. Here's the
```
public class SageQueryProvider : IQueryProvider
where TEntity : EntityBase
{
private readonly IView _view;
private readonly SageContextBase _context;
public SageQueryProvider(IView view, SageContextBase context)
{
_view = view;
_context = context;
}
public IQueryable CreateQuery(Expression expression)
{
var elementType = TypeSystem.GetElementType(expression.Type);
try
{
return (IQueryable)Activator.CreateInstance(typeof (ViewSet).MakeGenericType(elementType), _view, this, expression, _context);
}
catch (TargetInvocationException exception)
{
throw exception.InnerException;
}
}
public IQueryable CreateQuery(Expression expression)
{
var elementType = TypeSystem.GetElementType(expression.Type);
if (elementType == typeof(EntityBase))
{
Debug.Assert(elementType == typeof (TResult));
return (IQueryable)Activator.CreateInstance(typeof(ViewSet<>).MakeGenericType(elementType), _view, this, expression, _context);
}
var methodCallExpression = expression as MethodCallExpression;
if(methodCallExpression != null && methodCallExpression.Method.Name == "Select")
{
return (IQueryable)Execute(methodCallExpression);
}
throw new NotSupportedExcep
var vendorCodes = context.Vendors.ToList().Select(e => e.Code);Now, with a bit of help from Stack Overflow, I was able to modify my
IQueryProvider implementation to support this:var vendorCodes = context.Vendors.Select(e => e.Code);Or even this:
var vendors = context.Vendors.Select(e => new { e.Code, e.Name });Under the hood it's still LINQ-to-Objects handling it. Here's the
IQueryProvider implementation:```
public class SageQueryProvider : IQueryProvider
where TEntity : EntityBase
{
private readonly IView _view;
private readonly SageContextBase _context;
public SageQueryProvider(IView view, SageContextBase context)
{
_view = view;
_context = context;
}
public IQueryable CreateQuery(Expression expression)
{
var elementType = TypeSystem.GetElementType(expression.Type);
try
{
return (IQueryable)Activator.CreateInstance(typeof (ViewSet).MakeGenericType(elementType), _view, this, expression, _context);
}
catch (TargetInvocationException exception)
{
throw exception.InnerException;
}
}
public IQueryable CreateQuery(Expression expression)
{
var elementType = TypeSystem.GetElementType(expression.Type);
if (elementType == typeof(EntityBase))
{
Debug.Assert(elementType == typeof (TResult));
return (IQueryable)Activator.CreateInstance(typeof(ViewSet<>).MakeGenericType(elementType), _view, this, expression, _context);
}
var methodCallExpression = expression as MethodCallExpression;
if(methodCallExpression != null && methodCallExpression.Method.Name == "Select")
{
return (IQueryable)Execute(methodCallExpression);
}
throw new NotSupportedExcep
Solution
One of the things I think when I see a large case statement is would this be better off as some kind of lookup table. I think yours might have some scope for doing this since they all seem to do some processing on a viewSets and a filter. Using a simple lookup table to convert the string "Where", "Single" etc into a method call would allow you to separate the logic out a bit more. So, for example you could do something like this (better naming is left as an exercise for the reader):
This would allow you to replace the large case statement in your filter logic with:
public static class FilterExpressionHelper
{
readonly static Dictionary _methods;
static FilterExpressionHelper()
{
_methods = new Dictionary();
foreach(var methodInfo in typeof(FilterExpressionHelper).GetMethods(System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public).Where(x=>x.Name != "Execute"))
{
_methods.Add(methodInfo.Name, methodInfo);
}
}
public static object Execute(string methodName, ViewSet view, string filter)
{
if(_methods.ContainsKey(methodName))
return _methods[methodName].MakeGenericMethod(typeof(T)).Invoke(null, new object [] { view, filter });
throw new NotSupportedException($"Method '{methodName}' is not currently supported by this provider.");
}
public static object Where(ViewSet viewSet, string filter)
{
return viewSet.Select(filter);
}
public static object Single(ViewSet viewSet, string filter)
{
var singleResult = viewSet.SingleOrDefault(filter);
if (singleResult == null)
{
throw new InvalidOperationException("Sequence contains more than one element.");
}
return singleResult;
}
public static object SingleOrDefault(ViewSet viewSet, string filter)
{
return viewSet.SingleOrDefault(filter);
}
// etc
}This would allow you to replace the large case statement in your filter logic with:
FilterExpressionHelper.Execute(filterExpression.Method.Name, viewSet, filter);Code Snippets
public static class FilterExpressionHelper
{
readonly static Dictionary<string, MethodInfo> _methods;
static FilterExpressionHelper()
{
_methods = new Dictionary<string, MethodInfo>();
foreach(var methodInfo in typeof(FilterExpressionHelper).GetMethods(System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public).Where(x=>x.Name != "Execute"))
{
_methods.Add(methodInfo.Name, methodInfo);
}
}
public static object Execute<T>(string methodName, ViewSet<T> view, string filter)
{
if(_methods.ContainsKey(methodName))
return _methods[methodName].MakeGenericMethod(typeof(T)).Invoke(null, new object [] { view, filter });
throw new NotSupportedException($"Method '{methodName}' is not currently supported by this provider.");
}
public static object Where<T>(ViewSet<T> viewSet, string filter)
{
return viewSet.Select(filter);
}
public static object Single<T>(ViewSet<T> viewSet, string filter)
{
var singleResult = viewSet.SingleOrDefault(filter);
if (singleResult == null)
{
throw new InvalidOperationException("Sequence contains more than one element.");
}
return singleResult;
}
public static object SingleOrDefault<T>(ViewSet<T> viewSet, string filter)
{
return viewSet.SingleOrDefault(filter);
}
// etc
}FilterExpressionHelper.Execute(filterExpression.Method.Name, viewSet, filter);Context
StackExchange Code Review Q#132199, answer score: 3
Revisions (0)
No revisions yet.