user2810977 user2810977 - 3 months ago 27
C# Question

Making a custom class IQueryable

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

Answer

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>();
Comments