Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Making a custom class IQueryable

Tags:

c#

linq

I have been working with the TFS API for VS2010 and had to query FieldCollection which I found isn't supported by LINQ so I wanted to create a custom class to make the Field and FieldCollection queryable by LINQ so I found a basic template and tried to implement it

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using Microsoft.TeamFoundation.WorkItemTracking.Client;

public class WorkItemFieldCollection : IQueryable<Field>, IQueryProvider
{
    private List<Field> _fieldList = new List<Field>();

    #region Constructors

    /// <summary>
    /// This constructor is called by the client to create the data source.
    /// </summary>
    public WorkItemFieldCollection(FieldCollection fieldCollection)
    {
        foreach (Field field in fieldCollection)
        {
            _fieldList.Add(field);
        }

    }

    #endregion Constructors

    #region IQueryable Members

    Type IQueryable.ElementType
    {
        get { return typeof(Field); }
    }

    System.Linq.Expressions.Expression IQueryable.Expression
    {
        get { return Expression.Constant(this); }
    }

    IQueryProvider IQueryable.Provider
    {
        get { return this; }
    }

    #endregion IQueryable Members

    #region IEnumerable<Field> Members

    IEnumerator<Field> IEnumerable<Field>.GetEnumerator()
    {
        return (this as IQueryable).Provider.Execute<IEnumerator<Field>>(_expression);
    }

    private IList<Field> _field = new List<Field>();
    private Expression _expression = null;

    #endregion IEnumerable<Field> Members

    #region IEnumerable Members

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return (IEnumerator<Field>)(this as IQueryable).GetEnumerator();
    }

    private void ProcessExpression(Expression expression)
    {
        if (expression.NodeType == ExpressionType.Equal)
        {
            ProcessEqualResult((BinaryExpression)expression);
        }
        if (expression is UnaryExpression)
        {
            UnaryExpression uExp = expression as UnaryExpression;
            ProcessExpression(uExp.Operand);
        }
        else if (expression is LambdaExpression)
        {
            ProcessExpression(((LambdaExpression)expression).Body);
        }
        else if (expression is ParameterExpression)
        {
            if (((ParameterExpression)expression).Type == typeof(Field))
            {
                _field = GetFields();
            }
        }
    }

    private void ProcessEqualResult(BinaryExpression expression)
    {
        if (expression.Right.NodeType == ExpressionType.Constant)
        {
            string name = (String)((ConstantExpression)expression.Right).Value;
            ProceesItem(name);
        }
    }

    private void ProceesItem(string name)
    {
        IList<Field> filtered = new List<Field>();

        foreach (Field field in GetFields())
        {
            if (string.Compare(field.Name, name, true) == 0)
            {
                filtered.Add(field);
            }
        }
        _field = filtered;
    }

    private object GetValue(BinaryExpression expression)
    {
        if (expression.Right.NodeType == ExpressionType.Constant)
        {
            return ((ConstantExpression)expression.Right).Value;
        }
        return null;
    }

    private IList<Field> GetFields()
    {
        return _fieldList;
    }

    #endregion IEnumerable Members

    #region IQueryProvider Members

    IQueryable<S> IQueryProvider.CreateQuery<S>(System.Linq.Expressions.Expression expression)
    {
        if (typeof(S) != typeof(Field))
            throw new Exception("Only " + typeof(Field).FullName + " objects are supported.");

        this._expression = expression;

        return (IQueryable<S>)this;
    }

    IQueryable IQueryProvider.CreateQuery(System.Linq.Expressions.Expression expression)
    {
        return (IQueryable<Field>)(this as IQueryProvider).CreateQuery<Field>(expression);
    }

    TResult IQueryProvider.Execute<TResult>(System.Linq.Expressions.Expression expression)
    {
        MethodCallExpression methodcall = _expression as MethodCallExpression;

        foreach (var param in methodcall.Arguments)
        {
            ProcessExpression(param);
        }
        return (TResult)_field.GetEnumerator();
    }

    object IQueryProvider.Execute(System.Linq.Expressions.Expression expression)
    {

        return (this as IQueryProvider).Execute<IEnumerator<Field>>(expression);
    }

    #endregion IQueryProvider Members
}

It appeared to compile and was recognized by LINQ but i keep getting an error in the CreateQuery method because it passes in string and not a field

    IQueryable<S> IQueryProvider.CreateQuery<S>(System.Linq.Expressions.Expression expression)
    {
        if (typeof(S) != typeof(Field))
            throw new Exception("Only " + typeof(Field).FullName + " objects are supported.");

        this._expression = expression;

        return (IQueryable<S>)this;
    }

here is the Linq query I use... columnFilterList is List and fields is my custom FieldCollection class see above.

  foreach (var name in columnFilterList)
   {
        var fieldName = (from x in fields where x.Name == name select x.Name).First
   }

....I sure it is a simple mistake...could someone tell me what I am doing wrong...thanks

like image 410
user2810977 Avatar asked Sep 24 '13 12:09

user2810977


2 Answers

If you want an object to be usable by LINQ, implement IEnumerable<T>. IQueryable<T> is overkill for LINQ to Objects. It is designed for converting the expressions into another form.

Or if you want, you can do this

FieldCollection someFieldCollection = ...
IEnumerable<Field> fields = someFieldCollections.Cast<Field>();
like image 178
Daniel A. White Avatar answered Sep 21 '22 06:09

Daniel A. White


In your case of wrapping and building a class around an existing IEnumerable Collection type i.e. List<Field>,

you might just use a set of "forward function" wrappers that expose the IQueryable<Field> interface:

public class WorkItemFieldCollection : IEnumerable<Field>, IQueryable<Field>
{
    ...
    #region Implementation of IQueryable<Field>

    public Type ElementType
    {
        get
        {
            return this._fieldList.AsQueryable().ElementType;
        }
    }

    public Expression Expression
    {
        get
        {
            return this._fieldList.AsQueryable().Expression;
        }
    }

    public IQueryProvider Provider
    {
        get
        {
            return this._fieldList.AsQueryable().Provider;
        }
    }

    #endregion
    ...
}

You can now directly query your WorkItemFieldCollection:

var workItemFieldCollection = new WorkItemFieldCollection(...);
var Fuzz = "someStringId";
var workFieldItem = workItemFieldCollection.FirstOrDefault( c => c.Buzz == Fuzz );
like image 27
Lorenz Lo Sauer Avatar answered Sep 20 '22 06:09

Lorenz Lo Sauer