简体   繁体   English

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

[英]How do I convert complex T-SQL into Linq

I am working in an Asp.NET Core 2.0 project, using EntityFramework Core 2.0. 我正在使用EntityFramework Core 2.0进行Asp.NET Core 2.0项目。

I am trying to convert an existing legacy SQL stored procedures into Linq in EntityFramework Core and I am having difficulty with this particular segment of T-SQL; 我正在尝试将现有的旧版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)

I tried running it through Linqer v4.6 but it just basically passed the same from the SQL window into the Linq window. 我尝试通过Linqer v4.6运行它,但它基本上只是将相同的内容从SQL窗口传递到Linq窗口。

I cut the Stored Procedure code down to this in Linqer; 我在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)

And Linqer produced Linq code that I have in my project as this; 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)
                            };

But I can't figure out the Linq translation of the wrapping T-SQL code; 但是我无法弄清楚包装的T-SQL代码的Linq转换。

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

If it helps, I have provided some further details about the project; 如果有帮助,我将提供有关该项目的更多详细信息。

The Efn SQL Server Efn table has the following fields; Efn SQL Server Efn表具有以下字段;

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

and in my project, I have an Efn entity class as follows; 在我的项目中,我有一个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; }

}

Here is my dbcontext class 这是我的dbcontext类

public class SerialNumberContext : DbContext { 公共类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);
    }

}

Here is the complete legacy Stored Procedure 这是完整的旧版存储过程

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

For translating SQL to LINQ query comprehension: 要将SQL转换为LINQ查询理解:

  1. Translate subselects as separately declared variables. 将子选择转换为单独声明的变量。
  2. Translate each clause in LINQ clause order, translating monadic and aggregate operators ( DISTINCT , TOP , MIN , MAX etc) into functions applied to the whole LINQ query. 以LINQ子句顺序转换每个子句,将单子运算符和集合运算符( DISTINCTTOPMINMAX等)转换为应用于整个LINQ查询的函数。
  3. Use table aliases as range variables. 使用表别名作为范围变量。 Use column aliases as anonymous type field names. 使用列别名作为匿名类型字段名称。
  4. Use anonymous types ( new { ... } ) for multiple columns. 对多个列使用匿名类型( new { ... } )。
  5. LEFT JOIN is simulated by using into joinvariable and doing another from from the joinvariable followed by .DefaultIfEmpty() . LEFT JOIN通过使用模拟into joinvariable和从做另一个from所述joinvariable接着.DefaultIfEmpty()
  6. Replace COALESCE with the conditional operator ( ?: )and a null test. 用条件运算符( ?: COALESCEnull测试替换COALESCE
  7. Translate IN to .Contains() and NOT IN to ! 翻译IN.Contains()NOT IN! ... Contains() . ... Contains()
  8. Translate x BETWEEN low AND high to low <= x && x <= high . 翻译X BETWEEN AND <= X && X <=
  9. SELECT * must be replaced with select range_variable or for joins, an anonymous object containing all the range variables. SELECT *必须替换为select range_variable,或者对于联接来说,是包含所有范围变量的匿名对象。
  10. SELECT fields must be replaced with select new { ... } creating an anonymous object with all the desired fields or expressions. SELECT字段必须替换为select new { ... }创建具有所有所需字段或表达式的匿名对象。
  11. Proper FULL OUTER JOIN must be handled with an extension method. 正确的FULL OUTER JOIN必须使用扩展方法来处理。

For your query, you have 3 sub-queries based on the 3 SELECT s and you can translate them from inside out: 对于您的查询,您有3个基于3个SELECT的子查询,您可以从内而外转换它们:

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();

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM