محتویات سایت
        برچسب های محبوب 








 
   بررسی یک مشکل هنگام استفاده از DataLoadOption در کوئری های کامپایل شده
  در این ترفند به بررسی نکته بسیار مهم در مورد استفاده از DataLoadOption در کوئری های کامپایل شده (Compiled Queries) می پردازم.
   LINQ
   ۱۱۱۸۲
   این مقاله حاوی فایل ضمیمه نمی باشد
   مرتضی صحراگرد
   ۱۳۸۸/۶/۱۸
نسخه قابل چاپ نسخه قابل چاپ

تذکر :

برای درک صحیح موارد بیان شده در این ترفند، آشنایی متوسط با DataLoadOption و کوئری های کامپایل شده  (Compiled Queries) در زبان LINQ to SQL الزامی می باشد.

مقدمه:

زبان LINQ سهولت های بسیار زیادی را در هنگام ساخت لایه دیتا و اجرا نمودن کوئری های مختلف بر روی پایگاه داده SQL SERVER به ارمغان آورده است. ولی یکی از مشکلاتی که کوئری های این زبان دارند ، زمان زیادی است که برای اجرای یک دستور  صرف می شود (نسبت به زمانی که اشیاء ADO.NET و Stored Procedure صرف می کنند)

علت این موضوع  این است که کوئری های این زبان در زمان اجرا (RunTime) تجزیه و کامپایل می شوند. و هر دفعه که یک کوئری فراخوانده می شود، این موضع تکرار می شود.

در برنامه هایی که استفاده زیاد و سنگینی از این زبان می شود (بخصوص در برنامه های تحت وب)، به شکل کاملا واضحی می توانید کند بودن اجرای دستورات را حس کنید. و این موضوع می تواند به طور جدی عملکرد برنامه شما را به چالش بکشاند.

برای رفع این مشکل می توانید کوئری های خود را به شکل کامپایل شده در آورید. کوئری های کامپایل شده در زمان کامپایل (Compile Time) به شکل کامل کامپایل می شوند و دیگر این عمل در زمان اجرا اتفاق نخواهد افتاد و سرعت اجرای دستورات به شدت بالا رفته و تقریبا به اندازه روال های ذخیره شده (Stored Procedure) می شود.

یادآوری :

موضوع این ترفند، آموزش نحوه نوشتن کوئری های کامپایل شده نمی باشد و بنابراین وارد برخی جزئیات نمی شویم.

شروع :

ادامه این ترفند را با ذکر یک مثال ادامه می دهیم.  در اینجا فرض می کنیم که  دو جدول به نام های Order و OrderDetail داریم که در جدول Order سفارشات و در جدول OrderDetail جزئیات مربوط به هر سفارش نگهداری می شود.

اکنون متدی می نویسیم که مشخصات مربوط به یک Order را به همراه جزئیات آن بر می گرداند.

public Order GetOrder(int orderID)

{

    using (OrderDataContext context = new OrderDataContext())

    {

        DataLoadOptions loadOptions = new DataLoadOptions();

        loadOptions.LoadWith<Order>(m => m.OrderDetails);

        context.LoadOptions = loadOptions;

        var result = context.Orders.Where(m => m.OrderID == orderID).SingleOrDefault();

        return result;

    }

}

عملکرد متد بالا کاملا واضح  و مشخص می باشد. این متد شناسه یک Order را گرفته و مشخصات کامل آن از جدول Order و OrderDetail برمی گرداند.

اکنون قصد داریم برای بالا بردن راندمان اجرای این دستور، کوئری مربوطه را تبدیل به یک کوئری کامپایل شده کنیم.

نکته :

معمولا توصیه می شود که برای نوشتن یک کوئری کامپایل شده، از یک متغیر private و static استفاده نماییم و آن را از طریق یک متد معمولی فراخوانی نماییم. در قطعه کد زیر موارد را ملاحظه می کنید.

//declare compiled query

private static Func<OrderDataContext, int, Order> GetOrderQuery = CompiledQuery.Compile

    (

    (OrderDataContext context, int orderID) =>

        context.Orders.Where(m => m.OrderID == orderID).SingleOrDefault()

    );

 

//declare method

public Order GetOrder(int orderID)

{

    using (OrderDataContext context = new OrderDataContext())

    {

        DataLoadOptions loadOptions = new DataLoadOptions();

        loadOptions.LoadWith<Order>(m => m.OrderDetails);

        context.LoadOptions = loadOptions;

        var result = GetOrderQuery(context, orderID);

        return result;

    }

}

همناطور که قبلا نیز بیان کردم، هدف از این ترفند،آموزش نحوه نوشتن کوئری های کامپایل شده نمی باشد و بنابراین از بیان جزئیات آن خود داری می کنم.

اگر برای اولین بار متد بالا را فراخوانی کنید، بدون هیچ مشکلی اجرا می شود ولی در فراخوانی های بعدی با یک خطای استثنا مواجه می شوید که حاوی پیام زیر می باشد:

Compiled queries across DataContexts with different LoadOptions not supported

دلیل خطای بوجود آمده این می باشد که کوئری های کامپایل شده پس از اولین فراخوانی، DataLoadOption مربوط به خود را به خاطر می سپارند و اگر مجددا سعی کنیم که یک DataLoadOption دیگر به آن نسبت دهیم، خطای بالا به وجود می آید.

حتی اگر DataLoadOption و LoadWith کاملا مشابه فراخوانی قبل باشد، باز هم این مشکل وجود دارد.

برای رفع این مشکل باید DataLoadOption را نیز طوری ایجاد کنیم که فقط یکبار مقدار دهی شود و هر بار که قصد داریم این کوئری را فراخوانی نماییم، این DataLoadOption را به آن نسبت دهیم.

در شکل زیر این موضوع  را ملاحظه می نمایید.

//declare static loadOption

private static DataLoadOptions loadOptions;

 

//declare compiled query

private static Func<OrderDataContext, int, Order> GetOrderQuery = CompiledQuery.Compile

   (

   (OrderDataContext context, int orderID) =>

       context.Orders.Where(m => m.OrderID == orderID).SingleOrDefault()

   );

 

//declare method

public Order GetOrder(int orderID)

{

    using (OrderDataContext context = new OrderDataContext())

    {

        if (loadOptions == null)

        {

            loadOptions = new DataLoadOptions();

            loadOptions.LoadWith<Order>(m => m.OrderDetails);

        }

        context.LoadOptions = loadOptions;

        var result = GetOrderQuery(context, orderID);

        return result;

    }

}

در متد بالا  ما DataLoadOption را به شکل static تعریف نموده ایم و داخل متد چک می کنیم تا فقط برای اولین بار new شده و مقدار دهی گردد.

تا اینجای کار، مشکل ما حل شده و می توانیم بدون مشکل از این متد استفاده نماییم.

اما فرض کنید که قصد داریم از متدهای دیگری نیز این کوئری را فراخوانی نماییم. در نتیجه باید در تمام این متدها حتما شرط زیر را چک کنیم.

if (loadOptions == null)

{

    loadOptions = new DataLoadOptions();

    loadOptions.LoadWith<Order>(m => m.OrderDetails);

}

برای رفع این مشکل از یک Delegate استفاده می کنیم که در زمان کامپایل و فقط برای یکبار، عمل مقدار دهی به DataLoadOption را انجام دهد و از این پس بدون هیچ دغدغه ای به استفاده از این DataLoadOption بپردازیم.

به نحوه تعریف این Delegate توجه نمایید (بسیار جالب می باشد)

private static readonly DataLoadOptions GetOrderLoadOption = (new Func<DataLoadOptions>(() =>

                                  {

                                      DataLoadOptions loadOptions = new DataLoadOptions();

                                      loadOptions.LoadWith<Order>(m => m.OrderDetails);

                                      return loadOptions;

                                  }))();

در قسمت زیر نحوه تعریف و استفاده از موارد فوق را ملاحظه می نمایید.

//declare static loadoptions

private static readonly DataLoadOptions GetOrderLoadOption = (new Func<DataLoadOptions>(() =>

                                  {

                                      DataLoadOptions loadOptions = new DataLoadOptions();

                                      loadOptions.LoadWith<Order>(m => m.OrderDetails);

                                      return loadOptions;

                                  }))();

 

//declare static query

private static Func<OrderDataContext, int, Order> GetOrderQuery = CompiledQuery.Compile

   (

   (OrderDataContext context, int orderID) =>

       context.Orders.Where(m => m.OrderID == orderID).SingleOrDefault()

   );

 

//declare method

public Order GetOrder(int orderID)

{

    using (OrderDataContext context = new OrderDataContext())

    {

 

        context.LoadOptions = GetOrderLoadOption;

        var result = GetOrderQuery(context, orderID);

        return result;

    }

}

با استفاده از این روش، دیگر نیازی برای نوشتن شرط null بودن LoadOption نیست.

برگرفته از : 30sharp.com

                Omar Al Zabir