在 F# 中对字符串使用小于比较运算符

Using the less than comparison operator for strings in F#

我正在尝试按如下方式编写 F# 查询

let extract = 
    query {
        for trade in Schema.Trade do
        where (trade.Year + trade.Month <= "202007")
        take 100
        select (trade)
    }

问题是年份和月份都是string

我看到一个异常来自 Microsoft.FSharp.Linq.QueryModule

LessThanOrEqual 没有为类型“System.String”和“System.String”定义。

为什么不呢?有不同的运营商吗?

为了给你更多上下文,查询在 SQLProvider 中 运行,如果我只写 = 而不是 <=,自动生成的 sql 已经将 + 翻译成正确的

CONCAT(`trade`.`year`, `trade`.`month`)

所以这部分不是 SQLProvider 方面的问题。

C#底层代码

让我重新表述一下我认为幕后发生的事情。 抱歉,这只是我对问题的分析,我不想混淆。无论如何,我猜想发生了类似下面的事情(这是 C# 交互式)

> Expression<Func<String>> ex1 = () => "202006";;
> Expression<Func<String>> ex2 = () => "202005";;
> Expression.GreaterThan(ex1.Body, ex2.Body);;
System.InvalidOperationException: L'operatore binario GreaterThan non è definito per i tipi 'System.String' e 'System.String'.
  + System.Linq.Expressions.Expression.GetUserDefinedBinaryOperatorOrThrow(System.Linq.Expressions.ExpressionType, string, System.Linq.Expressions.Expression, System.Linq.Expressions.Expression, bool)
  + System.Linq.Expressions.Expression.GreaterThan(System.Linq.Expressions.Expression, System.Linq.Expressions.Expression, bool, System.Reflection.MethodInfo)

堆栈跟踪

这是来自上述 F# 程序的真实、完整的堆栈跟踪。

</ExceptionType><Message>L'operatore binario LessThanOrEqual non è definito per i tipi 'System.String' e 'System.String'.</Message><StackTrace>   in System.Linq.Expressions.Expression.GetUserDefinedBinaryOperatorOrThrow(ExpressionType binaryType, String name, Expression left, Expression right, Boolean liftToNull)
   in System.Linq.Expressions.Expression.LessThanOrEqual(Expression left, Expression right, Boolean liftToNull, MethodInfo method)
   in Microsoft.FSharp.Linq.RuntimeHelpers.LeafExpressionConverter.ConvExprToLinqInContext@512-7.Invoke(Tuple`2 tupledArg)
   in Microsoft.FSharp.Linq.RuntimeHelpers.LeafExpressionConverter.ConvExprToLinqInContext(ConvEnv env, FSharpExpr inp) in F:\workspace\_work\s\src\fsharp\FSharp.Core\Linq.fs:riga 512
   in Microsoft.FSharp.Linq.RuntimeHelpers.LeafExpressionConverter.ConvExprToLinqInContext(ConvEnv env, FSharpExpr inp) in F:\workspace\_work\s\src\fsharp\FSharp.Core\Linq.fs:riga 734
   in Microsoft.FSharp.Primitives.Basics.List.mapToFreshConsTail[a,b](FSharpList`1 cons, FSharpFunc`2 f, FSharpList`1 x) in F:\workspace\_work\s\src\fsharp\FSharp.Core\local.fs:riga 241
   in Microsoft.FSharp.Primitives.Basics.List.map[T,TResult](FSharpFunc`2 mapping, FSharpList`1 x) in F:\workspace\_work\s\src\fsharp\FSharp.Core\local.fs:riga 252
   in Microsoft.FSharp.Linq.RuntimeHelpers.LeafExpressionConverter.ConvExprsToLinq(ConvEnv env, FSharpList`1 es) in F:\workspace\_work\s\src\fsharp\FSharp.Core\Linq.fs:riga 820
   in Microsoft.FSharp.Linq.RuntimeHelpers.LeafExpressionConverter.ConvExprToLinqInContext(ConvEnv env, FSharpExpr inp) in F:\workspace\_work\s\src\fsharp\FSharp.Core\Linq.fs:riga 642
   in Microsoft.FSharp.Primitives.Basics.List.map[T,TResult](FSharpFunc`2 mapping, FSharpList`1 x) in F:\workspace\_work\s\src\fsharp\FSharp.Core\local.fs:riga 250
   in Microsoft.FSharp.Linq.RuntimeHelpers.LeafExpressionConverter.ConvExprsToLinq(ConvEnv env, FSharpList`1 es) in F:\workspace\_work\s\src\fsharp\FSharp.Core\Linq.fs:riga 820
   in Microsoft.FSharp.Linq.RuntimeHelpers.LeafExpressionConverter.ConvExprToLinqInContext(ConvEnv env, FSharpExpr inp) in F:\workspace\_work\s\src\fsharp\FSharp.Core\Linq.fs:riga 642
   in Microsoft.FSharp.Linq.RuntimeHelpers.LeafExpressionConverter.ConvExprToLinqInContext(ConvEnv env, FSharpExpr inp) in F:\workspace\_work\s\src\fsharp\FSharp.Core\Linq.fs:riga 734
   in Microsoft.FSharp.Linq.RuntimeHelpers.LeafExpressionConverter.EvaluateQuotation(FSharpExpr e) in F:\workspace\_work\s\src\fsharp\FSharp.Core\Linq.fs:riga 844
   in Microsoft.FSharp.Linq.QueryModule.EvalNonNestedInner(CanEliminate canElim, FSharpExpr queryProducingSequence) in F:\workspace\_work\s\src\fsharp\FSharp.Core\Query.fs:riga 1823
   in Microsoft.FSharp.Linq.QueryModule.clo@1926-1.Microsoft-FSharp-Linq-ForwardDeclarations-IQueryMethods-Execute[a,b](FSharpExpr`1 q) in F:\workspace\_work\s\src\fsharp\FSharp.Core\Query.fs:riga 1928
   in Program.main(String[] argv) in C:\FunctionalCmd\Program.fs:riga 23</StackTrace><ExceptionString>System.InvalidOperationException: L'operatore binario LessThanOrEqual non è definito per i tipi 'System.String' e 'System.String'.
   in System.Linq.Expressions.Expression.GetUserDefinedBinaryOperatorOrThrow(ExpressionType binaryType, String name, Expression left, Expression right, Boolean liftToNull)
   in System.Linq.Expressions.Expression.LessThanOrEqual(Expression left, Expression right, Boolean liftToNull, MethodInfo method)
   in Microsoft.FSharp.Linq.RuntimeHelpers.LeafExpressionConverter.ConvExprToLinqInContext@512-7.Invoke(Tuple`2 tupledArg)
   in Microsoft.FSharp.Linq.RuntimeHelpers.LeafExpressionConverter.ConvExprToLinqInContext(ConvEnv env, FSharpExpr inp) in F:\workspace\_work\s\src\fsharp\FSharp.Core\Linq.fs:riga 512
   in Microsoft.FSharp.Linq.RuntimeHelpers.LeafExpressionConverter.ConvExprToLinqInContext(ConvEnv env, FSharpExpr inp) in F:\workspace\_work\s\src\fsharp\FSharp.Core\Linq.fs:riga 734
   in Microsoft.FSharp.Primitives.Basics.List.mapToFreshConsTail[a,b](FSharpList`1 cons, FSharpFunc`2 f, FSharpList`1 x) in F:\workspace\_work\s\src\fsharp\FSharp.Core\local.fs:riga 241
   in Microsoft.FSharp.Primitives.Basics.List.map[T,TResult](FSharpFunc`2 mapping, FSharpList`1 x) in F:\workspace\_work\s\src\fsharp\FSharp.Core\local.fs:riga 252
   in Microsoft.FSharp.Linq.RuntimeHelpers.LeafExpressionConverter.ConvExprsToLinq(ConvEnv env, FSharpList`1 es) in F:\workspace\_work\s\src\fsharp\FSharp.Core\Linq.fs:riga 820
   in Microsoft.FSharp.Linq.RuntimeHelpers.LeafExpressionConverter.ConvExprToLinqInContext(ConvEnv env, FSharpExpr inp) in F:\workspace\_work\s\src\fsharp\FSharp.Core\Linq.fs:riga 642
   in Microsoft.FSharp.Primitives.Basics.List.map[T,TResult](FSharpFunc`2 mapping, FSharpList`1 x) in F:\workspace\_work\s\src\fsharp\FSharp.Core\local.fs:riga 250
   in Microsoft.FSharp.Linq.RuntimeHelpers.LeafExpressionConverter.ConvExprsToLinq(ConvEnv env, FSharpList`1 es) in F:\workspace\_work\s\src\fsharp\FSharp.Core\Linq.fs:riga 820
   in Microsoft.FSharp.Linq.RuntimeHelpers.LeafExpressionConverter.ConvExprToLinqInContext(ConvEnv env, FSharpExpr inp) in F:\workspace\_work\s\src\fsharp\FSharp.Core\Linq.fs:riga 642
   in Microsoft.FSharp.Linq.RuntimeHelpers.LeafExpressionConverter.ConvExprToLinqInContext(ConvEnv env, FSharpExpr inp) in F:\workspace\_work\s\src\fsharp\FSharp.Core\Linq.fs:riga 734
   in Microsoft.FSharp.Linq.RuntimeHelpers.LeafExpressionConverter.EvaluateQuotation(FSharpExpr e) in F:\workspace\_work\s\src\fsharp\FSharp.Core\Linq.fs:riga 844
   in Microsoft.FSharp.Linq.QueryModule.EvalNonNestedInner(CanEliminate canElim, FSharpExpr queryProducingSequence) in F:\workspace\_work\s\src\fsharp\FSharp.Core\Query.fs:riga 1823
   in Microsoft.FSharp.Linq.QueryModule.clo@1926-1.Microsoft-FSharp-Linq-ForwardDeclarations-IQueryMethods-Execute[a,b](FSharpExpr`1 q) in F:\workspace\_work\s\src\fsharp\FSharp.Core\Query.fs:riga 1928
   in Program.main(String[] argv) in C:\FunctionalCmd\Program.fs:riga 23</ExceptionString></Exception></TraceRecord>
Eccezione non gestita di tipo 'System.InvalidOperationException' in FSharp.Core.dll
L'operatore binario LessThanOrEqual non è definito per i tipi 'System.String' e 'System.String'.

我没有现成的数据库来尝试这个,但我认为问题在于您无法合理地比较 SQL 查询中的字符串。虽然连接字符串并检查字符串是否相等是合理的,但我认为对字符串进行比较不会达到您想要的效果。

我会尝试使用如下方式重写条件:

let extract = 
    query {
        for trade in Schema.Trade do
        where (int trade.Year < 2020 || 
          (int trade.Year = 2020 && int trade.Month <= 7))
        take 100
        select (trade)
    }

我假设您想要一个选择 2020 年 7 月之前的交易的查询,因此我使用更详细的条件对其进行了编码,该条件接受 2020 年之前的交易或 2020 年 7 月之前或 7 月的交易。

你能做这样的事情吗?

let extract = 
    let limitDate = System.DateTime(2020,08,01)
    query {
        for trade in Schema.Trade do
        where (trade < limitDate)
        take 100
        select (trade)
    }

这与描述的 C# 等效项非常相似 here

确实,如果我们看一下 linq2db ExpressionEqualityComparer 他们指向 CompareBinary

SQLProvider SqlRuntime.Patterns 指向 ConditionOperator.GreaterThan

| ExpressionType.GreaterThan,        (:? BinaryExpression as ce) -> Some (ConditionOperator.GreaterThan,  ce.Left,ce.Right)

可能是(见@Abel 评论)

a bug in the query translation, as string implements IComparable

我已经通过切换到 C#(更健壮并防止运行时异常和编译错误,与其语法一致,与 F# 中发生的情况相反)和 linq2db(也更成熟)解决了这个问题并且比 F# SQL 类型提供程序更强大)