Cannot use Linq Where and Select Extension Methods

Dec 3, 2014 at 8:28 PM
I am trying to get ClearScript to work with my a Linq data access Layer. I have unit of work object that exposes IQueryable of my various database entities. E.g. IQueryable<Project>

I am trying to write a linq statement to select specific projects from the unit of work using the Where extension method but I get the following message Error: 'System.Linq.IQueryable' does not contain a definition for 'Where'. Similiar errors occur for other extension methods that require Func<> delegates. E.g. Select, OrderBy, etc. Any help on getting this scenario to work would be much appreciated.

Unit Of Work Definition
Public class MyUnitOfWork : UnitOfWorkBase
{
    public IQueryable<Project> Projects { get { return this.Query<Project>(); } }
}
Script Engine Setup
using (var engine = new V8ScriptEngine())
{
    var uow = ContextFactory.GetModuleRepo<MyUnitOfWork>(); //creates unit of work instance
    engine.AddHostType(typeof(Enumerable).FullName, typeof(Enumerable));
    engine.AddHostType(typeof(Queryable).FullName, typeof(Queryable));
    engine.AddHostObject("uow", uow);
    engine.Evaluate(txtScript.Text);
}
Javascript
result = uow.Projects.Where(function(w) {return w.Id == 100;}).ToList();
Coordinator
Dec 4, 2014 at 2:38 AM
Edited Dec 4, 2014 at 2:46 AM
Hello!

ClearScript doesn't convert script functions to delegates automatically, but it provides an easy way to create a delegate that invokes a script function. Try something like this:
engine.AddHostType("Enumerable", typeof(Enumerable));
engine.AddHostType("ProjectPredicate", typeof(Func<Project, bool>));
engine.Execute(@"
    predicate = new ProjectPredicate(function (w) { return w.Id == 100; });
    result = uow.Projects.Where(predicate).ToList();
");
Please let us know if this doesn't work for you.

Thanks!
Marked as answer by rufio620 on 12/4/2014 at 9:09 AM
Dec 4, 2014 at 4:09 PM
This approach worked. Thanks for the example.

The only problem with this approach is that it requires a priori knowledge of the predicates that will be used. This kind of defeats the purpose of providing a scripting component in an application. It means I would have to enumerate all possible predicate types for possible linq statements.

I'm not sure if it is possible, but and excellent feature would be to allow for automatic conversion of script functions to delegates (at least For Func and Action delegate types.) This would greatly simplify usage of the LINQ language features.
Coordinator
Dec 4, 2014 at 8:12 PM
Hi again,

Unfortunately ambiguity prevents automatic conversion of script functions to delegates, especially when generics are involved.

For example, examining a JavaScript function gets us, at best, a parameter count, and while that might be enough to select the correct overload of Where(), it can't help us bind to the intended specialization of something like Select(), where the delegate's return type plays a critical role in finding the right method. To invoke Select() successfully we must either have explicit type arguments or a strongly-typed delegate; lacking both it's just not possible.

However, there are things you can do to make LINQ easier for script code to use. For example, instead of exposing the standard Enumerable class, consider exposing your own extensions that are specifically designed for script code:
public static class ScriptWhere {
    public static IEnumerable<T> Where<T>(this IEnumerable<T> source, dynamic predicate) {
        return source.Where(item => Convert.ToBoolean(predicate(item)));
    }
}
Then you could do this:
engine.AddHostType("ScriptWhere", typeof(ScriptWhere));
engine.Execute(@"
    result = uow.Projects.Where(function (w) { return w.Id == 100; }).ToList();
");
For Select() you can't get around having to specify the target type, but you could still make script code a bit happier:
public static class ScriptSelect {
    public static IEnumerable<T> Select<T>(this IEnumerable<object> source, dynamic selector) {
        return source.Select(item => (T)selector(item));
    }
}
And then:
engine.AddHostType("ScriptSelect", typeof(ScriptSelect));
engine.AddHostType("Int32", typeof(int));
engine.Execute(@"
    result = uow.Projects.Select(Int32, function (w) { return w.Id; }).ToList();
");
We haven't explored all the possibilities; there are just a few ideas.

Good luck!