Guon aspekten fan it optimalisearjen fan LINQ-fragen yn C#.NET foar MS SQL Server

LINQ ynfierd .NET as in krêftige nije gegevens manipulaasje taal. LINQ nei SQL as ûnderdiel dêrfan kinne jo frij maklik kommunisearje mei in DBMS mei help fan, bygelyks, Entity Framework. Troch it lykwols frij faak te brûken, ferjitte ûntwikkelders om te sjen nei hokker soarte SQL-query de queryable provider, yn jo gefal Entity Framework, sil generearje.

Litte wy nei twa haadpunten sjen mei in foarbyld.
Om dit te dwaan, meitsje in testdatabase yn SQL Server, en meitsje twa tabellen dêryn mei de folgjende query:

It meitsjen fan tabellen

USE [TEST]
GO

SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

CREATE TABLE [dbo].[Ref](
	[ID] [int] NOT NULL,
	[ID2] [int] NOT NULL,
	[Name] [nvarchar](255) NOT NULL,
	[InsertUTCDate] [datetime] NOT NULL,
 CONSTRAINT [PK_Ref] PRIMARY KEY CLUSTERED 
(
	[ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO

ALTER TABLE [dbo].[Ref] ADD  CONSTRAINT [DF_Ref_InsertUTCDate]  DEFAULT (getutcdate()) FOR [InsertUTCDate]
GO

USE [TEST]
GO

SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

CREATE TABLE [dbo].[Customer](
	[ID] [int] NOT NULL,
	[Name] [nvarchar](255) NOT NULL,
	[Ref_ID] [int] NOT NULL,
	[InsertUTCDate] [datetime] NOT NULL,
	[Ref_ID2] [int] NOT NULL,
 CONSTRAINT [PK_Customer] PRIMARY KEY CLUSTERED 
(
	[ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO

ALTER TABLE [dbo].[Customer] ADD  CONSTRAINT [DF_Customer_Ref_ID]  DEFAULT ((0)) FOR [Ref_ID]
GO

ALTER TABLE [dbo].[Customer] ADD  CONSTRAINT [DF_Customer_InsertUTCDate]  DEFAULT (getutcdate()) FOR [InsertUTCDate]
GO

Litte wy no de Ref-tabel ynfolje troch it folgjende skript út te fieren:

It ynfoljen fan de Ref tafel

USE [TEST]
GO

DECLARE @ind INT=1;

WHILE(@ind<1200000)
BEGIN
	INSERT INTO [dbo].[Ref]
           ([ID]
           ,[ID2]
           ,[Name])
    SELECT
           @ind
           ,@ind
           ,CAST(@ind AS NVARCHAR(255));

	SET @ind=@ind+1;
END 
GO

Litte wy de klanttabel op deselde manier folje mei it folgjende skript:

Befolking fan de klant tabel

USE [TEST]
GO

DECLARE @ind INT=1;
DECLARE @ind_ref INT=1;

WHILE(@ind<=12000000)
BEGIN
	IF(@ind%3=0) SET @ind_ref=1;
	ELSE IF (@ind%5=0) SET @ind_ref=2;
	ELSE IF (@ind%7=0) SET @ind_ref=3;
	ELSE IF (@ind%11=0) SET @ind_ref=4;
	ELSE IF (@ind%13=0) SET @ind_ref=5;
	ELSE IF (@ind%17=0) SET @ind_ref=6;
	ELSE IF (@ind%19=0) SET @ind_ref=7;
	ELSE IF (@ind%23=0) SET @ind_ref=8;
	ELSE IF (@ind%29=0) SET @ind_ref=9;
	ELSE IF (@ind%31=0) SET @ind_ref=10;
	ELSE IF (@ind%37=0) SET @ind_ref=11;
	ELSE SET @ind_ref=@ind%1190000;
	
	INSERT INTO [dbo].[Customer]
	           ([ID]
	           ,[Name]
	           ,[Ref_ID]
	           ,[Ref_ID2])
	     SELECT
	           @ind,
	           CAST(@ind AS NVARCHAR(255)),
	           @ind_ref,
	           @ind_ref;


	SET @ind=@ind+1;
END
GO

Sa krigen wy twa tabellen, wêrfan ien mear as 1 miljoen rigen gegevens hat, en de oare hat mear as 10 miljoen rigen gegevens.

No yn Visual Studio moatte jo in test Visual C# Console App (.NET Framework) projekt meitsje:

Guon aspekten fan it optimalisearjen fan LINQ-fragen yn C#.NET foar MS SQL Server

Dêrnei moatte jo in bibleteek tafoegje foar it Entity Framework om te ynteraksje mei de databank.
Om it ta te foegjen, klikje jo mei de rechtermuisknop op it projekt en selektearje NuGet-pakketten beheare út it kontekstmenu:

Guon aspekten fan it optimalisearjen fan LINQ-fragen yn C#.NET foar MS SQL Server

Fier dan yn it finster fan NuGet pakketbehear dat ferskynt it wurd "Entity Framework" yn yn it sykfinster en selektearje it Entity Framework-pakket en ynstallearje it:

Guon aspekten fan it optimalisearjen fan LINQ-fragen yn C#.NET foar MS SQL Server

Folgjende, yn 'e App.config-bestân, nei it sluten fan it elemint configSections, moatte jo it folgjende blok tafoegje:

<connectionStrings>
    <add name="DBConnection" connectionString="data source=ИМЯ_ЭКЗЕМПЛЯРА_MSSQL;Initial Catalog=TEST;Integrated Security=True;" providerName="System.Data.SqlClient" />
</connectionStrings>

Yn connectionString moatte jo de ferbiningsstring ynfiere.

Litte wy no 3 ynterfaces meitsje yn aparte bestannen:

  1. It ymplementearjen fan de IBaseEntityID-ynterface
    namespace TestLINQ
    {
        public interface IBaseEntityID
        {
            int ID { get; set; }
        }
    }
    

  2. Implementaasje fan de IBaseEntityName ynterface
    namespace TestLINQ
    {
        public interface IBaseEntityName
        {
            string Name { get; set; }
        }
    }
    

  3. Implementaasje fan de IBaseNameInsertUTCDate-ynterface
    namespace TestLINQ
    {
        public interface IBaseNameInsertUTCDate
        {
            DateTime InsertUTCDate { get; set; }
        }
    }
    

En yn in apart bestân sille wy in basisklasse BaseEntity meitsje foar ús twa entiteiten, dy't mienskiplike fjilden sille omfetsje:

Implementaasje fan de basisklasse BaseEntity

namespace TestLINQ
{
    public class BaseEntity : IBaseEntityID, IBaseEntityName, IBaseNameInsertUTCDate
    {
        public int ID { get; set; }
        public string Name { get; set; }
        public DateTime InsertUTCDate { get; set; }
    }
}

Folgjende sille wy ús twa entiteiten oanmeitsje yn aparte bestannen:

  1. Útfiering fan de Ref klasse
    using System.ComponentModel.DataAnnotations.Schema;
    
    namespace TestLINQ
    {
        [Table("Ref")]
        public class Ref : BaseEntity
        {
            public int ID2 { get; set; }
        }
    }
    

  2. Útfiering fan de klant klasse
    using System.ComponentModel.DataAnnotations.Schema;
    
    namespace TestLINQ
    {
        [Table("Customer")]
        public class Customer: BaseEntity
        {
            public int Ref_ID { get; set; }
            public int Ref_ID2 { get; set; }
        }
    }
    

Litte wy no in UserContext-kontekst oanmeitsje yn in apart bestân:

Implementaasje fan de UserContex klasse

using System.Data.Entity;

namespace TestLINQ
{
    public class UserContext : DbContext
    {
        public UserContext()
            : base("DbConnection")
        {
            Database.SetInitializer<UserContext>(null);
        }

        public DbSet<Customer> Customer { get; set; }
        public DbSet<Ref> Ref { get; set; }
    }
}

Wy krigen in klearmakke oplossing foar it útfieren fan optimisaasjetests mei LINQ nei SQL fia EF foar MS SQL Server:

Guon aspekten fan it optimalisearjen fan LINQ-fragen yn C#.NET foar MS SQL Server

Fier no de folgjende koade yn yn it Program.cs-bestân:

Program.cs triem

using System;
using System.Collections.Generic;
using System.Linq;

namespace TestLINQ
{
    class Program
    {
        static void Main(string[] args)
        {
            using (UserContext db = new UserContext())
            {
                var dblog = new List<string>();
                db.Database.Log = dblog.Add;

                var query = from e1 in db.Customer
                            from e2 in db.Ref
                            where (e1.Ref_ID == e2.ID)
                                 && (e1.Ref_ID2 == e2.ID2)
                            select new { Data1 = e1.Name, Data2 = e2.Name };

                var result = query.Take(1000).ToList();

                Console.WriteLine(dblog[1]);

                Console.ReadKey();
            }
        }
    }
}

Litte wy dan ús projekt lansearje.

Oan 'e ein fan it wurk sil it folgjende wurde werjûn op' e konsole:

Generearre SQL Query

SELECT TOP (1000) 
    [Extent1].[Ref_ID] AS [Ref_ID], 
    [Extent1].[Name] AS [Name], 
    [Extent2].[Name] AS [Name1]
    FROM  [dbo].[Customer] AS [Extent1]
    INNER JOIN [dbo].[Ref] AS [Extent2] ON ([Extent1].[Ref_ID] = [Extent2].[ID]) AND ([Extent1].[Ref_ID2] = [Extent2].[ID2])

Dat is, yn 't algemien, de LINQ-query generearre in SQL-query nei de MS SQL Server DBMS frij goed.

Litte wy no de EN-betingst feroarje nei OR yn 'e LINQ-query:

LINQ fraach

var query = from e1 in db.Customer
                            from e2 in db.Ref
                            where (e1.Ref_ID == e2.ID)
                                || (e1.Ref_ID2 == e2.ID2)
                            select new { Data1 = e1.Name, Data2 = e2.Name };

En litte wy ús applikaasje opnij starte.

De útfiering sil crashe mei in flater fanwege de kommando-útfiertiid fan mear as 30 sekonden:

Guon aspekten fan it optimalisearjen fan LINQ-fragen yn C#.NET foar MS SQL Server

As jo ​​​​nei de query sjogge dy't waard generearre troch LINQ:

Guon aspekten fan it optimalisearjen fan LINQ-fragen yn C#.NET foar MS SQL Server
, dan kinne jo derfoar soargje dat de seleksje bart troch it Cartesyske produkt fan twa sets (tabellen):

Generearre SQL Query

SELECT TOP (1000) 
    [Extent1].[Ref_ID] AS [Ref_ID], 
    [Extent1].[Name] AS [Name], 
    [Extent2].[Name] AS [Name1]
    FROM  [dbo].[Customer] AS [Extent1]
    CROSS JOIN [dbo].[Ref] AS [Extent2]
    WHERE [Extent1].[Ref_ID] = [Extent2].[ID] OR [Extent1].[Ref_ID2] = [Extent2].[ID2]

Litte wy de LINQ-query sa herskriuwe:

Optimalisearre LINQ-query

var query = (from e1 in db.Customer
                   join e2 in db.Ref
                   on e1.Ref_ID equals e2.ID
                   select new { Data1 = e1.Name, Data2 = e2.Name }).Union(
                        from e1 in db.Customer
                        join e2 in db.Ref
                        on e1.Ref_ID2 equals e2.ID2
                        select new { Data1 = e1.Name, Data2 = e2.Name });

Dan krije wy de folgjende SQL-query:

SQL query

SELECT 
    [Limit1].[C1] AS [C1], 
    [Limit1].[C2] AS [C2], 
    [Limit1].[C3] AS [C3]
    FROM ( SELECT DISTINCT TOP (1000) 
        [UnionAll1].[C1] AS [C1], 
        [UnionAll1].[Name] AS [C2], 
        [UnionAll1].[Name1] AS [C3]
        FROM  (SELECT 
            1 AS [C1], 
            [Extent1].[Name] AS [Name], 
            [Extent2].[Name] AS [Name1]
            FROM  [dbo].[Customer] AS [Extent1]
            INNER JOIN [dbo].[Ref] AS [Extent2] ON [Extent1].[Ref_ID] = [Extent2].[ID]
        UNION ALL
            SELECT 
            1 AS [C1], 
            [Extent3].[Name] AS [Name], 
            [Extent4].[Name] AS [Name1]
            FROM  [dbo].[Customer] AS [Extent3]
            INNER JOIN [dbo].[Ref] AS [Extent4] ON [Extent3].[Ref_ID2] = [Extent4].[ID2]) AS [UnionAll1]
    )  AS [Limit1]

Och, yn LINQ-fragen kin d'r mar ien join-betingst wêze, dus hjir is it mooglik om in lykweardige query te meitsjen mei twa queries foar elke betingst en se dan te kombinearjen fia Union om duplikaten tusken de rigen te ferwiderjen.
Ja, de fragen sille oer it generaal net lykweardich wêze, rekken hâldend mei dat folsleine dûbele rigen weromjûn wurde kinne. Yn it echte libben binne folsleine dûbele rigels lykwols net nedich en minsken besykje se kwyt te reitsjen.

Litte wy no de útfieringsplannen fan dizze twa fragen fergelykje:

  1. foar CROSS JOIN is de gemiddelde útfieringstiid 195 sekonden:
    Guon aspekten fan it optimalisearjen fan LINQ-fragen yn C#.NET foar MS SQL Server
  2. foar INNER JOIN-UNION is de gemiddelde útfieringstiid minder dan 24 sekonden:
    Guon aspekten fan it optimalisearjen fan LINQ-fragen yn C#.NET foar MS SQL Server

Lykas jo kinne sjen út 'e resultaten, foar twa tabellen mei miljoenen records, is de optimalisearre LINQ-query in protte kearen rapper dan de net-optimisearre.

Foar de opsje mei EN yn 'e betingsten, in LINQ-fraach fan it formulier:

LINQ fraach

var query = from e1 in db.Customer
                            from e2 in db.Ref
                            where (e1.Ref_ID == e2.ID)
                                 && (e1.Ref_ID2 == e2.ID2)
                            select new { Data1 = e1.Name, Data2 = e2.Name };

De juste SQL-query sil hast altyd wurde generearre, dy't gemiddeld yn sawat 1 sekonde sil rinne:

Guon aspekten fan it optimalisearjen fan LINQ-fragen yn C#.NET foar MS SQL Server
Ek foar LINQ to Objects manipulaasjes ynstee fan in query lykas:

LINQ-fraach (1e opsje)

var query = from e1 in seq1
                            from e2 in seq2
                            where (e1.Key1==e2.Key1)
                               && (e1.Key2==e2.Key2)
                            select new { Data1 = e1.Data, Data2 = e2.Data };

jo kinne in query brûke lykas:

LINQ-fraach (2e opsje)

var query = from e1 in seq1
                            join e2 in seq2
                            on new { e1.Key1, e1.Key2 } equals new { e2.Key1, e2.Key2 }
                            select new { Data1 = e1.Data, Data2 = e2.Data };

wêr:

It definiearjen fan twa arrays

Para[] seq1 = new[] { new Para { Key1 = 1, Key2 = 2, Data = "777" }, new Para { Key1 = 2, Key2 = 3, Data = "888" }, new Para { Key1 = 3, Key2 = 4, Data = "999" } };
Para[] seq2 = new[] { new Para { Key1 = 1, Key2 = 2, Data = "777" }, new Para { Key1 = 2, Key2 = 3, Data = "888" }, new Para { Key1 = 3, Key2 = 5, Data = "999" } };

, en it Para-type wurdt as folget definiearre:

Para Type Definysje

class Para
{
        public int Key1, Key2;
        public string Data;
}

Sa hawwe wy guon aspekten ûndersocht by it optimalisearjen fan LINQ-fragen nei MS SQL Server.

Spitigernôch ferjitte sels betûfte en liedende .NET-ûntwikkelders dat se moatte begripe wat de ynstruksjes dy't se brûke dogge efter de skermen. Oars wurde se konfigurators en kinne yn 'e takomst in tiidbom plantsje, sawol by it skaaljen fan de software-oplossing as mei lytse feroaringen yn eksterne omjouwingsomstannichheden.

Der is ek in koarte besprek dien hjir.

De boarnen foar de test - it projekt sels, it oanmeitsjen fan tabellen yn 'e TEST-databank, lykas it ynfoljen fan dizze tabellen mei gegevens lizze hjir.
Ek yn dit repository, yn 'e map Plans, binne d'r plannen foar it útfieren fan queries mei OR-betingsten.

Boarne: www.habr.com

Add a comment