HiveBrain v1.2.0
Get Started
← Back to all entries
patterncsharpMinor

LINQ Provider: Supporting Projections

Submitted by: @import:stackexchange-codereview··
0
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:

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):

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.