LINQ memasukkan .NET sebagai bahasa manipulasi data baru yang kuat. LINQ ke SQL sebagai bagiannya memungkinkan Anda berkomunikasi dengan DBMS dengan nyaman menggunakan, misalnya, Entity Framework. Namun, cukup sering menggunakannya, pengembang lupa melihat jenis kueri SQL apa yang akan dihasilkan oleh penyedia kueri, dalam kasus Anda Entity Framework.
Mari kita lihat dua poin utama dengan menggunakan sebuah contoh.
Untuk melakukannya, buat database Uji di SQL Server, dan buat dua tabel di dalamnya menggunakan kueri berikut:
Membuat tabel
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
Sekarang mari kita isi tabel Ref dengan menjalankan skrip berikut:
Mengisi tabel 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
Mari kita isi tabel Pelanggan dengan cara yang sama menggunakan skrip berikut:
Mengisi tabel Pelanggan
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
Jadi, kami menerima dua tabel, salah satunya memiliki lebih dari 1 juta baris data, dan yang lainnya memiliki lebih dari 10 juta baris data.
Sekarang di Visual Studio Anda perlu membuat proyek pengujian Aplikasi Konsol Visual C# (.NET Framework):
Selanjutnya, Anda perlu menambahkan perpustakaan untuk Entity Framework untuk berinteraksi dengan database.
Untuk menambahkannya, klik kanan pada proyek dan pilih Kelola Paket NuGet dari menu konteks:
Kemudian, pada jendela manajemen paket NuGet yang muncul, masukkan kata βEntity Frameworkβ di jendela pencarian dan pilih paket Entity Framework dan instal:
Selanjutnya, di file App.config, setelah menutup elemen configSections, Anda perlu menambahkan blok berikut:
<connectionStrings>
<add name="DBConnection" connectionString="data source=ΠΠΠ―_ΠΠΠΠΠΠΠΠ―Π Π_MSSQL;Initial Catalog=TEST;Integrated Security=True;" providerName="System.Data.SqlClient" />
</connectionStrings>
Di connectionString Anda harus memasukkan string koneksi.
Sekarang mari kita buat 3 antarmuka dalam file terpisah:
- Menerapkan antarmuka IBaseEntityID
namespace TestLINQ { public interface IBaseEntityID { int ID { get; set; } } }
- Implementasi antarmuka IBaseEntityName
namespace TestLINQ { public interface IBaseEntityName { string Name { get; set; } } }
- Implementasi antarmuka IBaseNameInsertUTCDate
namespace TestLINQ { public interface IBaseNameInsertUTCDate { DateTime InsertUTCDate { get; set; } } }
Dan dalam file terpisah kita akan membuat kelas dasar BaseEntity untuk dua entitas kita, yang akan mencakup bidang umum:
Implementasi kelas dasar BaseEntity
namespace TestLINQ
{
public class BaseEntity : IBaseEntityID, IBaseEntityName, IBaseNameInsertUTCDate
{
public int ID { get; set; }
public string Name { get; set; }
public DateTime InsertUTCDate { get; set; }
}
}
Selanjutnya, kita akan membuat dua entitas dalam file terpisah:
- Implementasi kelas Ref
using System.ComponentModel.DataAnnotations.Schema; namespace TestLINQ { [Table("Ref")] public class Ref : BaseEntity { public int ID2 { get; set; } } }
- Implementasi kelas Pelanggan
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; } } }
Sekarang mari buat konteks UserContext di file terpisah:
Implementasi kelas UserContex
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; }
}
}
Kami menerima solusi siap pakai untuk melakukan tes optimasi dengan LINQ ke SQL melalui EF untuk MS SQL Server:
Sekarang masukkan kode berikut ke dalam file Program.cs:
File 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<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();
}
}
}
}
Selanjutnya, mari kita luncurkan proyek kita.
Di akhir pekerjaan, yang berikut ini akan ditampilkan di konsol:
Kueri SQL yang dihasilkan
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])
Artinya, secara umum, kueri LINQ menghasilkan kueri SQL ke DBMS MS SQL Server dengan cukup baik.
Sekarang mari kita ubah kondisi AND menjadi OR pada query LINQ:
permintaan LINQ
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 };
Dan mari luncurkan aplikasi kita lagi.
Eksekusi akan terhenti karena kesalahan karena waktu eksekusi perintah melebihi 30 detik:
Jika Anda melihat kueri yang dihasilkan oleh LINQ:
, maka Anda dapat memastikan bahwa pemilihan terjadi melalui perkalian Cartesian dari dua himpunan (tabel):
Kueri SQL yang dihasilkan
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]
Mari kita tulis ulang query LINQ sebagai berikut:
Kueri LINQ yang dioptimalkan
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 });
Kemudian kita mendapatkan query SQL berikut:
kueri SQL
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]
Sayangnya, dalam kueri LINQ hanya ada satu kondisi gabungan, jadi di sini dimungkinkan untuk membuat kueri yang setara menggunakan dua kueri untuk setiap kondisi dan kemudian menggabungkannya melalui Union untuk menghapus duplikat di antara baris.
Ya, kueri umumnya tidak setara, dengan mempertimbangkan bahwa baris duplikat lengkap dapat dikembalikan. Namun, dalam kehidupan nyata, garis duplikat yang lengkap tidak diperlukan dan orang-orang mencoba untuk menghilangkannya.
Sekarang mari kita bandingkan rencana eksekusi kedua kueri ini:
- untuk CROSS JOIN waktu eksekusi rata-rata adalah 195 detik:
- untuk INNER JOIN-UNION waktu eksekusi rata-rata kurang dari 24 detik:
.
Juga di repositori ini, di folder Plans, terdapat rencana untuk mengeksekusi query dengan kondisi OR.
Sumber: www.habr.com