MÔned aspektid LINQ-pÀringute optimeerimisest C#.NET-ile MS SQL Serveri jaoks

LINQ tuli .NET-i uue vĂ”imsa andme manipuleerimise keeles. LINQ to SQL, selle osana, vĂ”imaldab mugavalt suhelda andmebaasidega nĂ€iteks Entity Frameworki kaudu. Kuid tihti, seda kasutades, unustavad arendajad vaadata, milline SQL-pĂ€ring genereeritakse queryable provider'i poolt, teie puhul — Entity Framework.

Vaatame kaht pÔhiaspekti nÀite kaudu.
Selleks loome SQL Serveris andmebaasi Test ja loome seal kaks tabelit jÀrgmise pÀringu abil:

Tabelite loomine

KASUTA [TEST]
GO

SEADKE ANSI_NULLS ON
GO

SEADKE QUOTED_IDENTIFIER ON
GO

LOO KREATE 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

KASUTA [TEST]
GO

SEADKE ANSI_NULLS ON
GO

SEADKE QUOTED_IDENTIFIER ON
GO

LOO KREATE 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

NĂŒĂŒd tĂ€itke tabel Ref jĂ€rgmise skripti kĂ€ivitamisega:

Tabeli Ref tÀitmine

KASUTA [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

Samamoodi tÀidame tabeli Customer jÀrgmise skripti abil:

Tabeli Customer tÀitmine

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

Nii sai meil kaks tabelit, millest ĂŒhes on ĂŒle 1 miljon rida andmeid ja teises ĂŒle 10 miljoni rida andmeid.

NĂŒĂŒd tuleb Visual Studios luua testprojekt Visual C# Console App (.NET Framework):

MÔned aspektid LINQ-pÀringute optimeerimisest C#.NET-ile MS SQL Serveri jaoks

SeejÀrel peab andmebaasiga suhtlemiseks lisama Entity Framework'i teegi.
Selle lisamiseks paremklĂ”psame projektil ja valime kontekstimenĂŒĂŒst Manage NuGet Packages:

MÔned aspektid LINQ-pÀringute optimeerimisest C#.NET-ile MS SQL Serveri jaoks

SeejĂ€rel sisestame avanevas NuGet-pakettide haldamise aknas otsinguvĂ€ljas sĂ”na „Entity Framework” ja valime paketi Entity Framework ning installime selle:

MÔned aspektid LINQ-pÀringute optimeerimisest C#.NET-ile MS SQL Serveri jaoks

SeejÀrel tuleb failis App.config configSections elemendi sulgemise jÀrel lisada jÀrgmine plokk:


Tuleb connectionStringi kirjutada ĂŒhenduse string.

NĂŒĂŒd loome eraldi failides 3 liidest:

  1. IBaseEntityID liidese rakendamine
    namespace TestLINQ
    {
        public interface IBaseEntityID
        {
            int ID { get; set; }
        }
    }
    

  2. IBaseEntityName liidese rakendamine
    namespace TestLINQ
    {
        public interface IBaseEntityName
        {
            string Name { get; set; }
        }
    }
    

  3. IBaseNameInsertUTCDate liidese rakendamine
    namespace TestLINQ
    {
        public interface IBaseNameInsertUTCDate
        {
            DateTime InsertUTCDate { get; set; }
        }
    }
    

Ja eraldi failis loome baasklass BaseEntity, kuhu kuuluvad ĂŒhised vĂ€ljad:

BaseEntity baasklassi rakendamine

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

SeejĂ€rel loome eraldi failides meie kaks entsĂŒsteemi:

  1. Ref klassi rakendamine
    using System.ComponentModel.DataAnnotations.Schema;
    
    namespace TestLINQ
    {
        [Table("Ref")]
        public class Ref : BaseEntity
        {
            public int ID2 { get; set; }
        }
    }
    

  2. Customer klassi rakendamine
    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; }
        }
    }
    

NĂŒĂŒd loome eraldi failis konteksti UserContext:

Klassi UserContext rakendamine

using System.Data.Entity;

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

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

Oleme saanud valmis lahenduse testimiseks LINQ to SQL kaudu EF MS SQL Serverile optimeerimiseks:

MÔned aspektid LINQ-pÀringute optimeerimisest C#.NET-ile MS SQL Serveri jaoks

NĂŒĂŒd sisestame faili Program.cs jĂ€rgmise koodi:

Fail Program.cs

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

SeejÀrel kÀivitame meie projekti.

Töö lÔpus kuvatakse konsoolil:

Genereeritud SQL-pÀring

VALI TOP (1000) 
    [Extent1].[Ref_ID] NAG [Ref_ID], 
    [Extent1].[Name] NAG [Name], 
    [Extent2].[Name] NAG [Name1]
    FROM  [dbo].[Customer] NAG [Extent1]
    SISELIKE [dbo].[Ref] NAG [Extent2] ON ([Extent1].[Ref_ID] = [Extent2].[ID]) JA ([Extent1].[Ref_ID2] = [Extent2].[ID2])

Nagu öeldud, on LINQ pÀring tÔeliselt hÀsti genereerinud SQL pÀringu MS SQL Serveri andmebaasi.

NĂŒĂŒd muudame tingimuse 'JA' peale 'VÕI' LINQ pĂ€ringus:

LINQ-pÀring

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 };

Ja kÀivitame uuesti meie rakenduse.

TĂ€idamine katkeb veateatega, mis on seotud kĂ€su tĂ€itmise aja ĂŒletamisega 30 sekundi jooksul:

MÔned aspektid LINQ-pÀringute optimeerimisest C#.NET-ile MS SQL Serveri jaoks

Kui vaadata, milline pÀring on selle kÀigus genereeritud LINQ:

MÔned aspektid LINQ-pÀringute optimeerimisest C#.NET-ile MS SQL Serveri jaoks
, siis saab veenduda, et valik tehakse kahe hulkade (tabelite) dekartaalsete korrutiste kaudu:

Genereeritud SQL-pÀring

VALI TOP (1000) 
    [Extent1].[Ref_ID] NAG [Ref_ID], 
    [Extent1].[Name] NAG [Name], 
    [Extent2].[Name] NAG [Name1]
    FROM  [dbo].[Customer] NAG [Extent1]
    RISTI ÜHENDUS [dbo].[Ref] NAG [Extent2]
    KUS [Extent1].[Ref_ID] = [Extent2].[ID] VÕI [Extent1].[Ref_ID2] = [Extent2].[ID2]

KĂŒsimus on, kuidas saaksime LINQ pĂ€ringu jĂ€rgmiselt ĂŒmber kirjutada:

Optimeeritud LINQ pÀring

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

Siis saame jÀrgmise SQL-pÀringu:

SQL-pÀring

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]

Kahjuks saavad LINQ-pĂ€ringutes ĂŒhenduse tingimuseks olla ainult ĂŒhed, mistĂ”ttu on siin vĂ”imalik teha ekvivalentne pĂ€ring kahes eraldi pĂ€ringus, millest kummagi tingimuse kohaselt jĂ€rgnevalt nende ĂŒhendamine Union-iga, et eemaldada ridu duplikaadid.
Jah, pĂ€ringud on ĂŒldiselt mittesarnased, arvestades, et vĂ”ivad tagasi anda tĂ€ielikke ridade koopiaid. Siiski, reaalses elus ei ole tĂ€ielikud kopeeritud read vajalikud ja neist pĂŒĂŒab lahti saada.

VĂ”rdleme nĂŒĂŒd nende kahe pĂ€ringu tĂ€itmise plaane:

  1. CROSS JOINi puhul on keskmine tÀitmise aeg 195 sek:
    MÔned aspektid LINQ-pÀringute optimeerimisest C#.NET-ile MS SQL Serveri jaoks
  2. INNER JOIN-UNION puhul on keskmine tÀitmise aeg alla 24 sek:
    MÔned aspektid LINQ-pÀringute optimeerimisest C#.NET-ile MS SQL Serveri jaoks

Nagu nÀhtub tulemustest, töötab optimeeritud LINQ-pÀring kahel miljonite ridade tabelil mitu korda kiiremini kui optimeerimata.

LINQ-pÀringu tingimuste puhul, kus on ja:

LINQ-pÀring

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 };

peaaegu alati genereeritakse Ôige SQL-pÀring, mis tÀitub keskmiselt umbes 1 sek:

MÔned aspektid LINQ-pÀringute optimeerimisest C#.NET-ile MS SQL Serveri jaoks
Lisaks LINQ to Objects manipulatsioonide jaoks, erinevalt pÀringu vormist:

LINQ-pÀring (1. variant)

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 };

vÔib kasutada pÀringut jÀrgmisel kujul:

LINQ-pÀring (2. variant)

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 };

kus:

Kahte massiivi mÀÀratlemine

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" } };

, ja tĂŒĂŒp Para on mÀÀratletud jĂ€rgmiselt:

TĂŒĂŒbi Para mÀÀratlemine

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

Nii oleme kÀsitlenud mÔningaid aspekte LINQ-pÀringute optimeerimisel MS SQL Serveris.

Kahjuks unustavad isegi kogenud ja juhtivad .NET-arendajad aru saada, mida teavad nende kasutatavad kÀsklused. Vastasel juhul muutuvad nad seadistajateks ja vÔivad tulevikus luua ajaliselt piiritletud probleemi nii programmilahenduse laiendamisel kui ka keskkonna tingimuste vÀiksematel muutustel.

Samuti viidi lĂ€bi vĂ€ike ĂŒlevaade siit.

Testi lĂ€htekood – ise projekt, TEST andmebaasi tabelite loomine ja nende tabelite andmetega tĂ€itmine on siit.
Selles hoidlas, kaustas Plans, on samuti pÀringute tÀitmise plaanid, mille tingimuseks on OR.

Allikas: habr.com

Osta usaldusvÀÀrne veebihosting DDoS kaitsega, VPS VDS serverid đŸ”„ Osta usaldusvÀÀrne veebihosting DDoS kaitsega, VPS VDS serverid | ProHoster