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
.
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
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