Thursday, August 13, 2009

Cách giải quyết Concurrency và xung đột khi tạo khóa tự động trong SQL Server

Yêu cầu

Trong yêu cầu thực tế của dự án mà tôi gặp phải, đó là trong trường hợp tạo mã khách hàng trong dữ liệu khách hàng, chúng tôi cần phải đảm bảo các yêu cầu sau:

1. Mã khách hàng được sinh tự động gồm 13 ký tự số.

2. Mã khách hàng được đánh theo nguyên tắc: [xxx]{0}[Số thứ tự tăng dần]

Trong đó: [xxx] là 3 ký tự số quy định tùy theo nơi đăng ký sẽ khách nhau; {0} là được chèn vào để đảm bảo mã khách hàng luôn là 13 ký tự số; [Số thứ tự tăng dần] là số tăng dần bắt đầu từ 1.

Phân tích

Rõ ràng, với [Số thứ tự tăng dần] thì chúng tôi đơn giản tạo 1 cột số nguyên và sử dụng tính năng Auto-increment của SQL Server. Với mã khách hàng của khách hàng mới, thì sẽ thực hiện lần lượt các công việc:

1. SELECT [Số thứ tự tăng dần] từ bảng Customers

2. [Mã khách hàng mới] = [Số thứ tự tăng dần] + 1

2. Gán các số 0 vào đầu [Mã khách hàng mới] để đủ 10 ký tự số

3. Gán 3 ký tự số [xxx] cho sẳn vào đầu [Mã khách hàng mới]

4. INSERT [Mã khách hàng mới] vào cơ sở dữ liệu

Vậy, việc giải quyết vấn đề Concurrency và xung đột sẽ như thế nào cần được đặt ra.

Cách giải quyết vấn đề

Có một cách giải quyết vấn đề đơn giản và hiệu quả là sử dụng lời gợi ý (hint) UPDLOCK. Khóa UPDATE là một shared lock mà sẽ không khóa các lệnh đọc khác. Tuy nhiên, tại một thời điểm chỉ có một xử lý (process) được giữ UPDLOCK trên 1 dòng (row), vì vậy nếu đồng thời có 2 xử lý song song đồng thời, thì xử lý thứ hai sẽ phải chờ xử lý thứ nhất hoàn thành rồi mới tiến hành giữ khóa UPDATE để thực hiện.

Và đây là đoạn code để mô tả cách giải quyết vấn đề trên:

BEGIN TRANSACTION

SELECT coalesce(MAX(CustomerIndex, 0)) + 1 FROM Customers WITH (UPDLOCK)

-- Thực hiện tạo Mã khách hàng mới dựa trên số thứ tự nhận được

INSERT Customers(...)

COMMIT TRANSACTION

Saturday, August 8, 2009

SQLite3 đã được viết bằng .NET

Noah Hart đã viết lại SQLite3 bằng C#. Mặc dù bản đầu tiên này vẫn chậm hơn so với bản gốc, tuy nhiên dự án này đã mở ra cách mới cho việc sử dụng SQLite trong C# mà không phải sử dụng P/Invoke hoặc mã lệnh không-an-toàn (unsafe code).

Dự án C#-SQLite được đưa trên trang Google Code với phiên bản của SQLite 3.6.16, mã nguồn này tuân thủ theo giấy phép (license) Create Common 3.0. C#-SQLite đã được kiểm thử với hơn 30,000 trường hợp test với chỉ có 9 trường hợp test vẫn chưa thực hiện được. Mã nhị phân hoàn chỉnh của nó là 528KB trong khi bản gốc là 506KB. Hiệu năng của nó là vẫn chậm hơn từ 3-5 lần so với bản gốc được cài đặt bằng C, nhưng mã nguồn C# hiện tại vẫn chưa được tối ưu hóa, và hiệu năng hiện tại vẫn ở mức chấp nhận được. Hiện tại, thì số dòng trên một giây được thể hiện ở bảng sau:

Test

C#-SQLite

SQLite 3

Insert

300K

1300K

Select

1500K

8450K

Update

60K

300K

Delete

250K

700K

C#-SQLite tuy có hiệu năng chậm hơn so với SQLite được cài đặt trên C, tuy nhiên nó thật sự là sự lựa chọn tốt cho các lập trình viên C#, bởi vì tránh phải sử dụng P/Invoke bởi vì nó khá chậm và không linh hoạt. Một lý do khác nữa là mã nguồn C sử dụng lệnh goto trong hầu hết mọi nơi, có thể cản trở việc ngăn chặn các ngoại lệ.

Tuesday, July 14, 2009

Sử dụng SystemParametersInfo để thay đổi thiết lập giao diện

Hàm SystemParametersInfo cho phép bạn truy cập và thay đổi hầu hết các thiết lập giao diện người sử dụng ở mức thấp (màn hình Display Properties của Windows).

Bạn có thể đọc chi tiết trên MSDN, ở đây tôi chỉ đưa một số ví dụ cơ bản:

  • SPI_GETICONTITLELOGFONT giúp bạn lấy thông tin font chữ được sử dụng cho các nhãn icon; SPI_SETICONTITLELOGFONT giúp bạn thay đổi thông tin này.
  • SPI_GETNONCLIENTMETRICS giúp bạn lấy thông tin về font chữ được sử dụng trong các màn hình của Window bao gồm captions, menus, status bars, và message boxes; SPI_SETNONCLIENTMETRICS giúp bạn thay đổi chúng.

Và một số thiết lập trong Control Panel như:

  • SPI_SETKEYBOARDDELAY và SPI_SETKEYBOARDSPEED giúp bạn thiết lập các tham số của keyboard.
  • SPI_SETDOUBLECLICKTIME giúp bạn thiết lập tốc độ double-click của con chuột.
  • SPI_SETMENUFADE giúp bạn thiết lập hoặc vô hiệu hóa menu fade animation.

Sau đây, tôi sẽ viết một đoạn chương trình demo để thay đổi thiết lập font chữ của MessageBox trên Windows.

    public class WindowsUISettings
    {
        // Khai báo sử dụng API SystemParametersInfo
        [DllImport("user32.dll", CharSet = CharSet.Auto)]
        private static extern bool SystemParametersInfo(
            int Action, int uiParam, [In, Out] NONCLIENTMETRICS pvParam, int WinIni);

        [StructLayout(LayoutKind.Sequential)]
        private class NONCLIENTMETRICS
        {
            public int cbSize = Marshal.SizeOf(typeof(NONCLIENTMETRICS));
            public int iBorderWidth;
            public int iScrollWidth;
            public int iScrollHeight;
            public int iCaptionWidth;
            public int iCaptionHeight;
            [MarshalAs(UnmanagedType.Struct)]
            public LOGFONT lfCaptionFont;
            public int iSmCaptionWidth;
            public int iSmCaptionHeight;
            [MarshalAs(UnmanagedType.Struct)]
            public LOGFONT lfSmCaptionFont;
            public int iMenuWidth;
            public int iMenuHeight;
            [MarshalAs(UnmanagedType.Struct)]
            public LOGFONT lfMenuFont;
            [MarshalAs(UnmanagedType.Struct)]
            public LOGFONT lfStatusFont;
            [MarshalAs(UnmanagedType.Struct)]
            public LOGFONT lfMessageFont;
        }


        private const int LF_FACESIZE = 32;

        // A "logical font" used by old-school windows
        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
        private class LOGFONT
        {
            public int lfHeight;
            public int lfWidth;
            public int lfEscapement;
            public int lfOrientation;
            public int lfWeight;
            public byte lfItalic;
            public byte lfUnderline;
            public byte lfStrikeOut;
            public byte lfCharSet;
            public byte lfOutPrecision;
            public byte lfClipPrecision;
            public byte lfQuality;
            public byte lfPitchAndFamily;
            
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = LF_FACESIZE)]
            public string lfFaceName;

            // to shut it up about the warnings
            public LOGFONT(string lfFaceName)
            {
                this.lfFaceName = lfFaceName;
                lfHeight = lfWidth = lfEscapement = lfOrientation = lfWeight = 0;
                lfItalic = lfUnderline = lfStrikeOut = lfCharSet = lfOutPrecision
                = lfClipPrecision = lfQuality = lfPitchAndFamily = 0;
            }
        }

        //Khai báo SPI lấy và thay đổi thiết lập về 
        //caption, menu, status bar, scroll bar và message box 
        private const int SPI_GETNONCLIENTMETRICS = 0x0029;
        private const int SPI_SETNONCLIENTMETRICS = 0x002A;

        private NONCLIENTMETRICS currentMetrics;

        /// <summary>
        /// Lưu giữ lại các giá trị ngầm định
        /// </summary>
        private static NONCLIENTMETRICS defaultMetrics;

        private WindowsUISettings()
        { }

        public static WindowsUISettings CurrentMetrics()
        {
            //Lưu giữ lại các giá trị ngầm định
            defaultMetrics = new NONCLIENTMETRICS();
            SystemParametersInfo(SPI_GETNONCLIENTMETRICS, defaultMetrics.cbSize, defaultMetrics, 0);

            NONCLIENTMETRICS pvParam = new NONCLIENTMETRICS();
            SystemParametersInfo(SPI_GETNONCLIENTMETRICS, pvParam.cbSize, pvParam, 0);

            WindowsUISettings instance = new WindowsUISettings();
            instance.currentMetrics = pvParam;

            return instance;
        }

        /// <summary>
        /// Lấy/Thiết lập font chữ cho MessageBox
        /// </summary>
        public Font MessageFont
        {
            get
            {
                return Font.FromLogFont(currentMetrics.lfMessageFont);
            }
            set
            {
                value.ToLogFont(currentMetrics.lfMessageFont);
            }
        }

        /// <summary>
        /// Thiết lập các thay đổi
        /// </summary>
        public void Apply()
        {
            SystemParametersInfo(SPI_SETNONCLIENTMETRICS, currentMetrics.cbSize, currentMetrics, 1);
        }

        /// <summary>
        /// Đặt lại các giá trị ngầm định
        /// </summary>
        public void RestoreDefault()
        {
            SystemParametersInfo(SPI_SETNONCLIENTMETRICS, defaultMetrics.cbSize, defaultMetrics, 1);
        }
    }

Và đây là ví dụ chạy thử:

Hãy tạo 1 form mới, trên đó có 2 button bao gồm, button1 và button2. Double-click vào button1 và sau đó button2, ví dụ xử lý sự kiện Click trên 2 button sẽ như sau:

    public partial class Form1 : Form
    {
        WindowsUISettings metrics 
            = WindowsUISettings.CurrentMetrics();

        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            Font messageFont = new Font(
                metrics.MessageFont.FontFamily, 14);

            metrics.MessageFont = messageFont;
            metrics.Apply();

            MessageBox.Show(
            "MessageBox đã được thay đổi font size thành 14.");
        }

        private void button2_Click(object sender, EventArgs e)
        {
            metrics.RestoreDefault();
            MessageBox.Show(
            "MessageBox đã được trở về font size 8.");

        }
    }
Qua ví dụ này, chắc các bạn đã hiểu cách để lấy thông tin và thay đổi các thông số khác.