دوشنبه ۲۷ دی ۱۳۹۵
 
 
 
کلمه عبور خود را فراموش کرده اید؟
 

 
 
 ایجاد پرسش های linq به صورت پویا
.NET C# Database / LINQ / EF
تاریخ ثبت:  ۹۰/۱۱/۱۹
تعداد نمایش:  ۱۲۵۷۱
  نویسنده: فرید بکران
 
   ۲۳  نفر تا این لحظه به این مقاله امتیاز داده اند.
 
   Bookmark and Share

مقدمه

حتما تا به حال به این مسئله بر خورده اید که در برنامه ی نیاز به فیلتر کردن یک سری اطلاعات با استفاده از پارامتر های ورودی کاربر وجود داشته باشد. به طور مثال نیاز به جستجوی کالاها در پایگاه داده بر اساس قیمت یا اندازه یا سال ساخت یا دیگر خصوصیات آن، باشد. و بعضی از این پارامترها به صورت اختیاری ظاهر خواهند شد. در اینگونه مواقع یکی از راههایی که به ذهن می‌رسد این است که کد پرسش sql مربوط به جستجو را به صورت پویا و با در نظر گرفتن ورودی های کاربر درست کنیم. این راهی است که خیلی از ما معمولا از آن استفاده می‌کردیم. ولی وقتی از تکنولوژی ای مانند linq to sql برای ارتباط با پایگاه داده استفاده می‌کنیم و نیاز داریم اطلاعات بازگردانده شده از طریق پرسش به صورت تعریف نوع شده باشند، این روش چندان جالب به نظر نمی‌رسد. به طور مثال وقتی قصد داریم روی جدول products در یک datacontext یک پرس Linq اجرا کنیم، باید با توجه به ورودی‌های کاربر آن پرسش linq را تولید کنیم. (به زبان کاملا ساده!)

به طور مثال می‌خواهیم پرسشی به صورت زیر را در نهایت روی context  مورد نظر خود اجرا نماییم. 

ShoppingDataBaseDataContext context = new ShoppingDataBaseDataContext();
context.Products.Where(p => p.Name.Contains("farid") && p.Price < 1000);

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


معرفی
Expression tree

Expression treeها در واقع ساختمان داده ای درخت مانند هستند برای نمایش کدهای قابل اجرا. هر کدام از گره های این درخت خود یک Expression است. یک Expression می تواند عبارت ریاضی مانند x < y   یا یک فراخوانی تابع یا هر چیز دیگری باشد. می توان یک Expression tree را کامپایل و سپس اجرا کرد. بدین ترتیب این امکان را پیدا می‌کنیم که در زمان اجرای برنامه کدهای قابل اجرا را دستکاری و کدهایی بسته به نیاز خودمان تولید کنیم. همان طور که در جملات گذشته ذکر شد. یکی از امکانات مهمی که در .net و زبان برنامه نویسی C# در اختیار برنامه نویس قرار می‌گیرید این است که برنامه نویس می‌تواند یک Expression tree را در زمان اجرای برنامه (و نه در زمان طراحی و برنامه نویسی) تولید کرده و اقدام به اجرای آن نماید. در این مقاله من نیز از این امکان برای تولید پرسش مورد نظر خود استفاده خواهم کرد. برای استفاده از امکانات مربوط به Expression در برنامه خود باید از فضای نام زیر استفاده نمایید.

using System.Linq.Expressions;

هریک از Expressionها بسته به نوعی که دارند می‌توانند نماینده انجام کارهایی متفاوت باشند. مانند عمل دودویی جمع، عملیات یکانی مانند دسترسی به خصوصیات یک کلاس یا فراخوانی یک تابع.

در فضای نامی که در بالا اشاره شد کلاس های Expression   مختلفی بسته به کدی که نمایندگی می‌کنند، وجود دارند. هریک از Expression ها دو خصوصیت مهم داردن که در زیر به آنها اشاره شده است.

  • Type: این خصوصیت نوع مقداری را که Expression در نهایت مشخص خواهد کرد را نشان می دهد. برای درک بهتر اگر یک Expression جهت دسترسی به خصوصیت Length یک رشته ایجاد شده باشد باید نوع آن int باشد. یا به زبان دیگر این خصوصیت نوع داده ای را مشخص می‌نماید که Expression پس از ارزیابی شدن خواهد داشت.
  • NodeType: این خصوصیت نوع عملیاتی را که Expression انجام می‌دهد را برمی‌گرداند. این خصوصیت در وافع کاری را که Expression مربوط به آن انجام می‌دهد را نشان می‌دهد. به طور مثال اگر Expression ای برای دسترسی به خصوصیت Length یک رشته درست شده باشد NodeType مربوط به آن MemberAccess خواهد بود. که این مقدار عضوی از نوع داده شمارنده ExpressionType است.

به عنوان یک مثال ساده فرض کنید می‌خواهیم Expression ای را که جمع دو عدد 2 و 4 را نشان می‌دهد بوجود آوریم. اگر ساختار درختی کد مورد نظر را (که به صورت 2 + 4 است) در نظر بگیریم نموداری مانند شکل زیر خواهد بود.


که هرکدام از گره های بالا یک Expression را نمایش می‌دهند. کد C# مربوط به تولید این درخت به صورت زیر است.

ConstantExpression exp1 = Expression.Constant(3);
ConstantExpression exp2 = Expression.Constant(4);
BinaryExpression res = Expression.Add(exp1, exp2);

در کد بالا همان طور که می بینید exp1 و exp2 از نوع مقدار ثابت هستند و res از نوع عملگر دودویی.

چگونگی اجرای یک Expression tree

پس از آنکه یک درخت دستورات را به صورت پویا ایجاد نمودیم نیاز به اجرای آن نیز وجود دارد. برای اجرای یک Expression tree ابتدا باید آنرا کامپایل نماییم. این کار را با استفاده از تکه کد زیر انجام خواهیم داد.

Func<int> compiled = Expression.Lambda<Func<int>>(res).Compile();

کاری که در کد بالا انجام می‌شود این است که ابتدا ما یک Lambda Expression از Expression tree مربوطه خودمان درست می کنیم. نکته مهم اینجاست که نوع این Lambda Expression ای که درست می‌کنیم در زمان کامپایل معلوم است. در اینجا منظور از نوع، انواع داده ای پارامترهای دریافتی و نوع داده ای مقدار بازگشتی Expression مربوطه است. پس از اینکه همچین Lambda Expression ای ایجاد شد می‌توانیم آنرا به کدهای قابل اجرا کامپایل نماییم. نوعی که متد compile برمیگرداند یک delegate است که می‌توان با فراخوانی آن اقدام به اجرای دستورات تولید شده کرد.

برگردیم به مثال اصلی خودمان. با اطلاعات مختصری که از روش مورد استفاده بدست آورده اید به تولید کد مورد نظر خود می‌پردازیم. هدف ما تولید کدی به صورت زیر بود.

context.Products.Where(p => p.Name.Contains("farid") && p.Price < 1000);

برای این کار می توانید درخت زیر را در نظر بگیرید.


همان طور که در شکل بالا مشاهده می‌کنید کل فرایند ایجاد کد مورد نظر از پارامتر  ورودی
Lambda Expression شروع شده و در نهایت کل متد مورد نظر ما تولید می شود. هریک از گره های درخت بالا نوعی از کلاس Expression هستند. به عنوان مثال مقادیر ثابت از نوع ConstantExpression   و گره هایی که متدی را فراخوانی می‌کنند از نوع MethodCallExpression هستند.

اکنون مراحل تولید کد مورد نظر خود را اجرا می‌کنیم.

اولین گام تولید Expression پایه برای استفاده از آن در دیگر Expression ها است. در نمودار نیز مشاهده می‌کنید که پارامتر ورودی p که به صورت ورودی به Lambda Expression ارسال خواهد شد به صورت پایه ای برای دیگر عملیات استفاده شده است. کدی که این پارامتر را تولید می کند به صورت زیر است.

ParameterExpression P = Expression.Parameter(typeof(Product), "p");

دقت کنید که تمام گره های موجود در نمودار بالا از نوع Expression هستند. در کد بالا وقتی اقدام به ایجاد یک پارامتر می‌کنید باید نوع آن را نیز مشخص نمایید. که در این مورد این پارامتر از نوع کلاس Product است. کلاس Product یکی از کلاس های موجود در DataContext ما است.

مرحله بعدی ایجاد مقادیر ثابت موجود هستند. این دو مقدار را می توان با تکه کد زیر تولید کرد.

ConstantExpression price = Expression.Constant(1000);
ConstantExpression name = Expression.Constant(“فرید”);

در کد بالا دو Expression تولید کردیم که مشخص کننده مقادیر ثابت در محاسبات ما هستند. مرحله بعدی دستیابی به مقادیر خصوصیت هایی از شی مربوطه کالا می‌باشد. Expression مربوط به این کارها (برای هریک از خصوصیت ها) به صورت زیر ایجاد می شود.

MemberExpression productPrice = Expression.Property(p,typeof(Product).GetProperty("Price"));
MemberExpression productName = Expression.Property(p,typeof(Product).GetProperty("Name"));

هریک از Expression های تولید شده برای دسترسی به یکی از خصوصیت های شی p استفاده می شوند. پارامتر  اولی که متد مورد استفاده می گیرد Expression مربوط به شی ای است که می خواهیم خصوصیتی مربوط به آن را دریافت نماییم. پارامتر دوم مشخصات مربوطه به آن خصوصیت است که در اینجا بوسیله Reflection بدست آمده است.

در مرحله بعد به سراغ 2 Expression مقایسه‌ای و فراخوانی تابع می‌رویم. Expression ای که عمل مقایسه قیمت را نشان می‌دهد به صورت زیر تولید می‌شود.

BinaryExpression comparison = Expression.GreaterThan(priceConstant, productPrice);

برای تولید این عمل مقایسه از متد GreaterThan استفاده شده است. این متد یک شی از نوع BinaryExpression بر می‌گرداند که مشخص کننده عمل مقایسه می باشد. پارامتر های ورودی این متد عملوند های چپ و راست مربوط به عملگر مقایسه می باشند.

اکنون نوبت تولید Expression مربوط به استفاده از متد Contains است! برای تولید این Expression از تکه کد زیر استفاده می‌کنیم.

MethodCallExpression methodcall = Expression.Call(productName,typeof(string).GetMethod("Contains"), nameConstant);

در کد بالا یک Expression که مشخص کننده فراخوانی متد است ایجاد شده است. این کار توسط متد Call از کلاس Expression انجام شده است. این متد شی ای از نوع MethodCallExpression بر می‌گرداند. این متد Expression مشخص کننده شی را که متد روی آن فراخوانی خواهد شد را در پارامتر اول، اطلاعات مربوط به متد را در پارامتر دوم، و اطلاعات مربوط به پارامتر های آن متد را در پارامتر های بعدی می گیرد.

مرحله بعدی && کردن این دو Expression ای است که هر دو پس از ارزیابی شدن مقداری بولی دارند. Expression تولید شده را برای درک بهتر PredicateBody می نامیم.

BinaryExpression PredicateBody = Expression.AndAlso(comparison, methodcall);

بدنه اصلی LambdaExpression مورد نظر ما از یک && بوجود آمده است. این بدنه توسط تکه کد بالا بوجود آمده. مطلب قابل توجه این است که متد AndAlso عملگری را تولید می‌کند که عملوند دوم را وقتی ارزیابی می‌کند که عملوند اول دارای مقدار true باشد.

MethodCallExpression whereMethod = Expression.Call(typeof(Queryable),
"Where", new Type[] { queryableData.ElementType }, queryableData.Expression, Expression.Lambda<Func<Product, bool>>(PredicateBody, p));

آخرین کاری که برای ایجاد کد مربوط به فیلتر خود باید انجام دهیم ایجاد Expression نمایش دهنده متد Where است. این کار را با متد Call انجام می‌دهیم. ولی از نسخه سربارگذاری شده ای استفاده می‌کنیم که پارامتر  ورودی اول آن نوع ای است که قرار است متد از آن اجرا شود، پارامتر دوم آن نام متد است، پارامتر سوم آن نوع داده ای مربوط به پارامتر نوع متد Generic است، پارامتر های بعدی آن پارامترهای  ورودی مربوط به متد است که در اینجا یک LambdaExpression و شی ای است که Where قرار است روی آن انجام شود.

برای اجرا کردن کدی که در مراحل بالا ایجاد شد باید از تکه کد زیر استفاده کرد.
IQueryable<Product> res = queryableData.Provider.CreateQuery<Product>(whereMethod);

این تکه کد یک شی از Iqueryable<T> ایجاد می کند که قادر به ارزیابی Expression مشخص شده در پارامتر  ورودی اش است. و لیستی که با نام res مشخش شده است حاوی نتیجه فیلتر خواهد بود.

نتیجه گیری

در این مقاله سعی شد به طور مختصر امکان ایجاد پرسش ها linq به صورت پویا شرح داده شود. ممکن است با آموختن این تکنیک روش برنامه نویسی فرد نیز تغییر کرده و برخی فعالیت ها به آسانی انجام شوند. این مقاله منبع کاملی جهت آموختن تمام نکات مربوط به این تکنولوژی نیست. برای مطالعه بیشتر باید به کتب مربوطه و منابع برنامه نویسی رسمی مراجعه کرد.


فایل ضمیمه مقاله: DynamicLinqQuery.rar
  کیفیت مقاله ارائه شده از نظر شما   
برای دادن رتبه به این مقاله می بایست Login کرده باشید.
  درباره نویسنده
فرید بکران
همه مقاله های نوشته شده توسط این کاربر (۴)
 
  پیام جدید
صفحه ۱ - پیامهای اصلی ۱ تا ۵ از مجموع ۵ پیام اصلی
اولین قبلی بعدی

 عنوان فرستنده تاریخ
 
تشكر مجید سامانی ۱۳۹۵/۸/۱۰
 
queryableData.ElementType محمد جواد تواضعی ۱۳۹۲/۵/۲۸
 
اگر بیش از 2 فیلد بود باید چیکار کنیم mohsen bahrzadeh ۱۳۹۱/۱/۲۰
پاسخ به: اگر بیش از 2 فیلد بود باید چیکار کنیم فرید بکران ۱۳۹۱/۲/۳
پاسخ به: اگر بیش از 2 فیلد بود باید چیکار کنیم Jahan Alem ۱۳۹۲/۳/۱۲
 
یک ایده برای ساده شدن فرایند نوشتن درخت عبارت سام ناصری ۱۳۹۰/۱۱/۲۰
پاسخ به: یک ایده برای ساده شدن فرایند نوشتن درخت عبارت فرید بکران ۱۳۹۰/۱۱/۲۳
پاسخ به: یک ایده برای ساده شدن فرایند نوشتن درخت عبارت سام ناصری ۱۳۹۰/۱۲/۱
پاسخ به: یک ایده برای ساده شدن فرایند نوشتن درخت عبارت فرید بکران ۱۳۹۰/۱۲/۱
پاسخ به: یک ایده برای ساده شدن فرایند نوشتن درخت عبارت سام ناصری ۱۳۹۰/۱۲/۱
 
راه دوم سعيد خان ۱۳۹۰/۱۱/۱۹
پاسخ به: راه دوم فرید بکران ۱۳۹۰/۱۱/۲۰
پاسخ به: راه دوم سام ناصری ۱۳۹۰/۱۱/۲۰
پاسخ به: راه دوم سعيد خان ۱۳۹۰/۱۱/۲۰
پاسخ به: راه دوم سام ناصری ۱۳۹۰/۱۲/۱
پاسخ به: راه دوم سعيد خان ۱۳۹۰/۱۱/۲۰
پاسخ به: راه دوم فرید بکران ۱۳۹۰/۱۱/۲۳
اولین قبلی بعدی

Copyright © 2006 - 2016 All Rights Reserved.
Please direct your questions or comments to