LINQ eniris .NET kiel potenca nova datummanipula lingvo. LINQ al SQL kiel parto de ĝi permesas vin sufiĉe oportune komuniki kun DBMS uzante, ekzemple, Entity Framework. Tamen, uzante ĝin sufiĉe ofte, programistoj forgesas rigardi kian SQL-demandon la demandebla provizanto, en via kazo Entity Framework, generos.
Ni rigardu du ĉefajn punktojn uzante ekzemplon.
Por fari tion, kreu Testan datumbazon en SQL-Servilo, kaj kreu du tabelojn en ĝi uzante la jenan demandon:
Kreante tabelojn
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
Nun ni plenigu la Ref-tabelon rulante la sekvan skripton:
Plenigante la Ref-tabelon
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
Ni simile plenigu la tabelon de Kliento uzante la jenan skripton:
Plenigante la tabelon de Kliento
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
Tiel, ni ricevis du tabelojn, unu el kiuj havas pli ol 1 milionon da vicoj da datumoj, kaj la alia havas pli ol 10 milionojn da vicoj da datumoj.
Nun en Visual Studio vi devas krei provan projekton de Visual C# Console App (.NET Framework):
Poste, vi devas aldoni bibliotekon por la Enta Kadro por interagi kun la datumbazo.
Por aldoni ĝin, dekstre alklaku la projekton kaj elektu Administri NuGet-Pakojn el la kunteksta menuo:
Poste, en la fenestro de administrado de pakaĵoj de NuGet, kiu aperas, enigu la vorton "Entity Framework" en la serĉfenestron kaj elektu la pakaĵon Entity Framework kaj instalu ĝin:
Poste, en la dosiero App.config, post fermo de la elemento configSections, vi devas aldoni la sekvan blokon:
<connectionStrings>
<add name="DBConnection" connectionString="data source=ИМЯ_ЭКЗЕМПЛЯРА_MSSQL;Initial Catalog=TEST;Integrated Security=True;" providerName="System.Data.SqlClient" />
</connectionStrings>
En connectionString vi devas enigi la konektan ĉenon.
Nun ni kreu 3 interfacojn en apartaj dosieroj:
- Efektivigo de la IBaseEntityID-interfaco
namespace TestLINQ { public interface IBaseEntityID { int ID { get; set; } } }
- Efektivigo de la IBaseEntityName-interfaco
namespace TestLINQ { public interface IBaseEntityName { string Name { get; set; } } }
- Efektivigo de la IBaseNameInsertUTCDate-interfaco
namespace TestLINQ { public interface IBaseNameInsertUTCDate { DateTime InsertUTCDate { get; set; } } }
Kaj en aparta dosiero ni kreos bazan klason BaseEntity por niaj du estaĵoj, kiuj inkluzivos komunajn kampojn:
Efektivigo de la baza klaso BaseEntity
namespace TestLINQ
{
public class BaseEntity : IBaseEntityID, IBaseEntityName, IBaseNameInsertUTCDate
{
public int ID { get; set; }
public string Name { get; set; }
public DateTime InsertUTCDate { get; set; }
}
}
Poste, ni kreos niajn du entojn en apartaj dosieroj:
- Efektivigo de la Ref-klaso
using System.ComponentModel.DataAnnotations.Schema; namespace TestLINQ { [Table("Ref")] public class Ref : BaseEntity { public int ID2 { get; set; } } }
- Efektivigo de la Klaso Kliento
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; } } }
Nun ni kreu kuntekston de UserContext en aparta dosiero:
Efektivigo de la UserContex klaso
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; }
}
}
Ni ricevis pretan solvon por fari optimumigajn testojn kun LINQ to SQL per EF por MS SQL Server:
Nun enigu la sekvan kodon en la dosieron Program.cs:
Program.cs dosiero
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();
}
}
}
}
Poste, ni lanĉu nian projekton.
Ĉe la fino de la laboro, la jena aperos sur la konzolo:
Generita SQL-Demando
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])
Tio estas, ĝenerale, la LINQ-demando generis SQL-demandon al la MS SQL Server DBMS sufiĉe bone.
Nun ni ŝanĝu la AND-kondiĉon al AŬ en la LINQ-demando:
LINQ-demando
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 };
Kaj ni lanĉu nian aplikaĵon denove.
La ekzekuto kraŝos kun eraro pro la komanda ekzekuttempo superanta 30 sekundojn:
Se vi rigardas la demandon, kiu estis generita de LINQ:
, tiam vi povas certigi, ke la elekto okazas per la kartezia produkto de du aroj (tabeloj):
Generita SQL-Demando
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]
Ni reverku la LINQ-demandon jene:
Optimumigita LINQ-demando
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 });
Tiam ni ricevas la sekvan SQL-demandon:
SQL-demando
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]
Ve, en LINQ-demandoj povas esti nur unu kunigkondiĉo, do ĉi tie eblas fari ekvivalentan demandon uzante du demandojn por ĉiu kondiĉo kaj poste kombinante ilin per Union por forigi duplikatojn inter la vicoj.
Jes, la demandoj ĝenerale estos ne-ekvivalentaj, konsiderante ke kompletaj duplikataj vicoj povas esti resenditaj. Tamen, en la reala vivo, kompletaj duobligitaj linioj ne estas bezonataj kaj homoj provas forigi ilin.
Nun ni komparu la ekzekutplanojn de ĉi tiuj du demandoj:
- por CROSS JOIN la averaĝa ekzekuttempo estas 195 sekundoj:
- por INNER JOIN-UNION la averaĝa ekzekuttempo estas malpli ol 24 sekundoj:
Kiel vi povas vidi el la rezultoj, por du tabeloj kun milionoj da rekordoj, la optimumigita LINQ-demando estas multajn fojojn pli rapida ol la neoptimumigita.
Por la opcio kun AND en la kondiĉoj, LINQ-demando de la formo:
LINQ-demando
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 };
La ĝusta SQL-demando preskaŭ ĉiam estos generita, kiu funkcios averaĝe en ĉirkaŭ 1 sekundo:
Ankaŭ por manipuladoj de LINQ to Objects anstataŭ demando kiel:
Demando LINQ (unua opcio)
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 };
vi povas uzi demandon kiel:
Demando LINQ (unua opcio)
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 };
kie:
Difinante du tabelojn
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" } };
, kaj la Para-tipo estas difinita jene:
Para Tipo Difino
class Para
{
public int Key1, Key2;
public string Data;
}
Tiel, ni ekzamenis kelkajn aspektojn en optimumigado de LINQ-demandoj al MS SQL Server.
Bedaŭrinde, eĉ spertaj kaj gvidaj .NET-programistoj forgesas, ke ili devas kompreni, kion faras la instrukcioj, kiujn ili uzas, malantaŭ la scenoj. Alie, ili fariĝas agordiloj kaj povas planti horloĝbombon en la estonteco kaj dum grimpado de la programara solvo kaj kun etaj ŝanĝoj en eksteraj mediaj kondiĉoj.
Mallonga revizio ankaŭ estis farita
La fontoj por la testo - la projekto mem, la kreado de tabeloj en la TEST-datumbazo, same kiel plenigi ĉi tiujn tabelojn per datumoj troviĝas.
Ankaŭ en ĉi tiu deponejo, en la dosierujo Planoj, estas planoj por efektivigi demandojn kun AŬ kondiĉoj.
fonto: www.habr.com