Custom IQueryProvider that returns to LinqToObjects

I wrote my own IQueryProvider class that takes an expression and parses it in a SQL database (I know I can use Linq2Sql, but there are some changes and settings that I need, which unfortunately makes Linq2Sql unusable). The class will identify and do something with properties that are marked (using attributes), but not ones that I would not want to pass this expression to the LinqToObject provider and allow it to filter the results after.

For example, suppose I have the following linq expression:

var parents=Context.Parents .Where(parent=>parent.Name.Contains("T") && parent.Age>18); 

The Parents class is a custom class that implements the IQueryProvider and IQueryable interfaces, but only the Age property is checked for search, so the Age property will be processed, but the Name property will be ignored because it is not checked. After I finished processing the Age property, I would like to pass the entire LinqToObjects expression for processing and filtering, but I donโ€™t know how to do it.

NB No need to delete the Age expression for the expression, because the result will be the same even after I process it, so I can always send the entire expression to LinqToObjects.

I tried the following code, but it does not work:

 IEnumerator IEnumerable.GetEnumerator() { if(this.expression != null && !this.isEnumerating) { this.isEnumerating = true; var queryable=this.ToList().AsQueryable(); var query = queryable.Provider.CreateQuery(this.expression); return query.GetEnumerator(); } return this; } 

this.isEnumerating is just a boolean flag to prevent recursion.

this.expression contains the following:

 {value(namespace.Parents`1[namespace.Child]).Where(parent => ((parent.Name.EndsWith("T") AndAlso parent.Name.StartsWith("M")) AndAlso (parent.Test > 0)))} 

When I look at the code, despite converting the results to a list, it still uses my custom class for the query. Therefore, I realized that since the Parent class was at the beginning of the expression, it still redirected the request back to my provider, so I tried to set this.expression to Argument [1] of the method call to be like this:

 {parent => ((parent.Name.EndsWith("T") AndAlso parent.Name.StartsWith("M")) AndAlso (parent.Test > 0))} 

What I like more, however, whenever I pass this to the CreateQuery function, I get this error. "An argument expression is not valid."

The node expression type is now โ€œQuoteโ€, although not โ€œCall,โ€ but the method is null. I suspect that I just need to make this expression a call expression in some way, and it will work, but I'm not sure how to do it.

Please keep in mind that this expression is a where clause, but it can be any expression, and I would rather not try to parse this expression to see what type it is before passing it to the List provider request.

There may be a way to hide or replace the parent class of the source expression with the list provider class, but still leave it in a state that can simply be passed as an expression to the list provider, regardless of the type of expression

Any help on this would be greatly appreciated!

+4
source share
1 answer

You were so close!

My goal was to avoid the need to "replicate" a set of functions completely dependent on the speculative flashing of SQL-to-Object. And you put me on the right track (thanks!) Here's how to copy SQL-to-Object to custom IQueryable:

 public IEnumerator<T> GetEnumerator() { // For my case (a custom object-oriented database engine) I still // have an IQueryProvider which builds a "subset" of objects each populated // with only "required" fields, as extracted from the expression. IDs, // dates, particular strings, what have you. This is "cheap" because it // has an indexing system as well. var en = ((IEnumerable<T>)this.provider.Execute(this.expression)); // Copy your internal objects into a list. var ar = new List<T>(en); var queryable = ar.AsQueryable<T>(); // This is where we went wrong: // queryable.Provider.CreateQuery(this.expression); // We can't re-reference the original expression because it will loop // right back on our custom IQueryable<>. Instead, swap out the first // argument with the List queryable: var mc = (MethodCallExpression)this.expression; var exp = Expression.Call(mc.Method, Expression.Constant(queryable), mc.Arguments[1]); // Now the CLR can do all of the heavy lifting var query = queryable.Provider.CreateQuery<T>(exp); return query.GetEnumerator(); } 

I canโ€™t believe it took me 3 days to figure out how to avoid reusing the wheel on LINQ-to-Object queries.

+3
source

All Articles