MS SQL সার্ভারের জন্য C#.NET-এ LINQ ক্যোয়ারী অপ্টিমাইজেশানের কিছু দিক

LINQ একটি শক্তিশালী নতুন ডেটা ম্যানিপুলেশন ভাষা হিসাবে .NET প্রবেশ করেছে। এটির অংশ হিসাবে LINQ থেকে SQL আপনাকে DBMS-এর সাথে বেশ সুবিধাজনকভাবে যোগাযোগ করতে দেয়, উদাহরণস্বরূপ, এন্টিটি ফ্রেমওয়ার্ক। যাইহোক, প্রায়শই এটি ব্যবহার করে, বিকাশকারীরা আপনার ক্ষেত্রে, সত্তা ফ্রেমওয়ার্ক, কোয়েরিযোগ্য প্রদানকারী কী ধরনের SQL কোয়েরি তৈরি করবে তা দেখতে ভুলে যান।

একটি উদাহরণ সহ দুটি প্রধান পয়েন্ট দেখা যাক।
এটি করার জন্য, এসকিউএল সার্ভারে আমরা একটি ডাটাবেস টেস্ট তৈরি করব এবং এতে আমরা নিম্নলিখিত ক্যোয়ারী ব্যবহার করে দুটি টেবিল তৈরি করব:

টেবিল তৈরি করা হচ্ছে

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

এখন নিম্নোক্ত স্ক্রিপ্টটি চালানোর মাধ্যমে রেফ টেবিলটি পূরণ করা যাক:

টেবিল ভর্তি রেফ

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

আসুন নিম্নলিখিত স্ক্রিপ্টটি ব্যবহার করে একইভাবে গ্রাহক টেবিলটি পূরণ করি:

গ্রাহক টেবিল জনবহুল

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

এইভাবে, আমরা দুটি টেবিল পেয়েছি, যার একটিতে 1 মিলিয়নের বেশি সারি ডেটা রয়েছে এবং অন্যটিতে 10 মিলিয়নেরও বেশি সারি ডেটা রয়েছে।

এখন ভিজ্যুয়াল স্টুডিওতে, আপনাকে একটি পরীক্ষামূলক প্রকল্প তৈরি করতে হবে ভিজ্যুয়াল C# কনসোল অ্যাপ (.NET ফ্রেমওয়ার্ক):

MS SQL সার্ভারের জন্য C#.NET-এ LINQ ক্যোয়ারী অপ্টিমাইজেশানের কিছু দিক

এর পরে, ডাটাবেসের সাথে ইন্টারঅ্যাক্ট করার জন্য আপনাকে এন্টিটি ফ্রেমওয়ার্কের জন্য একটি লাইব্রেরি যোগ করতে হবে।
এটি যোগ করতে, প্রকল্পে ডান-ক্লিক করুন এবং প্রসঙ্গ মেনু থেকে NuGet প্যাকেজ পরিচালনা করুন নির্বাচন করুন:

MS SQL সার্ভারের জন্য C#.NET-এ LINQ ক্যোয়ারী অপ্টিমাইজেশানের কিছু দিক

তারপরে, NuGet প্যাকেজ পরিচালনার উইন্ডোতে যেটি প্রদর্শিত হবে, অনুসন্ধান বাক্সে, "এন্টিটি ফ্রেমওয়ার্ক" শব্দটি লিখুন এবং সত্তা ফ্রেমওয়ার্ক প্যাকেজটি নির্বাচন করুন এবং এটি ইনস্টল করুন:

MS SQL সার্ভারের জন্য C#.NET-এ LINQ ক্যোয়ারী অপ্টিমাইজেশানের কিছু দিক

এরপরে, App.config ফাইলে, configSection উপাদানটি বন্ধ করার পরে, নিম্নলিখিত ব্লকটি যুক্ত করুন:

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

সংযোগ স্ট্রিং-এ আপনাকে সংযোগ স্ট্রিং লিখতে হবে।

এখন আলাদা ফাইলে 3টি ইন্টারফেস তৈরি করা যাক:

  1. IBaseEntityID ইন্টারফেসের বাস্তবায়ন
    namespace TestLINQ
    {
        public interface IBaseEntityID
        {
            int ID { get; set; }
        }
    }
    

  2. IBaseEntityName ইন্টারফেসের বাস্তবায়ন
    namespace TestLINQ
    {
        public interface IBaseEntityName
        {
            string Name { get; set; }
        }
    }
    

  3. IBaseNameInsertUTCDate ইন্টারফেসের বাস্তবায়ন
    namespace TestLINQ
    {
        public interface IBaseNameInsertUTCDate
        {
            DateTime InsertUTCDate { get; set; }
        }
    }
    

এবং একটি পৃথক ফাইলে, আমরা আমাদের দুটি সত্তার জন্য একটি BaseEntity বেস ক্লাস তৈরি করব, যা সাধারণ ক্ষেত্রগুলি অন্তর্ভুক্ত করবে:

বেস ক্লাস BaseEntity বাস্তবায়ন

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

এর পরে, পৃথক ফাইলে, আমরা আমাদের দুটি সত্তা তৈরি করব:

  1. রেফ ক্লাস বাস্তবায়ন
    using System.ComponentModel.DataAnnotations.Schema;
    
    namespace TestLINQ
    {
        [Table("Ref")]
        public class Ref : BaseEntity
        {
            public int ID2 { get; set; }
        }
    }
    

  2. গ্রাহক শ্রেণীর বাস্তবায়ন
    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; }
        }
    }
    

এখন একটি পৃথক ফাইলে একটি UserContext প্রসঙ্গ তৈরি করা যাক:

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

আমরা MS SQL সার্ভারের জন্য EF এর মাধ্যমে LINQ থেকে SQL এর সাথে অপ্টিমাইজেশন পরীক্ষা পরিচালনা করার জন্য একটি প্রস্তুত সমাধান পেয়েছি:

MS SQL সার্ভারের জন্য C#.NET-এ LINQ ক্যোয়ারী অপ্টিমাইজেশানের কিছু দিক

এখন Program.cs ফাইলে, নিম্নলিখিত কোড লিখুন:

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

পরবর্তী, আমাদের প্রকল্প চালানো যাক.

কাজ শেষে, নিম্নলিখিতগুলি কনসোলে প্রদর্শিত হবে:

এসকিউএল কোয়েরি তৈরি করা হয়েছে

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])

অর্থাৎ, সাধারণভাবে, LINQ কোয়েরিটি MS SQL সার্ভার DBMS-এ একটি SQL কোয়েরি তৈরি করেছে।

এখন আসুন LINQ ক্যোয়ারীতে AND কন্ডিশনকে OR এ পরিবর্তন করি:

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

এর আবার আমাদের অ্যাপ্লিকেশন চালানো যাক.

30 সেকেন্ডের বেশি কমান্ড কার্যকর করার সময় সম্পর্কিত একটি ত্রুটির সাথে মৃত্যুদন্ড ক্র্যাশ হবে:

MS SQL সার্ভারের জন্য C#.NET-এ LINQ ক্যোয়ারী অপ্টিমাইজেশানের কিছু দিক

আপনি যদি দেখেন যে LINQ দ্বারা কোন প্রশ্ন তৈরি হয়েছে:

MS SQL সার্ভারের জন্য C#.NET-এ LINQ ক্যোয়ারী অপ্টিমাইজেশানের কিছু দিক
, তারপর আপনি নিশ্চিত করতে পারেন যে নির্বাচনটি দুটি সেটের (টেবিল) কার্টেসিয়ান পণ্যের মাধ্যমে ঘটে:

এসকিউএল কোয়েরি তৈরি করা হয়েছে

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]

আসুন এইভাবে LINQ ক্যোয়ারী পুনরায় লিখি:

অপ্টিমাইজ করা LINQ ক্যোয়ারী

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

তারপর আমরা নিম্নলিখিত 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]

হায়, LINQ কোয়েরিতে শুধুমাত্র একটি যোগদানের শর্ত থাকতে পারে, তাই প্রতিটি শর্তের জন্য দুটি প্রশ্নের মাধ্যমে একটি সমতুল্য ক্যোয়ারী করা সম্ভব, তারপরে সারিগুলির মধ্যে সদৃশগুলি সরানোর জন্য ইউনিয়নের মাধ্যমে তাদের ইউনিয়ন অনুসরণ করা সম্ভব।
হ্যাঁ, সম্পূর্ণ ডুপ্লিকেট সারিগুলি ফেরত দেওয়া হতে পারে তা বিবেচনা করে প্রশ্নগুলি সাধারণত অ-সমতুল্য হবে৷ যাইহোক, বাস্তব জীবনে, সম্পূর্ণ ডুপ্লিকেট লাইন প্রয়োজন হয় না এবং তারা তাদের পরিত্রাণ পেতে চেষ্টা করছে।

এখন এই দুটি প্রশ্নের বাস্তবায়ন পরিকল্পনা তুলনা করা যাক:

  1. ক্রস যোগদানের জন্য, গড় কার্যকর করার সময় হল 195 সেকেন্ড:
    MS SQL সার্ভারের জন্য C#.NET-এ LINQ ক্যোয়ারী অপ্টিমাইজেশানের কিছু দিক
  2. অভ্যন্তরীণ যোগদান-ইউনিয়নের জন্য গড় কার্যকর করার সময় 24 সেকেন্ডের কম:
    MS SQL সার্ভারের জন্য C#.NET-এ LINQ ক্যোয়ারী অপ্টিমাইজেশানের কিছু দিক

ফলাফল থেকে দেখা যায়, লক্ষাধিক রেকর্ড সহ দুটি টেবিলের জন্য, অপ্টিমাইজ করা LINQ ক্যোয়ারী অ-অপ্টিমাইজ করা থেকে অনেকগুণ দ্রুত।

ফর্মের একটি LINQ কোয়েরির শর্তে AND এর সাথে বৈকল্পিকের জন্য:

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

প্রায় সবসময় একটি সঠিক এসকিউএল কোয়েরি তৈরি হবে, যা গড়ে প্রায় 1 সেকেন্ডের জন্য চলবে:

MS SQL সার্ভারের জন্য C#.NET-এ LINQ ক্যোয়ারী অপ্টিমাইজেশানের কিছু দিক
এছাড়াও ভিউটি জিজ্ঞাসা করার পরিবর্তে LINQ থেকে অবজেক্ট ম্যানিপুলেশনের জন্য:

LINQ ক্যোয়ারী (1ম বিকল্প)

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

আপনি একটি প্রশ্ন ব্যবহার করতে পারেন যেমন:

LINQ ক্যোয়ারী (2ম বিকল্প)

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

যেখানে:

দুটি অ্যারে সংজ্ঞায়িত করা

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

, এবং প্যারা টাইপটি নিম্নরূপ সংজ্ঞায়িত করা হয়েছে:

প্যারা টাইপ সংজ্ঞা

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

এইভাবে, আমরা MS SQL সার্ভারে LINQ প্রশ্নগুলি অপ্টিমাইজ করার কিছু দিক বিবেচনা করেছি।

দুর্ভাগ্যবশত, এমনকি অভিজ্ঞ এবং নেতৃস্থানীয় .NET বিকাশকারীরা ভুলে যান যে তারা যে নির্দেশাবলী ব্যবহার করে তা পর্দার আড়ালে কী করছে তা বোঝা দরকার। অন্যথায়, তারা কনফিগারেটে পরিণত হয় এবং ভবিষ্যতে একটি সফ্টওয়্যার সমাধান স্কেল করার সময় এবং বাহ্যিক পরিবেশগত অবস্থার সামান্য পরিবর্তনের সাথে একটি টাইম বোমা ফেলতে পারে।

একটি ছোট পর্যালোচনা ছিল এখানে.

পরীক্ষার জন্য উত্স - প্রকল্প নিজেই, TEST ডাটাবেসে টেবিল তৈরি করে, পাশাপাশি ডেটা দিয়ে এই টেবিলগুলি পূরণ করে এখানে.
এছাড়াও প্ল্যান ফোল্ডারের এই সংগ্রহস্থলে OR শর্তাবলী সহ প্রশ্নগুলি চালানোর পরিকল্পনা রয়েছে।

উত্স: www.habr.com

একটি মন্তব্য জুড়ুন