How to use a LINQ provider from F#?

196 Views Asked by At

What is a proper way of using LINQ queries in F# when using a provider (LINQ to NHibernate for example) so as to work the same as in C# (same AST)?

My specific issue is that translating a query to F# throws an error while the C# one works. This might be caused by F# not generating the same AST. Roslyn provides a Visual Studio AST visualizer extension for C# but I am not aware of any AST viewer for F#.

Having the following working C# query:

.First(someEntity => someEntity.SomeNullableInt.HasValue);

when translated to F#:

.First(fun someEntity -> someEntity.SomeNullableInt.HasValue)

it fails with the following error:

System.NotSupportedException: Boolean Invoke(System.Nullable`1[System.Int32])
>    at NHibernate.Linq.Visitors.HqlGeneratorExpressionTreeVisitor.VisitMethodCallExpression(MethodCallExpression expression)
   at NHibernate.Linq.Visitors.QueryModelVisitor.VisitWhereClause(WhereClause whereClause, QueryModel queryModel, Int32 index)
   at Remotion.Linq.QueryModelVisitorBase.VisitBodyClauses(ObservableCollection`1 bodyClauses, QueryModel queryModel)
   at Remotion.Linq.QueryModelVisitorBase.VisitQueryModel(QueryModel queryModel)
   at NHibernate.Linq.Visitors.QueryModelVisitor.GenerateHqlQuery(QueryModel queryModel, VisitorParameters parameters, Boolean root)
   at NHibernate.Linq.NhLinqExpression.Translate(ISessionFactoryImplementor sessionFactory, Boolean filter)
   at NHibernate.Hql.Ast.ANTLR.ASTQueryTranslatorFactory.CreateQueryTranslators(IQueryExpression queryExpression, String collectionRole, Boolean shallow, IDictionary`2 filters, ISessionFactoryImplementor factory)
   at NHibernate.Engine.Query.QueryPlanCache.GetHQLQueryPlan(IQueryExpression queryExpression, Boolean shallow, IDictionary`2 enabledFilters)
   at NHibernate.Impl.AbstractSessionImpl.GetHQLQueryPlan(IQueryExpression queryExpression, Boolean shallow)
   at NHibernate.Impl.AbstractSessionImpl.CreateQuery(IQueryExpression queryExpression)
   at NHibernate.Linq.DefaultQueryProvider.PrepareQuery(Expression expression, IQuery& query, NhLinqExpression& nhQuery)
   at NHibernate.Linq.DefaultQueryProvider.Execute(Expression expression)
   at NHibernate.Linq.DefaultQueryProvider.Execute[TResult](Expression expression)
   ...
Stopped due to error

Using .First(fun someEntity -> someEntity.SomeReferenceType <> null) works correctly though, which leads to the conclusion above: AST is generated differently in the case of using .HasValue.

1

There are 1 best solutions below

0
On

There is not in general any way to generate the same expression tree from F# as you can from C#. In this particular case, I think the issue is that F# sometimes inserts defensive copies of value types to protect against possible mutations so the actual quotation generated will be the equivalent of something more like

someEntity => ((System.Func<bool?,bool>)(copyOfNullable => copyOfNullable.HasValue)).Invoke(someEntity.SomeNullableInt)

This is unfortunate, because nullable types are immutable so these defensive copies are unnecessary, but it's not generally feasible for the F# compiler to determine whether a given type is mutable or not so defensive copies are added in many cases where they are not strictly needed.

To work around this, one option is to define a helper method to simplify away expression tree elements that you don't need, so that you'd call something like

.First(Simplify(fun someEntity -> someEntity.SomeNullableInt.HasValue))