الأربعاء، 18 فبراير 2015

قواعد بيانات SQL Server : تحويل دالة مكتوبة بلغة C# إلى دالة SQL Server




هذه المقالة متعلقة بقواعد بيانات Microsoft SQL Server وتناقش النقاط التالية:
-          تحويل دالة C# إلى دالة SQL Server واستخدامها ضمن الاستعلامات المنفذة على قاعدة بيانات SQL Server
المتطلبات السابقة لقراءة هذه المقالة:
-          إلمام بأساسيات قواعد بيانات Microsoft SQL Server
-          إلمام بأساسيات لغة البرمجة C# وبيئة التطوير المتكاملة Visual Studio

تعتبر لغة Transact-SQL هي الخيار البديهي لإنشاء دوال SQL Server معرفة بواسطة المستخدم، ولكنها ليست الخيار الوحيد لذلك، يمكنك استخدام لغة برمجية من لغات .NET كلغة C# على سبيل المثال لإنشاء دالة SQL Server، يُعرف هذا النوع من الدوال باسم CLR SQL Server User-Defined Functions ، اعتقد أن التسمية واضحة باستثناء الكلمة الأولى فيها وهي CLR ، ما هو الـ CLR؟ وما علاقته بموضوعنا و بلغة C# ؟ حسناً، في الحقيقة هذه المقالة ليست للحديث عن إطار عمل دوت نت ومكوناته، ولكن باختصار، الـ CLR هو أحد المكونات الرئيسية الهامة في إطار عمل دوت نت وهو المسؤول عن القيام بالترجمة الفورية JIT Compilation من اللغة الوسيطة IL التي تكتب بها مخرجات مترجمات لغات .NET مثل مترجم C# إلى الشفرة المنخفضة أو ما يعرف بالـ Native code ، لذلك يصح مجازاً أن نطلق على لغات .NET أنها لغات CLR ، وبذلك اعتقد أن جزء CLR في التسمية السابقة قد أصبح واضحاً بالنسبة لك!
إذا كنت قد قرأت مقالتي التي تحدثت فيها عن دالة soundex فلابد أنك قد وجدت في الجزء الأخير من المقالة تمثيل لخوارزمية soundex الداعمة للغة العربية باستخدام لغة C# ، وقد كانت الدالة كالآتي: 

        public static string SoundexAr(string word)
        {
            int length = 4;

            // Value to return
            string value = "";


            try
            {
                switch (word[0])
                {
                    case 'ا':
                    case 'أ':
                    case 'إ':
                    case 'آ':
                        {
                            word = word.Substring(1, word.Length - 1);
                        }
                        break;

                }

                // Size of the word to process
                int size = word.Length;
                // Make sure the word is at least two characters in length
                if (size > 1)
                {

                    // Convert the word to character array for faster processing
                    char[] chars = word.ToCharArray();
                    // Buffer to build up with character codes
                    StringBuilder buffer = new StringBuilder();
                    buffer.Length = 0;
                    // The current and previous character codes
                    int prevCode = 0;
                    int currCode = 0;
                    // Ignore first character and replace it with fixed value

                    buffer.Append('x');

                    // Loop through all the characters and convert them to the proper character code
                    for (int i = 1; i < size; i++)
                    {
                        switch (chars[i])
                        {
                            case 'ا':
                            case 'أ':
                            case 'إ':
                            case 'آ':
                            case 'ح':
                            case 'خ':
                            case 'ه':
                            case 'ع':
                            case 'غ':
                            case 'ش':
                            case 'و':
                            case 'ي':
                                currCode = 0;
                                break;
                            case 'ف':
                            case 'ب':
                                currCode = 1;
                                break;

                            case 'ج':
                            case 'ز':
                            case 'س':
                            case 'ص':
                            case 'ظ':
                            case 'ق':
                            case 'ك':
                                currCode = 2;
                                break;
                            case 'ت':
                            case 'ث':
                            case 'د':
                            case 'ذ':
                            case 'ض':
                            case 'ط':
                                currCode = 3;
                                break;
                            case 'ل':
                                currCode = 4;
                                break;
                            case 'م':
                            case 'ن':
                                currCode = 5;
                                break;
                            case 'ر':
                                currCode = 6;
                                break;
                        }

                        // Check to see if the current code is the same as the last one
                        if (currCode != prevCode)
                        {
                            // Check to see if the current code is 0 (a vowel); do not process vowels
                            if (currCode != 0)
                                buffer.Append(currCode);
                        }
                        // Set the new previous character code
                        prevCode = currCode;
                        // If the buffer size meets the length limit, then exit the loop
                        if (buffer.Length == length)
                            break;
                    }
                    // Pad the buffer, if required
                    size = buffer.Length;
                    if (size < length)
                        buffer.Append('0', (length - size));
                    // Set the value to return
                    value = buffer.ToString();
                }
                // Return the value
                return value;
            }
            catch
            {
                return "0000";
            }
        }

سنقوم بتحويل دالة C# السابقة إلى دالة SQL Server ، لذلك يرجى اتباع الخطوات التالية: 
(ملاحظة: لقد تم اختبار الخطوات التالية على Visual Studio Ultimate 2013 Update 4 و SQL Server Express 2008 R2 ، قد تختلف الخطوات اعتماداً على نوع الإصدارات التي تستخدمها)

1.         تأكد أولاً من تحديث مكونات فيجوال ستوديو، تحديداً تحديث Microsoft SQL Server for Database Tooling لكي لا تواجه مشاكل أثناء بناء ونشر المشروع. للقيام بذلك: من Visual Studio اختر القائمة Tools ثم الخيار Extensions and Updates ، ثم اضغط على Update الخاص بالتحديث المذكور بالأعلى، ثم تابع الخطوات المعروضة على شاشتك لإتمام التحديث.
2.       من نافذة SQL Server Object Explorer ، انقر على  قاعدة البيانات التي تريدها بالزر الأيمن واختر إنشاء مشروع قاعدة بيانات جديد.


3.      تظهر النافذة التالية، اضغط على زر start:

4.        الآن قم باتباع الخطوات الموضحة في الصور التالية، النتيجة ستكون كما في الصورة الأخيرة:







5.       في الصورة الأخيرة ستجد تعريف الدالة SoundexAr التي تستقبل المدخل من نوع string وسترجع كائن من نوع SqlString والذي سيستقبل مشيده القيمة النصية التي سيتم سيتم إرجاعها أثناء استدعاء الدالة SoundexAr . في جسم الدالة، قم باستبدال الكود الافتراضي التي تمت إضافته أثناء إنشاء الملف بجسم دالة C# السابقة مع مراعاة تعديل جمل الإرجاع return statements لكي ترجع قيم متوافقة مع النوع المصرح به في تعريف الدالة، سيكون تعريف وجسم الدالة كما يلي:

    public static SqlString SoundexAr(string word)
    {
        int length = 4;

        // Value to return
        string value = "";

        try
        {
            switch (word[0])
            {
                case 'ا':
                case 'أ':
                case 'إ':
                case 'آ':
                    {
                        word = word.Substring(1, word.Length - 1);
                    }
                    break;

            }

            // Size of the word to process
            int size = word.Length;
            // Make sure the word is at least two characters in length
            if (size > 1)
            {

                // Convert the word to character array for faster processing
                char[] chars = word.ToCharArray();
                // Buffer to build up with character codes
                System.Text.StringBuilder buffer = new System.Text.StringBuilder();
                buffer.Length = 0;
                // The current and previous character codes
                int prevCode = 0;
                int currCode = 0;
                // Ignore first character and replace it with fixed value

                buffer.Append('x');

                // Loop through all the characters and convert them to the proper character code
                for (int i = 1; i < size; i++)
                {
                    switch (chars[i])
                    {
                        case 'ا':
                        case 'أ':
                        case 'إ':
                        case 'آ':
                        case 'ح':
                        case 'خ':
                        case 'ه':
                        case 'ع':
                        case 'غ':
                        case 'ش':
                        case 'و':
                        case 'ي':
                            currCode = 0;
                            break;
                        case 'ف':
                        case 'ب':
                            currCode = 1;
                            break;

                        case 'ج':
                        case 'ز':
                        case 'س':
                        case 'ص':
                        case 'ظ':
                        case 'ق':
                        case 'ك':
                            currCode = 2;
                            break;
                        case 'ت':
                        case 'ث':
                        case 'د':
                        case 'ذ':
                        case 'ض':
                        case 'ط':
                            currCode = 3;
                            break;
                        case 'ل':
                            currCode = 4;
                            break;
                        case 'م':
                        case 'ن':
                            currCode = 5;
                            break;
                        case 'ر':
                            currCode = 6;
                            break;
                    }

                    // Check to see if the current code is the same as the last one
                    if (currCode != prevCode)
                    {
                        // Check to see if the current code is 0 (a vowel); do not process vowels
                        if (currCode != 0)
                            buffer.Append(currCode);
                    }
                    // Set the new previous character code
                    prevCode = currCode;
                    // If the buffer size meets the length limit, then exit the loop
                    if (buffer.Length == length)
                        break;
                }
                // Pad the buffer, if required
                size = buffer.Length;
                if (size < length)
                    buffer.Append('0', (length - size));
                // Set the value to return
                value = buffer.ToString();
            }
            // Return the value
            return new SqlString(value);
        }
        catch
        {
            return new SqlString("0000");
        }

    }


6.       الآن وقبل بناء ونشر الحل، قم التحقق من تكامل الـ CLR الخاص بنسخة قواعد البيانات التي تستخدمها واجعل مشروعك متوافق مع إطار عمل دوت نت المتكامل مع نسخة قواعد البيانات التي تستخدمها، قم بتنفيذ الاستعلام التالي على قاعدة البيانات لمعرفة رقم تكامل الـ CLR:

SELECT * FROM sys.dm_clr_properties;

7.      توجه إلى خصائص المشروع، وقم بتعديل إطار العمل الهدف ليتوافق مع النتيجة التي أظهرها الاستعلام السابق:


8.      قم ببناء ونشر الحل، يمكنك عمل ذلك بخطوة واحدة سريعة، الضغط على زر Start والذي يظهر في شريط الأدوات بجابنه سهم أخضر، مع تحديد تهيئة البناء والنشر لتكون Release كما هو موضح في الصورة:


9.       أخيرا قم بإضافة المرجع – ملف .dll – التي تم إنتاجه بواسطة الخطوة السابقة إلى قاعدة البيانات، ومن ثم قم بإنشاء دالة SQL Server تستخدم المرجع السابق، يمكنك عمل ذلك عبر تنفيذ الاستعلام التالي على قاعدة البيانات مع تغيير ما يلزم في الكود – مثل مسار المرجع - :
-- Install Assembly
CREATE ASSEMBLY MyHotelAssembly FROM 'C:\MyHotelDatabaseProject1.dll'
GO
-- Create SoundexAr Function
CREATE FUNCTION [dbo].[SoundexAr](@word nvarchar(max))
RETURNS nvarchar(4)
WITH EXECUTE AS CALLER
AS
EXTERNAL NAME MyHotelAssembly.UserDefinedFunctions.SoundexAr;
GO

10.      الآن يمكنك استخدام الدالة السابقة ضمن استعلامات SQL Server ، قم بتجربة الاستعلام التالي لترى النتيجة:

SELECT dbo.SoundexAr(N'احمد'), dbo.SoundexAr(N'أحمد'), dbo.SoundexAr(N'أحميد'), dbo.SoundexAr(N'احمدد');

تهانينا! لقد قمت بتعريف دالة مكتوبة بلغة C# لكي تكون دالة SQL Server