Je travaille dans un projet Asp.NET Core 2.0 en utilisant EntityFramework Core 2.0.
J’essaie de convertir des procédures stockées SQL existantes en Linq dans EntityFramework Core et je rencontre des difficultés avec ce segment particulier de 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)
J’ai essayé de l’exécuter avec Linqer v4.6, mais il a simplement passé la même chose de la fenêtre SQL à la fenêtre Linq.
J’ai coupé le code de procédure stockée jusqu’à celui-ci dans 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)
Et Linqer a produit le code Linq que j’ai dans mon projet comme ceci;
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) };
Mais je n’arrive pas à comprendre la traduction Linq du code T-SQL enveloppant;
SET @Target = (SELECT MIN(A.[Serial]) FROM ( ... ... ...) A)
Si cela peut aider, j’ai fourni quelques détails supplémentaires sur le projet;
La table Efn Efn SQL Server comporte les champs suivants;
[Mid] INT NOT NULL, [Date] DATE NOT NULL, [LowSerial] BIGINT NOT NULL, [HighSerial] BIGINT NOT NULL
et dans mon projet, j’ai une classe d’entités Efn comme suit;
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; } }
Voici ma classe dbcontext
Classe publique SerialNumberContext: DbContext {
public DbSet Efns { get; set; } public SerialNumberContext(DbContextOptions options) : base(options) { } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity() .HasIndex(e => new { e.Mid, e.HighSerial, e.Date, e.LowSerial }) .IsUnique() .HasName("IX_Efn_Mid_HighSerial_Date_LowSerial") .ForSqlServerIsClustered(); modelBuilder.Entity() .HasIndex(e => new { e.Mid, e.LowSerial }) .HasName("IX_Efn_Mid_LowSerial"); base.OnModelCreating(modelBuilder); } }
Voici la procédure stockée héritée complète
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
Pour traduire SQL en compréhension de requête LINQ:
DISTINCT
, TOP
, MIN
, MAX
etc.) en fonctions appliquées à l’ensemble de la requête LINQ. new {
… }
) pour plusieurs colonnes. LEFT JOIN
est simulé en utilisant joinvariable et en en faisant une autre à partir from
.DefaultIfEmpty()
suivie de .DefaultIfEmpty()
. COALESCE
par l’opérateur conditionnel ( ?:
COALESCE
par un test null
. IN
en .Contains()
et NOT IN
!
… Contains()
. AND
haut en bas <=
x &&
x <=
haut . SELECT *
doit être remplacé par select plage_variable ou pour les jointures, un object anonyme contenant toutes les variables de plage. SELECT
champs SELECT
doivent être remplacés par select new {
... }
créer un object anonyme avec tous les champs ou expressions souhaités. FULL OUTER JOIN
doit être traité avec une méthode d’extension. Pour votre requête, vous avez 3 sous-requêtes basées sur les 3 SELECT
et vous pouvez les traduire de l'intérieur vers l'extérieur:
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();