Implementing a custom QueryProvider with in-memory query - c#

I'm trying to create a wrapper around QueryableBase and INhQueryProvider that would receive a collection in the constructor and query it in-memory instead of going to a database. This is so I can mock the behavior of NHibernate's ToFuture() and properly unit test my classes.
The problem is that I'm facing a stack overflow due to infinite recursion and I'm struggling to find the reason.
Here's my implementation:
public class NHibernateQueryableProxy<T> : QueryableBase<T>, IOrderedQueryable<T>
{
public NHibernateQueryableProxy(IQueryable<T> data) : base(new NhQueryProviderProxy<T>(data))
{
}
public NHibernateQueryableProxy(IQueryParser queryParser, IQueryExecutor executor) : base(queryParser, executor)
{
}
public NHibernateQueryableProxy(IQueryProvider provider) : base(provider)
{
}
public NHibernateQueryableProxy(IQueryProvider provider, Expression expression) : base(provider, expression)
{
}
public new IEnumerator<T> GetEnumerator()
{
return Provider.Execute<IEnumerable<T>>(Expression).GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
internal class NhQueryProviderProxy<T> : INhQueryProvider
{
private readonly IQueryProvider provider;
public NhQueryProviderProxy(IQueryable<T> data)
{
provider = data.AsQueryable().Provider;
}
public IQueryable CreateQuery(Expression expression)
{
return new NHibernateQueryableProxy<T>(this, expression);
}
public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
{
return new NHibernateQueryableProxy<TElement>(this, expression);
}
public object Execute(Expression expression)
{
return provider.Execute(expression);
}
public TResult Execute<TResult>(Expression expression)
{
return provider.Execute<TResult>(expression);
}
public object ExecuteFuture(Expression expression)
{
return provider.Execute(expression);
}
public void SetResultTransformerAndAdditionalCriteria(IQuery query, NhLinqExpression nhExpression, IDictionary<string, Tuple<object, IType>> parameters)
{
throw new NotImplementedException();
}
}
Edit: I've kind of figured out the problem. One of the arguments to expression is my custom queryable. When this expression is executed by the provider, it causes an infinite call loop between CreateQuery and Execute. Is it possible to change all the references to my custom queryable to the queryable wrapped by this class?

After a while I decided to give it another try and I guess I've managed to mock it. I didn't test it with real case scenarios but I don't think many tweaks will be necessary. Most of this code is either taken from or based on this tutorial. There are some caveats related to IEnumerable when dealing with those queries.
We need to implement QueryableBase since NHibernate asserts the type when using ToFuture.
public class NHibernateQueryableProxy<T> : QueryableBase<T>
{
public NHibernateQueryableProxy(IQueryable<T> data) : base(new NhQueryProviderProxy<T>(data))
{
}
public NHibernateQueryableProxy(IQueryProvider provider, Expression expression) : base(provider, expression)
{
}
}
Now we need to mock a QueryProvider since that's what LINQ queries depend on and it needs to implement INhQueryProvider because ToFuture() also uses it.
public class NhQueryProviderProxy<T> : INhQueryProvider
{
private readonly IQueryable<T> _data;
public NhQueryProviderProxy(IQueryable<T> data)
{
_data = data;
}
// These two CreateQuery methods get called by LINQ extension methods to build up the query
// and by ToFuture to return a queried collection and allow us to apply more filters
public IQueryable CreateQuery(Expression expression)
{
Type elementType = TypeSystem.GetElementType(expression.Type);
return (IQueryable)Activator.CreateInstance(typeof(NHibernateQueryableProxy<>)
.MakeGenericType(elementType), new object[] { this, expression });
}
public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
{
return new NHibernateQueryableProxy<TElement>(this, expression);
}
// Those two Execute methods are called by terminal methods like .ToList() and .ToArray()
public object Execute(Expression expression)
{
return ExecuteInMemoryQuery(expression, false);
}
public TResult Execute<TResult>(Expression expression)
{
bool IsEnumerable = typeof(TResult).Name == "IEnumerable`1";
return (TResult)ExecuteInMemoryQuery(expression, IsEnumerable);
}
public object ExecuteFuture(Expression expression)
{
// Here we need to return a NhQueryProviderProxy so we can add more queries
// to the queryable and use another ToFuture if desired
return CreateQuery(expression);
}
private object ExecuteInMemoryQuery(Expression expression, bool isEnumerable)
{
var newExpr = new ExpressionTreeModifier<T>(_data).Visit(expression);
if (isEnumerable)
{
return _data.Provider.CreateQuery(newExpr);
}
return _data.Provider.Execute(newExpr);
}
public void SetResultTransformerAndAdditionalCriteria(IQuery query, NhLinqExpression nhExpression, IDictionary<string, Tuple<object, IType>> parameters)
{
throw new NotImplementedException();
}
}
The expression tree visitor will change the type of the query for us:
internal class ExpressionTreeModifier<T> : ExpressionVisitor
{
private IQueryable<T> _queryableData;
internal ExpressionTreeModifier(IQueryable<T> queryableData)
{
_queryableData = queryableData;
}
protected override Expression VisitConstant(ConstantExpression c)
{
// Here the magic happens: the expression types are all NHibernateQueryableProxy,
// so we replace them by the correct ones
if (c.Type == typeof(NHibernateQueryableProxy<T>))
return Expression.Constant(_queryableData);
else
return c;
}
}
And we also need a helper (taken from the tutorial) to get the type being queried:
internal static class TypeSystem
{
internal static Type GetElementType(Type seqType)
{
Type ienum = FindIEnumerable(seqType);
if (ienum == null) return seqType;
return ienum.GetGenericArguments()[0];
}
private static Type FindIEnumerable(Type seqType)
{
if (seqType == null || seqType == typeof(string))
return null;
if (seqType.IsArray)
return typeof(IEnumerable<>).MakeGenericType(seqType.GetElementType());
if (seqType.IsGenericType)
{
foreach (Type arg in seqType.GetGenericArguments())
{
Type ienum = typeof(IEnumerable<>).MakeGenericType(arg);
if (ienum.IsAssignableFrom(seqType))
{
return ienum;
}
}
}
Type[] ifaces = seqType.GetInterfaces();
if (ifaces != null && ifaces.Length > 0)
{
foreach (Type iface in ifaces)
{
Type ienum = FindIEnumerable(iface);
if (ienum != null) return ienum;
}
}
if (seqType.BaseType != null && seqType.BaseType != typeof(object))
{
return FindIEnumerable(seqType.BaseType);
}
return null;
}
}
To test the above code, I ran the following snippet:
var arr = new NHibernateQueryableProxy<int>(Enumerable.Range(1, 10000).AsQueryable());
var fluentQuery = arr.Where(x => x > 1 && x < 4321443)
.Take(1000)
.Skip(3)
.Union(new[] { 4235, 24543, 52 })
.GroupBy(x => x.ToString().Length)
.ToFuture()
.ToList();
var linqQuery = (from n in arr
where n > 40 && n < 50
select n.ToString())
.ToFuture()
.ToList();
As I said, no complex scenarios were tested but I guess only a few tweaks will be necessary for real-world usages.

Related

Wire up ExpressionVisitor with EF Core Include

I have an ExpressionVisitor which I add to EF Core's IQueryable<T>. Everything works fine except the Include methods. Probably because they enforce your IQueryable<T>.Provider to be an EntityQueryProvider.
Whenever I try to Include now it results in multiple queries which in turn results in the error "A second operation started on this context before a previous operation completed. Any instance members are not guaranteed to be thread safe.".
How can I wire up my ExpressionVisitor so it still works with EF Core's Include functionality?
My issue is similar to this one except for EF Core instead of EF.
I hook up my ExpressionVisitor by calling it on the DbSet:
return new Translator<TEntity>(
_dbSet
.AsNoTracking());
This is my Translator class:
public class Translator<T> : IOrderedQueryable<T>
{
private readonly Expression _expression;
private readonly TranslatorProvider<T> _provider;
public Translator(IQueryable source)
{
_expression = Expression.Constant(this);
_provider = new TranslatorProvider<T>(source);
}
public Translator(IQueryable source, Expression expression)
{
if (expression == null)
{
throw new ArgumentNullException(nameof(expression));
}
_expression = expression;
_provider = new TranslatorProvider<T>(source);
}
public IEnumerator<T> GetEnumerator()
{
return ((IEnumerable<T>)_provider.ExecuteEnumerable(_expression)).GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return _provider.ExecuteEnumerable(_expression).GetEnumerator();
}
public Type ElementType => typeof(T);
public Expression Expression => _expression;
public IQueryProvider Provider => _provider;
}
And this is my TranslatorProvider<T> class (I've taken out the non-relevant Visit methods to shorten the post):
public class TranslatorProvider<T> : ExpressionVisitor, IQueryProvider
{
private readonly IQueryable _source;
public TranslatorProvider(IQueryable source)
{
if (source == null)
{
throw new ArgumentNullException(nameof(source));
}
_source = source;
}
public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
{
if (expression == null)
{
throw new ArgumentNullException(nameof(expression));
}
return new Translator<TElement>(_source, expression);
}
public IQueryable CreateQuery(Expression expression)
{
if (expression == null)
{
throw new ArgumentNullException(nameof(expression));
}
var elementType = expression.Type.GetGenericArguments().First();
var result = (IQueryable) Activator.CreateInstance(typeof(Translator<>).MakeGenericType(elementType),
_source, expression);
return result;
}
public TResult Execute<TResult>(Expression expression)
{
if (expression == null)
{
throw new ArgumentNullException(nameof(expression));
}
var result = (this as IQueryProvider).Execute(expression);
return (TResult) result;
}
public object Execute(Expression expression)
{
if (expression == null)
{
throw new ArgumentNullException(nameof(expression));
}
var translated = Visit(expression);
return _source.Provider.Execute(translated);
}
internal IEnumerable ExecuteEnumerable(Expression expression)
{
if (expression == null)
{
throw new ArgumentNullException(nameof(expression));
}
var translated = Visit(expression);
return _source.Provider.CreateQuery(translated);
}
protected override Expression VisitConstant(ConstantExpression node)
{
if (node.Type == typeof(Translator<T>))
{
return _source.Expression;
}
else
{
return base.VisitConstant(node);
}
}
}
Update (EF Core 3.x):
The internal query pipeline infrastructure has changed. The new query expression preprocessing extension point is QueryTranslationPreprocessor class - Process method. Plugging it in requires replacing the IQueryTranslationPreprocessorFactory. e.g.
using System.Linq.Expressions;
namespace Microsoft.EntityFrameworkCore.Query
{
public class CustomQueryTranslationPreprocessor : RelationalQueryTranslationPreprocessor
{
public CustomQueryTranslationPreprocessor(QueryTranslationPreprocessorDependencies dependencies, RelationalQueryTranslationPreprocessorDependencies relationalDependencies, QueryCompilationContext queryCompilationContext)
: base(dependencies, relationalDependencies, queryCompilationContext) { }
public override Expression Process(Expression query) => base.Process(Preprocess(query));
private Expression Preprocess(Expression query)
{
// query = new YourExpressionVisitor().Visit(query);
return query;
}
}
public class CustomQueryTranslationPreprocessorFactory : IQueryTranslationPreprocessorFactory
{
public CustomQueryTranslationPreprocessorFactory(QueryTranslationPreprocessorDependencies dependencies, RelationalQueryTranslationPreprocessorDependencies relationalDependencies)
{
Dependencies = dependencies;
RelationalDependencies = relationalDependencies;
}
protected QueryTranslationPreprocessorDependencies Dependencies { get; }
protected RelationalQueryTranslationPreprocessorDependencies RelationalDependencies;
public QueryTranslationPreprocessor Create(QueryCompilationContext queryCompilationContext)
=> new CustomQueryTranslationPreprocessor(Dependencies, RelationalDependencies, queryCompilationContext);
}
}
and
optionsBuilder.ReplaceService<IQueryTranslationPreprocessorFactory, CustomQueryTranslationPreprocessorFactory>();
Original:
Apparently custom query providers don't fit in the current EF Core queryable pipeline, since several methods (Include, AsNoTracking etc.) require provider to be EntityQueryProvider.
At the time of writing (EF Core 2.1.2), the query translation process involves several services - IAsyncQueryProvider, IQueryCompiler, IQueryModelGenerator and more. All they are replaceable, but the easiest place for interception I see is the IQueryModelGenerator service - ParseQuery method.
So, forget about custom IQueryable / IQueryProvider implementation, use the following class and plug your expression visitor inside Preprocess method:
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore.Query.Internal;
using Remotion.Linq;
using Remotion.Linq.Parsing.ExpressionVisitors.TreeEvaluation;
class CustomQueryModelGenerator : QueryModelGenerator
{
public CustomQueryModelGenerator(INodeTypeProviderFactory nodeTypeProviderFactory, IEvaluatableExpressionFilter evaluatableExpressionFilter, ICurrentDbContext currentDbContext)
: base(nodeTypeProviderFactory, evaluatableExpressionFilter, currentDbContext)
{ }
public override QueryModel ParseQuery(Expression query) => base.ParseQuery(Preprocess(query));
private Expression Preprocess(Expression query)
{
// return new YourExpressionVisitor().Visit(query);
return query;
}
}
and replace the corresponding EF Core service inside your derived context OnConfiguring override:
optionsBuilder.ReplaceService<IQueryModelGenerator, CustomQueryModelGenerator>();
The drawback is that this is using EF Core "internal" stuff, so you should keep monitoring for changes in the future updates.

ObjectSet wrapper not working with linqToEntities subquery

for access control purposes in a intensive DB use system I had to implement an objectset wrapper, where the AC will be checked.
The main objective is make this change preserving the existing code for database access, that is implemented with linq to entities all over the classes (there is no centralized layer for database).
The ObjectSetWrapper created is like that:
public class ObjectSetWrapper<TEntity> : IQueryable<TEntity> where TEntity : EntityObject
{
private IQueryable<TEntity> QueryableModel;
private ObjectSet<TEntity> ObjectSet;
public ObjectSetWrapper(ObjectSet<TEntity> objectSetModels)
{
this.QueryableModel = objectSetModels;
this.ObjectSet = objectSetModels;
}
public ObjectQuery<TEntity> Include(string path)
{
return this.ObjectSet.Include(path);
}
public void DeleteObject(TEntity #object)
{
this.ObjectSet.DeleteObject(#object);
}
public void AddObject(TEntity #object)
{
this.ObjectSet.AddObject(#object);
}
public IEnumerator<TEntity> GetEnumerator()
{
return QueryableModel.GetEnumerator();
}
public Type ElementType
{
get { return typeof(TEntity); }
}
public System.Linq.Expressions.Expression Expression
{
get { return this.QueryableModel.Expression; }
}
public IQueryProvider Provider
{
get { return this.QueryableModel.Provider; }
}
public void Attach(TEntity entity)
{
this.ObjectSet.Attach(entity);
}
public void Detach(TEntity entity)
{
this.ObjectSet.Detach(entity);
}
IEnumerator IEnumerable.GetEnumerator()
{
return this.QueryableModel.GetEnumerator();
}
}
It's really simple and works for simple queries, like that:
//db.Product is ObjectSetWrapper<Product>
var query = (from item in db.Product where item.Quantity > 0 select new { item.Id, item.Name, item.Value });
var itensList = query.Take(10).ToList();
But when I have subqueries like that:
//db.Product is ObjectSetWrapper<Product>
var query = (from item in db.Product
select new
{
Id = item.Id,
Name = item.Name,
SalesQuantity = (from sale in db.Sale where sale.ProductId == item.Id select sale.Id).Count()
}).OrderByDescending(x => x.SalesQuantity);
var productsList = query.Take(10).ToList();
I get NotSupportedException, saying I can't create a constant value of my inner query entity type:
Unable to create a constant value of type 'MyNamespace.Model.Sale'.
Only primitive types or enumeration types are supported in this
context.
How can I get my queries working? I don't really need to make my wrapper an ObjectSet type, I just need to use it in queries.
Updated
I have changed my class signature. Now it's also implementing IObjectSet<>, but I'm getting the same NotSupportedException:
public class ObjectSetWrapper<TEntity> : IQueryable<TEntity>, IObjectSet<TEntity> where TEntity : EntityObject
EDIT:
The problem is that the following LINQ construction is translated into LINQ expression containing your custom class inside (ObjectSetWrapper).
var query = (from item in db.Product
select new
{
Id = item.Id,
Name = item.Name,
SalesQuantity = (from sale in db.Sale where sale.ProductId == item.Id select sale.Id).Count()
}).OrderByDescending(x => x.SalesQuantity);
LINQ to Entities tries to convert this expression into SQL statement, but it has no idea how to deal with the custom classes (as well as custom methods).
The solution in such cases is to replace IQueryProvider with the custom one, which should intercept the query execution and translate LINQ expression, containing custom classes/methods into valid LINQ to Entities expression (which operates with entities and object sets).
Expression conversion is performed using the class, derived from ExpressionVisitor, which performs expression tree traversal, replacing relevant nodes, to the nodes which can be accepted by LINQ to Entities
Part 1 - IQueryWrapper
// Query wrapper interface - holds and underlying query
interface IQueryWrapper
{
IQueryable UnderlyingQueryable { get; }
}
Part 2 - Abstract QueryWrapperBase (not generic)
abstract class QueryWrapperBase : IQueryProvider, IQueryWrapper
{
public IQueryable UnderlyingQueryable { get; private set; }
class ObjectWrapperReplacer : ExpressionVisitor
{
public override Expression Visit(Expression node)
{
if (node == null || !typeof(IQueryWrapper).IsAssignableFrom(node.Type)) return base.Visit(node);
var wrapper = EvaluateExpression<IQueryWrapper>(node);
return Expression.Constant(wrapper.UnderlyingQueryable);
}
public static Expression FixExpression(Expression expression)
{
var replacer = new ObjectWrapperReplacer();
return replacer.Visit(expression);
}
private T EvaluateExpression<T>(Expression expression)
{
if (expression is ConstantExpression) return (T)((ConstantExpression)expression).Value;
var lambda = Expression.Lambda(expression);
return (T)lambda.Compile().DynamicInvoke();
}
}
protected QueryWrapperBase(IQueryable underlyingQueryable)
{
UnderlyingQueryable = underlyingQueryable;
}
public abstract IQueryable<TElement> CreateQuery<TElement>(Expression expression);
public abstract IQueryable CreateQuery(Expression expression);
public TResult Execute<TResult>(Expression expression)
{
return (TResult)Execute(expression);
}
public object Execute(Expression expression)
{
expression = ObjectWrapperReplacer.FixExpression(expression);
return typeof(IQueryable).IsAssignableFrom(expression.Type)
? ExecuteQueryable(expression)
: ExecuteNonQueryable(expression);
}
protected object ExecuteNonQueryable(Expression expression)
{
return UnderlyingQueryable.Provider.Execute(expression);
}
protected IQueryable ExecuteQueryable(Expression expression)
{
return UnderlyingQueryable.Provider.CreateQuery(expression);
}
}
Part 3 - Generic QueryWrapper<TElement>
class QueryWrapper<TElement> : QueryWrapperBase, IOrderedQueryable<TElement>
{
private static readonly MethodInfo MethodCreateQueryDef = GetMethodDefinition(q => q.CreateQuery<object>(null));
public QueryWrapper(IQueryable<TElement> underlyingQueryable) : this(null, underlyingQueryable)
{
}
protected QueryWrapper(Expression expression, IQueryable underlyingQueryable) : base(underlyingQueryable)
{
Expression = expression ?? Expression.Constant(this);
}
public virtual IEnumerator<TElement> GetEnumerator()
{
return ((IEnumerable<TElement>)Execute<IEnumerable>(Expression)).GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public Expression Expression { get; private set; }
public Type ElementType
{
get { return typeof(TElement); }
}
public IQueryProvider Provider
{
get { return this; }
}
public override IQueryable CreateQuery(Expression expression)
{
var expressionType = expression.Type;
var elementType = expressionType
.GetInterfaces()
.Single(t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(IEnumerable<>))
.GetGenericArguments()
.Single();
var createQueryMethod = MethodCreateQueryDef.MakeGenericMethod(elementType);
return (IQueryable)createQueryMethod.Invoke(this, new object[] { expression });
}
public override IQueryable<TNewElement> CreateQuery<TNewElement>(Expression expression)
{
return new QueryWrapper<TNewElement>(expression, UnderlyingQueryable);
}
private static MethodInfo GetMethodDefinition(Expression<Action<QueryWrapper<TElement>>> methodSelector)
{
var methodCallExpression = (MethodCallExpression)methodSelector.Body;
return methodCallExpression.Method.GetGenericMethodDefinition();
}
}
Part 4 - finally your ObjectSetWrapper
public class ObjectSetWrapper<TEntity> : IQueryable<TEntity>, IQueryWrapper where TEntity : class
{
private IQueryable<TEntity> QueryableModel;
private ObjectSet<TEntity> ObjectSet;
public ObjectSetWrapper(ObjectSet<TEntity> objectSetModels)
{
this.QueryableModel = new QueryWrapper<TEntity>(objectSetModels);
this.ObjectSet = objectSetModels;
}
public ObjectQuery<TEntity> Include(string path)
{
return this.ObjectSet.Include(path);
}
public void DeleteObject(TEntity #object)
{
this.ObjectSet.DeleteObject(#object);
}
public void AddObject(TEntity #object)
{
this.ObjectSet.AddObject(#object);
}
public IEnumerator<TEntity> GetEnumerator()
{
return QueryableModel.GetEnumerator();
}
public Type ElementType
{
get { return typeof(TEntity); }
}
public System.Linq.Expressions.Expression Expression
{
get { return this.QueryableModel.Expression; }
}
public IQueryProvider Provider
{
get { return this.QueryableModel.Provider; }
}
public void Attach(TEntity entity)
{
this.ObjectSet.Attach(entity);
}
public void Detach(TEntity entity)
{
this.ObjectSet.Detach(entity);
}
IEnumerator IEnumerable.GetEnumerator()
{
return this.QueryableModel.GetEnumerator();
}
IQueryable IQueryWrapper.UnderlyingQueryable
{
get { return this.ObjectSet; }
}
}
Your inner query fails because you are referencing another dataset when you should be traversing foreign keys:
SalesQuantity = item.Sales.Count()

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

Best way to concat strings and numbers in SQL server using Entity Framework 5?

For some reason Microsoft decided to not support simple concat in EF5.
e.g.
Select(foo => new
{
someProp = "hello" + foo.id + "/" + foo.bar
}
This will throw if foo.id or foo.bar are numbers.
The workaround I've found is apparently this pretty peice of code:
Select(foo => new
{
someProp = "hello" +
SqlFunctions.StringConvert((double?)foo.id).Trim() +
"/" +
SqlFunctions.StringConvert((double?)foo.bar).Trim()
}
Which works fine, but is just horrid to look at.
So, is there some decent way to accomplish this with cleaner code?
I'm NOT interested in doing this client side, so no .AsEnumerable() answers please.
For those interested.
I got so pissed with the lack of this feature that I implemented it myself using an ExpressionVisitor.
You can now write code like the one in the original question.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data.Objects.SqlClient;
using System.Linq;
using System.Linq.Expressions;
namespace Crawlr.Web.Code
{
public static class ObjectSetExExtensions
{
public static ObjectSetEx<T> Extend<T>(this IQueryable<T> self) where T : class
{
return new ObjectSetEx<T>(self);
}
}
public class ObjectSetEx<T> : IOrderedQueryable<T>
{
private readonly QueryProviderEx provider;
private readonly IQueryable<T> source;
public ObjectSetEx(IQueryable<T> source)
{
this.source = source;
provider = new QueryProviderEx(this.source.Provider);
}
#region IQueryableEx<T> Members
public IEnumerator<T> GetEnumerator()
{
return source.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return source.GetEnumerator();
}
public Type ElementType
{
get { return source.ElementType; }
}
public Expression Expression
{
get { return source.Expression; }
}
public IQueryProvider Provider
{
get { return provider; }
}
#endregion
}
public class QueryProviderEx : IQueryProvider
{
private readonly IQueryProvider source;
public QueryProviderEx(IQueryProvider source)
{
this.source = source;
}
#region IQueryProvider Members
public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
{
Expression newExpression = ExpressionReWriterVisitor.Default.Visit(expression);
IQueryable<TElement> query = source.CreateQuery<TElement>(newExpression);
return new ObjectSetEx<TElement>(query);
}
public IQueryable CreateQuery(Expression expression)
{
Expression newExpression = ExpressionReWriterVisitor.Default.Visit(expression);
IQueryable query = source.CreateQuery(newExpression);
return query;
}
public TResult Execute<TResult>(Expression expression)
{
Expression newExpression = ExpressionReWriterVisitor.Default.Visit(expression);
return source.Execute<TResult>(newExpression);
}
public object Execute(Expression expression)
{
Expression newExpression = ExpressionReWriterVisitor.Default.Visit(expression);
return source.Execute(newExpression);
}
#endregion
}
public class ExpressionReWriterVisitor : ExpressionVisitor
{
public static readonly ExpressionReWriterVisitor Default = new ExpressionReWriterVisitor();
protected override Expression VisitUnary(UnaryExpression node)
{
if (node.NodeType == ExpressionType.Convert && node.Operand.Type == typeof(int) && node.Type==typeof(object))
{
var operand = node.Operand;
var stringConvertMethod = typeof(SqlFunctions).GetMethod("StringConvert", new Type[] { typeof(double?) });
var trimMethod = typeof(string).GetMethod("Trim",new Type[] {});
var dOperand = Expression.Convert(operand, typeof(double?));
return Expression.Call(Expression.Call(stringConvertMethod, dOperand),trimMethod);
}
return base.VisitUnary(node);
}
}
}
usage:
var res = model
.FooSet
.Extend() //<- applies the magic
.Select(foo => new
{
someProp = "hello" + foo.id + "/" + foo.bar
}

Repository Pattern with Caching and SqlMethods

I have a repository interface as below:
public interface IDataContext<TId> : IDisposable
{
IQueryable<T> Repository<T>() where T : class, IEntity<TId>;
T FindById<T>(TId id) where T : class, IEntity<TId>;
void Insert<T>(T item) where T : class, IEntity<TId>;
void Delete<T>(T item) where T : class, IEntity<TId>;
void Commit();
}
Note that Repository<T> returns an IQueryable<T>.
I have a class that can wrap a LinqToSQL data context, with the Repository<T> method as below:
public IQueryable<T> Repository<T>() where T : class, IEntity<int>
{
ITable table = _context.GetTable(GetEntityType<T>());
return table.Cast<T>();
}
This works fine, I can do something like
new Repository(new SQLDataContext())
.Repository<MyEntity>().Where(e => SqlMethods.Like(e.Id, "123%");
Now I've started thinking about caching but I have a problem.
I've created a class that wraps and implements an IDataContext<TId> that will cache results from calls to Repository<T> in memory. Something like the below:
public IQueryable<T> Repository<T>() where T : class, IEntity<TId>
{
// Actual caching logic here.....
return _CachedEntities[typeof(T)].OfType<T>().AsQueryable<T>();
}
The issue I have is that now the IQueryable<T> I return is in-memory, not translated to SQL, so I get an exception about using SqlMethods.Like.
TL;DR: So, how can I create my caching repository wrapper in such a way that the calling classes don't need to worry about whether the IDataContext<T> it's dealing with is an in-memory repository (i.e. the caching one) or a normal LinqToSQL repository?
It's possible, you need to write custom IQueryProvider and IQueryable<T>:
public static class MySqlMethods
{
public static bool Like(string matchExpression, string pattern)
{
//Your implementation
return true;
}
}
public class ChangeMethodsVisitor : ExpressionVisitor
{
//This method will change SqlMethods to MySqlMethods.
protected override Expression VisitMethodCall(MethodCallExpression node)
{
if (node.Method.DeclaringType == typeof(SqlMethods))
{
//Getting method from MySqlMethods class.
var method = typeof(MySqlMethods).GetMethod(node.Method.Name,
node.Method.GetParameters()
.Select(info => info.ParameterType)
.ToArray());
return Expression.Call(method, node.Arguments);
}
return base.VisitMethodCall(node);
}
}
public class MyQueryProvider : IQueryProvider
{
private static readonly ExpressionVisitor ExpressionVisitor = new ChangeMethodsVisitor();
private readonly IQueryProvider _queryProvider;
public MyQueryProvider(IQueryProvider queryProvider)
{
_queryProvider = queryProvider;
}
public IQueryable CreateQuery(Expression expression)
{
expression = ExpressionVisitor.Visit(expression);
var queryable = _queryProvider.CreateQuery(expression);
//Wrap queryable to MyQuery class.
var makeGenericType = typeof(MyQuery<>).MakeGenericType(queryable.ElementType);
return (IQueryable)makeGenericType.GetConstructor(new[] { typeof(IQueryable<>).MakeGenericType(queryable.ElementType) })
.Invoke(new object[] { queryable });
}
public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
{
expression = ExpressionVisitor.Visit(expression);
//Wrap queryable to MyQuery class.
var queryable = _queryProvider.CreateQuery<TElement>(expression);
return new MyQuery<TElement>(queryable);
}
public object Execute(Expression expression)
{
expression = ExpressionVisitor.Visit(expression);
return _queryProvider.Execute(expression);
}
public TResult Execute<TResult>(Expression expression)
{
expression = ExpressionVisitor.Visit(expression);
return _queryProvider.Execute<TResult>(expression);
}
}
public class MyQuery<T> : IOrderedQueryable<T>
{
private readonly IQueryable<T> _queryable;
public MyQuery(IQueryable<T> queryable)
{
_queryable = queryable;
Provider = new MyQueryProvider(_queryable.Provider);
}
public MyQuery(IEnumerable<T> enumerable)
: this(enumerable.AsQueryable())
{
}
public IEnumerator<T> GetEnumerator()
{
return _queryable.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public Expression Expression
{
get { return _queryable.Expression; }
}
public Type ElementType
{
get { return _queryable.ElementType; }
}
public IQueryProvider Provider { get; private set; }
}
And then you can use it:
var list = new List<string>(){"test", "test1"};
var myQuery = new MyQuery<string>(list);
var queryable = myQuery.Where(s => SqlMethods.Like(s, "123%")).ToArray();

Categories

Resources