ایجاد کوئری های پارامتربندی شده درADO.NET

خیلی از برنامه های مدرن در رابطه با به کارگیری رشته هستند. چه تجزیه محتوای XML، ساخت HTML برای مرورگر یا تلاش برای درک آنچه کاربر درون فیلد ورودی می نویسد باشد، توسعه دهندگان هیچ انتخابی ندارند جز سر و کار داشتن با متن. زبان های برنامه نویسی امروزی ابزارهای مناسبی برای کار با رشته ها دارند، اما یک قسمت فرمت شده از متن هنوز هم می تواند تمام روز شما را خراب کند، به ویژه وقتی که داده های کاربر پیچیده هستند.

ایجاد کوئری های پارامتربندی شده درADO.NET

چنین مواردی با کوئری های SQL است. آن ها به نظر بی ضرر و بسیار مفید هستند. اما خطرات در هر کلمه جدا شده با فضای خالی در کمین هستند. یک کوئری پایه ای که با استفاده از الحاق رشته ای C# ساخته شده است را در نظر بگیرید:

// ----- Code specific to Windows Forms/ADO.NET/SQL Server.
//       Assumes: "using System.Data.SqlClient;"
string sqlText = "UPDATE Customer " +
  "SET Name = '" + NameField.Text + "', " +
  "Age = " + AgeField.Text + " " +
  "WHERE ID = " + CustomerID;
SqlCommand dbCommand = new SqlCommand(sqlText, existingSqlConnection);
dbCommand.ExecuteNonQuery();

این کوئری گاهی وقت ها کار می کند. زیرا از رشته های پایه با کمی اعتبارسنجی داده ساخته شده است، که مسائلی مانند موارد زیر را متحمل می شود:

اگر NameField.Text شامل یک علامت نقل قول تنها (تک کوتیشن) باشد، مانند اسم "O’Reilly"، علامت نقل قول به یک علامت نامتعادل تبدیل می شود و کوئری شکست می خورد.

اگر AgeField.Text خالی باشد، یا حاوی مقدار غیرعددی باشد، فیلد Age پایگاه داده به درستی تنظیم نخواهد شد، و کوئری شکست می خورد.

اگر یک هکر "0AND Password = ' abc123'" را در فیلد ورودی AgeField بنویسد، هدف کوئری را تغییر خواهد داد و پایگاه داده را برای سوءاستفاده باز می کند. این حمله "SQL Injection Attack" است که شما در مورد آن زیاد شنیده اید.

فراتر از این مسائل داده ای، کوئری موارد مربوط به کارایی را نیز شامل می شود، به خصوص در سطح پایگاه داده. بعضی از پایگاه داده ها مثل SQL Server، کوئری های وارد شده را از طریق فرآیند کامپایل ارسال می کنند. پایگاه داده هر کوئری را ذخیره می کند و نسخه کامپایل شده را زمانی که درخواستی برای همان کوئری ارسال می شود، دوباره استفاده می کند. با جاسازی متن ارائه شده توسط کاربر در بدنه کوئری، احتمال کمی وجود دارد یا اصلا احتمال ندارد که کوئری قبلی دوباره استفاده شود، و نیاز به تجزیه کوئری و کامپایل شدن افزایش یابد.

بدیهی است که راه حلی برای این مشکل وجود دارد: کوئری های پارامتربندی شده (parameterized queries). چنین کوئری هایی بدنه کوئری را از مقادیر داده های مدیریت شده توسط آن کوئری جدا می کند، و انتظارات نوع داده را بر روی آن مقادیر اجرا می کند. این فرآیند با جایگزینی مقادیر hardcoded در متن کوئری placeholderها آغاز می شود، هر کدام با علامت "@" شروع می شوند:

string sqlText = @"UPDATE Customer
  SET Name = @NewName, Age = @NewAge
  WHERE ID = @RecordID";
SqlCommand dbCommand = new SqlCommand(sqlText, existingSqlConnection);

بلافاصله همه چیز ساده تر می شود. گام بعدی یک بخش جدید است، استفاده از پارامترها برای ارائه منبع مقادیر داده ها که سرانجام موتور پایگاه داده در مکان های placeholder قرار می دهد. هر پارامتر به شیء فرمان ADO.NET از طریق مجموعه Parameters اضافه می شود. ساده ترین راه برای افزودن یک مقدار به این مجموعه از طریق متد AddWithValue است:

dbCommand.Parameters.AddWithValue("@RecordID", CustomerID);

یک فرمت صریح تر، نمونه new SqlParameter را به صورت دستی درج می کند، و بعد از آنکه اعضای آن به درستی تنظیم شدند آن را به مجموعه اضافه می کند:

dbCommand.Parameters.Add(new SqlParameter("@RecordID", CustomerID));

پارامترها شیوه های بسیاری را به شما ارائه می دهند تا داده ها را قبل از ارسال به پایگاه داده دست کاری کنید. در اینجا کدهایی که فیلدهای nullable را بر اساس نوع ورودی کاربر مدیریت می‌کند موجود است:

SqlParameter oneParam = new SqlParameter("@NewAge", SqlDbType.Int);
if (int.TryParse(AgeField.Text, out int age) == true)
  oneParam.Value = age;
else
  oneParam.Value = System.DBNull.Value;
dbCommand.Parameters.Add(oneParam);

کوئری های پارامتربندی شده نیز می توانند با stored procedureها در سطح پایگاه داده مورد استفاده قرار گیرند، هر چند پیش از آن باید کوئری را مطلع سازید.

// ----- Placeholders are not required, just the procedure name.
dbCommand.CommandType = CommandType.StoredProcedure;
dbCommand.CommandText = "ProcedureNameGoesHere";

اگر پارامتر stored procedure فقط بخواهد داده ها را باز گرداند، ویژگی Direction شیء پارامتر، هدایت مسیر را فراهم می کند.

oneParam.Direction = ParameterDirection.Output;
// ----- Then, when running the query:
dbCommand.ExecuteReader();
long newID = Convert.ToInt64(dbCommand.Parameters["@OutputID"].Value);

ADO.NET متن کوئری و مجموعه پارامترها را به پایگاه داده به صورت اجزای جداگانه‌ای از درخواست پردازش کلی ارسال می کند، و با بهترین شیوه برای ادغام آن ها جهت ایمنی و کارایی، آن ها را به پایگاه داده ارسال می کند. و شما را از نگرانی در مورد سرگردانی داده ها و ایمنی در برابر هکرها رها می سازد.