LINQ hyri nĂ« .NET si njĂ« gjuhĂ« e re e fuqishme pĂ«r manipulimin e tĂ« dhĂ«nave. LINQ to SQL si pjesĂ« e tij lejon komunikim tĂ« lehtĂ« me DBMS me anĂ« tĂ«, pĂ«r shembull, Entity Framework. MegjithatĂ«, shpesh ndodh qĂ« zhvilluesit harrojnĂ« tĂ« kontrollojnĂ« se çfarĂ« lloj pyetje SQL do tĂ« generejĂ« ofruesi i queryable, nĂ« rastin tuaj â Entity Framework.
Le të shqyrtojmë dy pika kryesore me një shembull.
Për këtë, do të krijojmë një bazë të dhënash Test në SQL Server, dhe në të do të krijojmë dy tabela me anë të pyetjes së mëposhtme:
Krijimi i tabelave
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
Tani, le të mbushim tabelën Ref duke ekzekutuar skriptin e mëposhtem:
Mbushja e tabelës Ref
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
Po ashtu, do ta mbushim tabelën Customer me anë të skriptit të mëposhtëm:
Mbushja e tabelës Customer
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
Kështu, ne morëm dy tabela, njëra me më shumë se 1 milion rreshta të dhënash, dhe tjetra me më shumë se 10 milion rreshta të dhënash.
Tani në Visual Studio, është e nevojshme të krijoni një projekt testues Visual C# Console App (.NET Framework):

Më pas, duhet të shtoni një bibliotekë për Entity Framework për ndërveprimin me bazën e të dhënave.
Për ta shtuar atë, do të klikojmë me një kliku të djathtë mbi projektin dhe do të zgjedhim në menunë kontekstuale Menaxho Paketat NuGet:

Më pas, në dritaren e menaxhimit të paketave NuGet që shfaqet, do të shkruajmë "Entity Framework" në kutinë e kërkimit dhe do të zgjedhim paketën Entity Framework dhe do ta instalojmë atë:

Pastaj, në skedarin App.config, pas mbylljes së elementit configSections, duhet të shtojmë bllokun e mëposhtëm:
Në connectionString duhet të shkruajmë vargun e lidhjes.
Tani do të krijojmë 3 interface në skeda të ndara:
- Implementimi i interfesës IBaseEntityID
namespace TestLINQ { public interface IBaseEntityID { int ID { get; set; } } } - Implementimi i interfesës IBaseEntityName
namespace TestLINQ { public interface IBaseEntityName { string Name { get; set; } } } - Implementimi i interfesës IBaseNameInsertUTCDate
namespace TestLINQ { public interface IBaseNameInsertUTCDate { DateTime InsertUTCDate { get; set; } } }
Dhe në një skedë të veçantë do të krijojmë klasën bazë BaseEntity për dy entitetet tona, e cila do të përmbajë fushat e përbashkëta:
Implementimi i klasës bazë BaseEntity
namespace TestLINQ
{
public class BaseEntity : IBaseEntityID, IBaseEntityName, IBaseNameInsertUTCDate
{
public int ID { get; set; }
public string Name { get; set; }
public DateTime InsertUTCDate { get; set; }
}
}
Më pas, në skeda të ndara do të krijojmë dy entitetet tona:
- Implementimi i klasës Ref
using System.ComponentModel.DataAnnotations.Schema; namespace TestLINQ { [Table("Ref")] public class Ref : BaseEntity { public int ID2 { get; set; } } } - Implementimi i klasës Customer
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; } } }
Tani do të krijojmë kontekstin UserContext në një skedë të veçantë:
Implementimi i klasës UserContext
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; }
}
}
Kemi marrë një zgjidhje të gatshme për të kryer teste optimizimi me LINQ to SQL përmes EF për MS SQL Server:

Tani, në skedarin Program.cs, do të shkruajmë kodin e mëposhtëm:
Skedari 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();
}
}
}
}
Në vazhdim do të fillojmë projektin tonë.
Në fund të ekzekutimit do të shfaqet në konsolë:
Kërkesa SQL e gjeneruar
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])
Pra, në përgjithësi, kërkesa LINQ gjeneroi një kërkesë SQL mjaft të mirë për DBMS MS SQL Server.
Tani do të ndryshojmë kushtin AND në OR në kërkesën LINQ:
duhet të bëhet strukturisht identike me optimale
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 };
Dhe përsëri do të ekzekutojmë aplikacionin tonë.
Ekzekutimi do të dështojë me një gabim që lidhet me tejkalimin e kohës së ekzekutimit të komandës prej 30 sekondash:

Nëse shqyrtojmë se cila kërkesë u gjenerua nga LINQ:

, mund të sigurohemi se përzgjedhja po bëhet përmes produktit kartesian të dy grupeve (tabelave):
Kërkesa SQL e gjeneruar
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]
Le të shkruajmë përsëri kërkesën LINQ si më poshtë:
Kërkesa LINQ e optimizuar
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 });
Atëherë do të marrim kërkesën e mëposhtme SQL:
SQL-kërkesës
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]
Fatkeqësisht, në kërkesat LINQ, kushti i bashkimit mund të jetë vetëm një, kështu që këtu është e mundur të bëni një kërkesë ekuivalente përmes dy kërkesave sipas çdo kushti dhe më pas t'i bashkoni ato përmes Union për të hequr dublikata midis rreshtave.
Po, kërkesat në përgjithësi do të rezultojnë të jenë jo ekuivalente, duke marrë parasysh se mund të kthehen rreshta të plotë dublikat. Megjithatë, në jetë reale, rreshtat e plota dublikuar nuk janë të nevojshme dhe përpiqen të hiqen.
Tani le të krahasojmë planet e ekzekutimit të këtyre dy kërkesave:
- për CROSS JOIN, koha mesatare e ekzekutimit është 195 sekonda:

- për INNER JOIN-UNION, koha mesatare e ekzekutimit është më pak se 24 sekonda:

Siç vërehet nga rezultatet, për dy tabela me miliona regjistrime, kërkesa LINQ e optimizuar punon shumë më shpejt se e paoptimizuara.
Për variantin me AND në kushtet e kërkesës LINQ si:
duhet të bëhet strukturisht identike me optimale
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 };
përgjithësisht do të gjenerohet një kërkesë SQL e saktë, e cila do të ketë një kohë mesatare ekzekutimi prej rreth 1 sekonde:

Po ashtu, për manipulimet LINQ to Objects përveç kërkesës si:
Kërkesa LINQ (varianti i parë)
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 };
mund të përdorim një kërkesë si:
Kërkesa LINQ (varianti i dytë)
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 };
ku:
Përkufizimi i dy fushave
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" } };
, dhe tipi Para përcaktohet si më poshtë:
Përkufizimi i tipit Para
class Para
{
public int Key1, Key2;
public string Data;
}
Dhe kështu kemi shqyrtuar disa aspekte të optimizimit të kërkesave LINQ për MS SQL Server.
Fatkeqësisht, edhe zhvilluesit më të përvojë dhe liderët e .NET harrojnë që është e nevojshme të kuptojnë se çfarë bëjnë pas skenës ato instruksione që ata përdorin. Në të kundërt, ata bëhen konfigurues dhe mund të vunë një bombë me veprim të ngadalshëm në të ardhmen, si gjatë zgjerimit të zgjidhjes software, ashtu edhe në kushte të jashtme të ndryshme.
Po ashtu u krye një përmbledhje e vogël dhe .
Burimet për testin-së projektit, krijimi i tabelave në bazën e të dhënave TEST, si dhe mbushja e këtyre tabelave ndodhen .
Po ashtu në këtë depo, në dosjen Plans ndodhen planet e realizimit të kërkesave me kushte OSE.
Burimi: habr.com


