【问题标题】:How to make EF core translate custom sort into a plain 'When Then' instead of hierarchical如何使 EF 核心将自定义排序转换为普通的“When Then”而不是分层
【发布时间】:2020-04-20 01:04:53
【问题描述】:

问题是 - 我得到以下错误

案例表达式只能嵌套到第 10 级。

当我尝试将以下 LINQ 代码与 EF 核心一起使用时

var extendedResult = result.Select(p => new
    {
        readModelFields = p,
        sortKey =
            p.CaseState == CaseState.Scheduled.ToString() ? 1 :
            p.CaseState == CaseState.Queued.ToString() ? 2 :
            p.CaseState == "Investigation" ? 3 :
            p.CaseState == "Awaiting Customer" ? 4 :
            p.CaseState == "State 5" ? 5 :
            p.CaseState == "State 6" ? 6 :
            p.CaseState == "State 7" ? 7 :
            p.CaseState == "State 8" ? 8 :
            p.CaseState == "State 9" ? 9 :
            p.CaseState == "State 10" ? 10 :
            p.CaseState == "State 11" ? 11 :
            p.CaseState == "Rejected" ? 12 :
            p.CaseState == "Blocked" ? 13 :
            p.CaseState == "Postponed" ? 14 :
            15
    });

    return IsAscending(query.SortDirection)
        ? extendedResult.OrderBy(p => p.sortKey).Select(p => p.readModelFields)
        : extendedResult.OrderByDescending(p => p.sortKey).Select(p => p.readModelFields);

这个问题是由 SQL 生成 SQL 的方式引起的(这是一个结果)

exec sp_executesql N'SELECT [o].[Id], [o].[Owner], [o].[CaseState],  [o].[LastActionDate], [o].[CountryCode], [o].[Name], [o].[Risk]
FROM [dbo].[OpenCases] AS [o]
ORDER BY CASE
    WHEN [o].[CaseState] = N''Scheduled'' THEN 1
    ELSE CASE
        WHEN [o].[CaseState] = N''Queued'' THEN 2
        ELSE CASE
            WHEN [o].[CaseState] = N''Investigation'' THEN 3
            ELSE CASE
                WHEN [o].[CaseState] = N''Awaiting Customer'' THEN 4
                ELSE CASE
                    WHEN [o].[CaseState] = N''State 6'' THEN 6
                    ELSE CASE
                        WHEN [o].[CaseState] = N''State 7'' THEN 7
                        ELSE CASE
                            WHEN [o].[CaseState] = N''State 8'' THEN 8
                            ELSE CASE
                                WHEN [o].[CaseState] = N''State 9'' THEN 9
                                ELSE CASE
                                    WHEN [o].[CaseState] = N''State 10'' THEN 10
                                    ELSE CASE
                                        WHEN [o].[CaseState] = N''State 11'' THEN 11
                                        ELSE CASE
                                            WHEN [o].[CaseState] = N''Rejected'' THEN 12
                                            ELSE CASE
                                                WHEN [o].[CaseState] = N''Blocked'' THEN 13
                                                ELSE CASE
                                                    WHEN [o].[CaseState] = N''Postponed'' THEN 14
                                                    ELSE 15
                                                END
                                            END
                                        END
                                    END
                                END
                            END
                        END
                    END
                END
            END
        END
    END
END DESC
OFFSET @__p_0 ROWS FETCH NEXT @__p_1 ROWS ONLY',N'@__p_0 int,@__p_1 int',@__p_0=0,@__p_1=2147483647

我想要的是 EF 构建这样的查询:

...
ORDER BY 
CASE
   WHEN CaseState = "Investigation" THEN 3
   WHEN CaseState = "State 5" THEN 5
   WHEN CaseState = "State 6" THEN 6
   WHEN CaseState = "State 7" THEN 7
   .......
   WHEN CaseState = "Postponed" THEN 14
END desc

【问题讨论】:

  • 您对 SQL 的生成方式影响不大。最简单的方法是将“状态 5”转换为“5”的数据库表中,然后使用 Join。这将是数据库风格。非常棘手的事情:定义一个具有相应值的 Enum,这将使用字符串到 int 转换进行序列化,但它不适用于字符串中的空格。
  • 感谢您的支持。虽然 - 我在这里专门发布了这个问题,以了解是否有可能以某种方式影响它。如果不是 - 那么我将不得不按照您建议的方式在 SQL 中添加列或表。现在 - 我正在寻找并回答如何重写 LINQ 或如何告诉 EF 以不同的方式构建“CASE When Then”。
  • 一般来说是可以的,但并不容易。 SQL 提供程序有注入机制。仅当您在不同场景中经常需要它时才值得。您也可以只使用 RawSQL-Query 发送 SQL,或者在从数据库加载字符串后,您可以在 LINQ 中将字符串转换为整数到对象(或使用自定义 IComparer)。
  • 我肯定会使用将原始 sql 传递给 EF 上下文并利用安全查询参数化或几乎任何其他方法,而不是仅向表中添加额外的列以使使用 EF 更容易一些。
  • Issues - EntityFrameworkCore 提出建议,允许将新的 C# 8.0 switch 表达式转换为 SQL。 expr switch { e1 => ret1, e2 => ret2, _ => ret3 } 将转换为 CASE expr WHEN e1 THEN ret1 WHEN e2 THEN ret2 ELSE ret3 END

标签: c# sql-server linq entity-framework-core


【解决方案1】:

您遇到了由#12729: Flatten CASE expressions to avoid error "Case expressions may only be nested to level 10" 跟踪的 EF Core 查询翻译错误。正如我们所见,它已在 EF Core 存储库中修复,但修复将包含在 5.0.0 版本中(无论何时发布)。

可以通过连接到 EF Core 内部基础架构来修复它,但这需要知道确切的 EF Core 版本,并且还需要针对不同的 EF Core 版本使用不同的方法,因为几乎每个甚至是次要版本都会更改基础架构。

作为一般的解决方法,我可以建议将单个多条件运算符表达式替换为多个单个条件表达式的总和。它不会生成所需的平面CASE SQL 表达式,但至少不会产生有问题的错误。像这样的:

sortKey =
    (p.CaseState == CaseState.Scheduled.ToString() ? 1 : 0) +
    (p.CaseState == CaseState.Queued.ToString() ? 2 : 0) +
    (p.CaseState == "Investigation" ? 3 : 0) +
    (p.CaseState == "Awaiting Customer" ? 4 : 0) +
    (p.CaseState == "State 5" ? 5 : 0) + 
    (p.CaseState == "State 6" ? 6 : 0) +
    (p.CaseState == "State 7" ? 7 : 0) +
    (p.CaseState == "State 8" ? 8 : 0) +
    (p.CaseState == "State 9" ? 9 : 0) +
    (p.CaseState == "State 10" ? 10 : 0) +
    (p.CaseState == "State 11" ? 11 : 0) +
    (p.CaseState == "Rejected" ? 12 : 0) +
    (p.CaseState == "Blocked" ? 13 : 0) +
    (p.CaseState == "Postponed" ? 14 : 15)

【讨论】:

    【解决方案2】:

    使用字符串映射来转换状态怎么样?

    var stateMapString = "Investigation    Awaiting CustomerState 5          State 6          State 7          State 8          State 9          State 10         State 11         Rejected         Blocked          Postponed";
    
    var extendedResult = result.Select(p => new
        {
            readModelFields = p,
            sortKey = p.CaseState == CaseState.Scheduled.ToString() ? 1 :
                      p.CaseState == CaseState.Queued.ToString() ? 2 :
                      (p.CaseState != "" && stateMapString.IndexOf(p.CaseState) >= 0)
                            ? stateMapString.IndexOf(p.CaseState)/17+3
                            : 15
        });
    

    如果您的状态可能发生变化,您可以计算地图字符串:

    var stateStrings = new[] { "Investigation",
                                "Awaiting Customer",
                                "State 5",
                                "State 6",
                                "State 7",
                                "State 8",
                                "State 9",
                                "State 10",
                                "State 11",
                                "Rejected",
                                "Blocked",
                                "Postponed" };
    var stateMaxLen = stateStrings.Max(s => s.Length);
    var stateMapString = String.Join("", stateStrings.Select(s => s.PadRight(stateMaxLen)));
    

    只需在查询中使用stateMaxLen 来划分IndexOf 结果。

    【讨论】:

      猜你喜欢
      • 2021-03-13
      • 1970-01-01
      • 1970-01-01
      • 2017-10-18
      • 1970-01-01
      • 2020-05-20
      • 1970-01-01
      • 1970-01-01
      • 2020-05-05
      相关资源
      最近更新 更多