Как использовать поставщика LINQ от F#?
Как правильно использовать запросы LINQ в F# при использовании провайдера (например, LINQ to NHibernate), чтобы работать так же, как в C# (то же самое AST)?
Моя конкретная проблема заключается в том, что при переводе запроса на F# выдается ошибка, пока работает C#. Это может быть вызвано тем, что F# не генерирует тот же AST. Roslyn предоставляет расширение визуализатора Visual Studio AST для C#, но я не знаю ни одного средства просмотра AST для F#.
Имея следующий рабочий запрос C#:
.First(someEntity => someEntity.SomeNullableInt.HasValue);
при переводе на F#:
.First(fun someEntity -> someEntity.SomeNullableInt.HasValue)
происходит сбой со следующей ошибкой:
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
С помощью .First(fun someEntity -> someEntity.SomeReferenceType <> null)
работает правильно, хотя, что приводит к заключению выше: AST генерируется по-разному в случае использования .HasValue
,
1 ответ
В общем случае не существует способа генерировать такое же дерево выражений из F#, как вы можете из C#. В данном конкретном случае, я думаю, проблема в том, что F# иногда вставляет защитные копии типов значений для защиты от возможных мутаций, поэтому фактическая сгенерированная цитата будет эквивалентна чему-то более похожему
someEntity => ((System.Func<bool?,bool>)(copyOfNullable => copyOfNullable.HasValue)).Invoke(someEntity.SomeNullableInt)
Это прискорбно, поскольку обнуляемые типы являются неизменяемыми, поэтому эти защитные копии не нужны, но компилятор F# обычно не может определить, является ли данный тип изменяемым или нет, поэтому защитные копии добавляются во многих случаях, когда они не являются строго необходимыми.
Чтобы обойти это, можно выбрать вспомогательный метод, чтобы упростить ненужные элементы дерева выражений, чтобы вызывать что-то вроде
.First(Simplify(fun someEntity -> someEntity.SomeNullableInt.HasValue))