如何将复杂的 T-SQL 转换为 Linq
How do I convert complex T-SQL into Linq
我正在 Asp.NET Core 2.0 项目中工作,使用 EntityFramework Core 2.0。
我正在尝试将现有遗留 SQL 存储过程转换为 EntityFramework Core 中的 Linq,但我在处理 T-SQL;[=19 的这个特定部分时遇到困难=]
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 window 传递到 Linq window.
我在 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 服务器 Efn table 有以下字段;
[Mid] INT NOT NULL,
[Date] DATE NOT NULL,
[LowSerial] BIGINT NOT NULL,
[HighSerial] BIGINT NOT NULL
在我的项目中,我有一个 Efn 实体 class,如下所示;
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; }
}
这是我的数据库上下文 class
public class 序列号上下文: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 查询理解:
- 将 subselects 翻译成单独声明的变量。
- 翻译 LINQ 子句顺序中的每个子句,将单子和聚合运算符(
DISTINCT
、TOP
、MIN
、MAX
等)翻译成应用于整体的函数LINQ 查询。
- 使用 table 别名作为范围变量。使用列别名作为匿名类型字段名称。
- 对多列使用匿名类型 (
new {
... }
)。
LEFT JOIN
是通过使用 into
joinvariable 并从 from
中执行另一个 joinvariable[=69= 来模拟的] 然后是 .DefaultIfEmpty()
.
- 将
COALESCE
替换为条件运算符 (?:
) 和 null
测试。
- 将
IN
翻译成 .Contains()
并将 NOT IN
翻译成 !
...Contains()
.
- 将x
BETWEEN
lowAND
high翻译成低 <=
x &&
x <=
高.
SELECT *
必须替换为 select range_variable 或对于联接,包含所有范围变量的匿名对象。
SELECT
字段必须替换为 select new {
... }
创建一个包含所有所需字段或表达式的匿名对象。
- 正确的
FULL OUTER JOIN
必须用扩展方法处理。
对于您的查询,您有 3 个 sub-queries 基于 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();
我正在 Asp.NET Core 2.0 项目中工作,使用 EntityFramework Core 2.0。
我正在尝试将现有遗留 SQL 存储过程转换为 EntityFramework Core 中的 Linq,但我在处理 T-SQL;[=19 的这个特定部分时遇到困难=]
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 window 传递到 Linq window.
我在 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 服务器 Efn table 有以下字段;
[Mid] INT NOT NULL,
[Date] DATE NOT NULL,
[LowSerial] BIGINT NOT NULL,
[HighSerial] BIGINT NOT NULL
在我的项目中,我有一个 Efn 实体 class,如下所示;
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; }
}
这是我的数据库上下文 class
public class 序列号上下文: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 查询理解:
- 将 subselects 翻译成单独声明的变量。
- 翻译 LINQ 子句顺序中的每个子句,将单子和聚合运算符(
DISTINCT
、TOP
、MIN
、MAX
等)翻译成应用于整体的函数LINQ 查询。 - 使用 table 别名作为范围变量。使用列别名作为匿名类型字段名称。
- 对多列使用匿名类型 (
new {
...}
)。 LEFT JOIN
是通过使用into
joinvariable 并从from
中执行另一个 joinvariable[=69= 来模拟的] 然后是.DefaultIfEmpty()
.- 将
COALESCE
替换为条件运算符 (?:
) 和null
测试。 - 将
IN
翻译成.Contains()
并将NOT IN
翻译成!
...Contains()
. - 将x
BETWEEN
lowAND
high翻译成低<=
x&&
x<=
高. SELECT *
必须替换为 select range_variable 或对于联接,包含所有范围变量的匿名对象。SELECT
字段必须替换为select new {
...}
创建一个包含所有所需字段或表达式的匿名对象。- 正确的
FULL OUTER JOIN
必须用扩展方法处理。
对于您的查询,您有 3 个 sub-queries 基于 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();