c# - 如何将复杂的 T-SQL 转换为 Linq

标签 c# linq t-sql entity-framework-core

我正在使用 EntityFramework Core 2.0 参与 Asp.NET Core 2.0 项目。

我正在尝试将现有的旧版 SQL 存储过程转换为 EntityFramework Core 中的 Linq,但我在处理 T-SQL 的这个特定部分时遇到了困难;

        SET @Target = (SELECT MIN(A.[Serial]) 
                        FROM (SELECT [HighSerial] + 1 AS 'Serial' 
                            FROM [Efn]
                            WHERE [Mid] = @Mid
                            AND ([HighSerial] + 1) BETWEEN @MinSerial AND @MaxSerial
                            AND ([HighSerial] + 1) NOT IN (SELECT [LowSerial] 
                                                        FROM [Efn]
                                                        WHERE [Mid] = @Mid)) A)

我尝试通过 Linqer v4.6 运行它,但它基本上只是将相同的内容从 SQL 窗口传递到 Linq 窗口。

我在 Linqer 中将存储过程代码缩减为这样;

SELECT [HighSerial] + 1 AS 'Serial' 
                            FROM [Efn]
                            WHERE [Mid] = @Mid
                            AND ([HighSerial] + 1) BETWEEN @MinSerial AND @MaxSerial
                            AND ([HighSerial] + 1) NOT IN (SELECT [LowSerial] 
                                                        FROM [Efn]
                                                        WHERE [Mid] = @Mid)

Linqer 生成了我项目中的 Linq 代码,如下所示;

                var query = from Efn in _serialNumberContext.Efns
                            where
                                Efn.Mid == mid &&
                                (Efn.HighSerial + 1) >= minSerial && (Efn.HighSerial + 1) <= maxSerial &&
                                !
                                    (from Efn0 in _serialNumberContext.Efns
                                     where
                                        Efn0.Mid == mid
                                     select new
                                     {
                                         Efn0.LowSerial
                                     }).Contains(new { LowSerial = (Int64)(Efn.HighSerial + 1) })
                            select new
                            {
                                Serial = (Efn.HighSerial + 1)
                            };

但我无法弄清楚包装 T-SQL 代码的 Linq 翻译;

SET @Target = (SELECT MIN(A.[Serial]) 
                FROM ( 
                        ...
                        ...
                        ...) A)

如果有帮助,我提供了有关该项目的一些进一步详细信息;

Efn SQL Server Efn 表具有以下字段;

    [Mid] INT NOT NULL,
    [Date] DATE NOT NULL,
    [LowSerial] BIGINT NOT NULL,
    [HighSerial] BIGINT NOT NULL

在我的项目中,我有一个 Efn 实体类,如下所示;

public class Efn
{

    [Required]
    [Column(TypeName = "int")]
    public int Mid { get; set; }

    [Required]
    [Column(TypeName="date")]
    public DateTime Date { get; set; }

    [Required]
    [Column(TypeName = "bigint")]
    public long LowSerial { get; set; }

    [Required]
    [Column(TypeName = "bigint")]
    public long HighSerial { get; set; }

}

这是我的 dbcontext 类

公共(public)类 SerialNumberContext :DbContext {

    public DbSet<Efn> Efns { get; set; }

    public SerialNumberContext(DbContextOptions<SerialNumberContext> options) : base(options)
    {

    }


    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Efn>()
            .HasIndex(e => new { e.Mid, e.HighSerial, e.Date, e.LowSerial })
            .IsUnique()
            .HasName("IX_Efn_Mid_HighSerial_Date_LowSerial")
            .ForSqlServerIsClustered();

        modelBuilder.Entity<Efn>()
            .HasIndex(e => new { e.Mid, e.LowSerial })
            .HasName("IX_Efn_Mid_LowSerial");

        base.OnModelCreating(modelBuilder);
    }

}

这是完整的旧存储过程

USE [SerialNumberDB]
GO

IF EXISTS (SELECT 1 FROM sys.objects WHERE name = N'fetchEfnSerial' AND [type]=N'P')
BEGIN
    DROP PROCEDURE [dbo].[fetchEfnSerial]
END

GO

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER OFF
GO

CREATE PROCEDURE [dbo].[fetchEfnSerial]
(
    @Mid INT, 
    @MinSerial BIGINT = NULL,
    @MaxSerial BIGINT = NULL
)
AS

DECLARE @Date DATE = CONVERT(DATE, GETDATE())

DECLARE @Target BIGINT;
DECLARE @MAX_BIG_INT BIGINT = 9223372036854775807;

IF (@MinSerial IS NULL) BEGIN SET @MinSerial = 1 END
IF (@MaxSerial IS NULL) BEGIN SET @MaxSerial = @MAX_BIG_INT END

SET @Target = NULL;

BEGIN TRY
    BEGIN TRANSACTION
        IF ((SELECT 1 
            FROM [Efn] 
            WHERE @MinSerial BETWEEN [LowSerial] AND [HighSerial] 
            AND [Mid] = @Mid) IS NULL)
        BEGIN
            SET @Target = @MinSerial
        END
        ELSE
        BEGIN
            SET @Target = (SELECT MIN(A.[Serial]) 
                            FROM (SELECT [HighSerial] + 1 AS 'Serial' 
                                FROM [Efn]
                                WHERE [Mid] = @Mid
                                AND ([HighSerial] + 1) BETWEEN @MinSerial AND @MaxSerial
                                AND ([HighSerial] + 1) NOT IN (SELECT [LowSerial] 
                                                            FROM [Efn]
                                                            WHERE [Mid] = @Mid)) A)
        END

        IF @Target IS NULL
        BEGIN
            DECLARE @ErrorText VARCHAR(255) = 'ERROR: No Serial Numbers are available in the specified range; between MinSerial: ' + CONVERT(VARCHAR(19), @MinSerial)
                                                + ' and MaxSerial: ' + CONVERT(VARCHAR(19), @MaxSerial)
            RAISERROR (@ErrorText, 16, 1)
        END

        IF @Target IS NOT NULL
        BEGIN
            IF EXISTS (SELECT 1
                FROM [Efn]
                WHERE [Mid] = @Mid AND [Date] = @Date
                AND [HighSerial] = @Target - 1)
            BEGIN
                -- If for this MID, the max value in the serial number block before the target
                -- serial number is from today, just update the max serial number of that block.
                UPDATE [Efn]
                SET [HighSerial] = @Target
                WHERE [Mid] = @Mid
                AND [HighSerial] = @Target - 1
            END
            ELSE
            BEGIN
                -- Otherwise, we need to make a new serial number block for this MID for today.
                INSERT INTO [Efn]
                SELECT @Mid, @Date, @Target, @Target
            END

            -- Return the target serial number to the caller so it can be used.
            SELECT @Target AS 'Serial'
        END

    COMMIT TRANSACTION

END TRY
BEGIN CATCH
    IF (@@TRANCOUNT > 0) ROLLBACK TRANSACTION
    DECLARE @ERRORMSG NVARCHAR(255)
    SET @ERRORMSG = ERROR_MESSAGE()
    RAISERROR(@ERRORMSG, 16, 1)
END CATCH

GO

最佳答案

将 SQL 转换为 LINQ 查询理解:

  1. 将子选择转换为单独声明的变量。
  2. 按 LINQ 子句顺序转换每个子句,将一元运算符和聚合运算符( DISTINCTTOPMINMAX 等)转换为应用于整个 LINQ 查询的函数。
  3. 使用表别名作为范围变量。使用列别名作为匿名类型字段名称。
  4. 对多个列使用匿名类型 ( new { ... } )。
  5. LEFT JOIN使用 into 进行模拟joinvariable 并从 from 执行另一个操作joinvariable 后跟 .DefaultIfEmpty() .
  6. 替换COALESCE使用条件运算符 ( ?: ) 和 null测试。
  7. 翻译 IN.Contains()NOT IN! ... Contains() .
  8. 翻译x BETWEEN AND 最高最低 <= x && x <=
  9. SELECT *必须替换为 select range_variable 或对于连接,包含所有范围变量的匿名对象。
  10. SELECT字段必须替换为 select new { ... }使用所有所需的字段或表达式创建一个匿名对象。
  11. 正确FULL OUTER JOIN必须使用扩展方法来处理。

对于您的查询,您有 3 个基于 3 SELECT 的子查询s,您可以从内到外翻译它们:

var lowSerials = from Efn in _serialNumberContext.Efns
                 where Efn.Mid == mid
                 select Efn.LowSerial;

var serials = from Efn in _serialNumberContext.Efns
              where Efn.Mid == mid &&
                    minSerial <= Efn.HighSerial + 1 && Efn.HighSerial + 1 <= maxSerial &&
                    !lowSerials.Contains(Efn.HighSerial + 1)
              select Efn.HighSerial + 1;

var Target = serials.Min();

关于c# - 如何将复杂的 T-SQL 转换为 Linq,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48895936/

相关文章:

linq - LINQ 选择项的 Lambda 表达式

c# - 使用 linq 时摆脱嵌套的 foreach 循环

t-sql - 如何忽略 LIKE 查询中的零宽度空格字符或解决方法?

c# - regsvr32 文件已加载,但入口点 dllregisterserver

c# - 如何在 LINQ 查询语言的 Where 中使用索引/位置?

c# - SQL CROSS JOIN问题

sql-server - 如何在 SQL Server 2008 中将 dbo 所有权更改为另一个用户登录名?

c# - 加入线程时是否需要内存屏障?

asp.net - linq 中的 switch 语句

sql - 如何在 SQL 中选择 XML Path 查询的输出?