ایجاد LINQ داینامیک با استفاده از Expressions

چهارشنبه 18 فروردین 1395

در این مقاله شما با نحوه ی ایجاد LINQ داینامیک با استفاده از Expression آشنا خواهید شد.همچنین کدهایی مربوطه را مورد بررسی قرار میدهیم.

ایجاد  LINQ داینامیک با استفاده از Expressions

نکته : این مقاله برای نسخه 3.5 و 3.0. دات نت مناسب است.

در این مقاله مطالب زیر آموزش داده میشود:

_ایجاد یک lambda Expressionsساده

_ایجاد یک  داینامیک Linq Expressions

قبل از ادامه ی مقاله میخواهم به شما یاداوری کنم که نگاهی به کلاس MSDN documentation of Expression و متدها و propertyهای آن بیندازید.

ایجاد یک lambda Expressionsساده:

ابتدا یک expression به عنوان نمونه در نظرمیگیریم که دو عدد را جمع میکند .

Expression<Func<int, int, int>> sumExpr = (a, b) => a + b;

این قسمت نحوه ی نوشتن کد را نشان میدهد اما لازم نیست که  دقیقا مانند قطعه کد بالا یک expression ساخته شود.کلاسهای non-generic Expressionباعث میشود که متدها و propertyهای زیادی برای شماقابل دسترس شود. اگر بخواهیم که این expression را باز کنیم.به چیزی شبیه قطعه کد پایین میرسیم.

Parameters = {a, b}  
2.	Body = a + b  
3.	NodeType = Lambda  

بنابراین، ما نیاز داریم که  در اینجا دو پارامتر را تعریف کنیم و این کار را به صورت زیر انجام میدهیم.

1.	var paramExprA = Expression.Parameter(typeof(int), "a");  
2.	var paramExprB = Expression.Parameter(typeof(int), "b");  

حالا باید بدنه ی کدمان را بسازیم، همانطور که در کد مشاهده میکنید بدنه ی کد یک عملیات اجرایی دوگانه است که توسط عملگر جمع اجرا میشود.بنابراین این قسمت یک expression (دوگانه) با متد جمع و فرستادن این پارامترها به آن، میباشد.

1.	  var body = BinaryExpression.Add(paramExprA, paramExprB);  

در این قسمت نوع داده lambda را بعنوان func که دو متد از جنس int دارد و یک مقدار از جنس int  برمیگرداند، تعریف کرده ایم. سپس بدنه و پارامترهای expression را می نویسیم. و به این صورت expression (عبارت) کامل میشود.حالا اگر شما نگاهی به lambda در حالت debug بیندازید دقیقا همانطور که انتظار میرود،  کار خواهد کرد.

حالا برای اجرای این کد، باید همان کاری که برای تعریف expression کردیم را در اینجا انجام دهیم.

var result = lambda.Compile()(2, 3);

متد مورد نظر میتواند به وسیله genericها تعمیم یابد و بعد از آن میتواند با هر نوع داده ای فراخوانی شود:

1.	private static Expression < Func < T, T, T >> BuildIt < T > ()   
2.	{  
3.	    var paramExprA = Expression.Parameter(typeof(T), "a");  
4.	    var paramExprB = Expression.Parameter(typeof(T), "b");  
5.	  
6.	    var body = BinaryExpression.Add(paramExprA, paramExprB);  
7.	  
8.	    var lambda = Expression.Lambda < Func < T,  
9.	        T, T >> (body, paramExprA, paramExprB);  
10.	  
11.	    return lambda;  
12.	}  

شما میتوانید آن را به  صورت زیر فراخوانی کنید:

var res = BuildIt<int>().Compile()(2, 3);

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

ایجاد یک Linq expression  ساده و داینامیک:

اکنون ما یک کلاس  ‘testDemo’ داریم و میخواهیم که لیستی از اشیاء را از ‘testDemo’  به لیست دیگری  از اشیاء در کلاس ‘SelectListItem’  تبدیل کنیم.این یک کلاس در ASP.NET MVC است که آیتم های منبع HTML Dropdownlist را نمایش میدهد و بسیار مورد استفاده قرار میگیرد.با در نظر گرفتن این مطلب، کلاسهایی از این نوع میتوانند موجودیت های DbContext را از اطلاعات پایگاه داده بگیرند و به اشیائی سازگار با Dropdownlist تبدیل کنند.

برای کلاسهایی متعدد LINQ به این صورت عمل میکند:

1.	MyDbContext db = new MyDbContext();  
2.	  
3.	List < SelectListItem > selectedItems = new List < SelectListItem > ();  
4.	if (type == null) return selectedItems;  
5.	  
6.	if (type == typeof(TestDemo))  
7.	    selectedItems = db.TestDemo.Select(i => new SelectListItem  
8.	    {  
9.	        Text = i.Name, Value = i.Id.ToString()  
10.	    }).ToList();  
11.	if (type == typeof(TestDemo1))  
12.	    selectedItems = db.TestDemo1.Select(i => new SelectListItem  
13.	    {  
14.	        Text = i.Name, Value = i.Id.ToString()  
15.	    }).ToList();  
16.	  
17.	if (type == typeof(TestDemo2))  
18.	    selectedItems = db.TestDemo2.Select(i => new SelectListItem  
19.	    {  
20.	        Text = i.Name, Value = i.Id.ToString()  
21.	    }).ToList();  
22.	  
23.	if (type == typeof(TestDemo3))  
24.	    selectedItems = db.TestDemo3.Select(i => new SelectListItem  
25.	    {  
26.	        Text = i.Name, Value = i.Id.ToString()  
27.	    }).ToList();  

کد بالا  سوالی است که یکی از کاربران یک سایت برنامه نویسی پرسیده است و درخواست کرده است که سایر کاربران اشکال کد بالا را رفع کنند.در این کد چیزی که به نظر کمی ناخوشایند میرسد این است که قوانین مربوط به انتخاب یک آیتم LINQ در همه ی کلاسها تکرار میشود.در نظر بگیرید که اگر 10-12 کلاس داشته باشیم متدها به چه شکلی پیاده سازی خواهند شد و کار چقدر مشکل خواهد شد.

بنابراین راه حل این است که یک Generic method ایجاد کنیم که expression را میسازد و لیستی از "SelectListItem" را برمیگرداند.

بیایید مشخصه ی یک Generic method را تعریف کنیم:

public static IEnumerable<SelectListItem> GetList<T>(this IQueryable<T> source)

این یک  generic Extension methodبرای IQueryable ها  است که میتواند بوسیله ی هر داده ای از نوع  IQueryables  فراخوانی شود.T یک موجودیت است که باید فراخوانی شود. در مثال ما T  همان TestDemo، TestDemo 1 ... خواهد بود.

برای ایجاد expression آنرا مانند شاخه های درخت خواهیم شکست.

i => new SelectListItem { Text = i.Name, Value = i.Id.ToString() }; 

آیتم هایی که ما تاکنون داشته ایم به شرح زیر است:

1.	Parameters = i  
2.	Body = new SelectListItem { Text = i.Name, Value = i.Id.ToString() }  
3.	NodeType = Lambda  

بیایید ابتدا پارامترها را تعریف کنیم:

1.	var paramExpr = Expression.Parameter(typeof(T), "i");  

در قدم دوم بدنه را بایدتعریف کنیم.اما در مورد قسمت بدنه با جزئیات بیشتری صحبت خواهیم کرد زیرا در این قسمت مجموعه ای از چیزهای مختلف برای اجرا وجود دارد که عبارتند از :

_ ایجاد یک شی از نوع SelectedListItem.

_پر کردن property های شی با استفاده از پارامترها  i.e. i.

_ صدا زدن متد ()Tostring برای property i.Id .

در ابتدا ما به اطلاعاتی درباره ی property ها هم از منبع و هم از کلاسهای مبدا نیاز داریم که به ما نقشه ی راه در طول ایجاد یک شی را میدهد.در زیر ما از Reflection  برای گرفتن اطلاعات property هر دو کلاس و ایجاد یک نقشه استفاده کرده ایم.بنابراین ما به راحتی میتوانیم مسیردهی بین propertyها را شناسایی کنیم.

1.	KeyValuePair<PropertyInfo, PropertyInfo> sourceDestPropMap1  
2.	= new KeyValuePair<PropertyInfo, PropertyInfo>(  
3.	// Text prop of selected item  
4.	typeof(SelectListItem).GetProperty("Text"),  
5.	// Name prop of T class  
6.	typeof(T).GetProperty("Name"));   
7.	  
8.	KeyValuePair<PropertyInfo, PropertyInfo> sourceDestPropMap2  
9.	= new KeyValuePair<PropertyInfo, PropertyInfo>(  
10.	// Value prop of Selected Item  
11.	typeof(SelectListItem).GetProperty("Value"),  
12.	// Id prop from T class  
13.	typeof(T).GetProperty("Id"));   

اکنون اجازه دهید که property مربوط به Map را تعریف کنیم.

1.	var propertyA = Expression.Property(paramExpr, sourceDestPropMap1.Value);  
2.	  
3.	var propertyB = Expression.Property(paramExpr, sourceDestPropMap2.Value);

حالا ما نیاز داریم که متد ‘ToString’ رابرای property دوم فراخوانی کنیم.

1.	var propertyBToString = Expression.Call(propertyB, typeof(object).GetMethod("ToString"));  

در اینجا ما ایجاد کننده ی شی از کلاس ‘SelecListItem’ و فرمت property را تعریف میکنیم.

1.	var createObject = Expression.New(typeof(SelectListItem));  
2.	  
3.	var InitializePropertiesOnObject = Expression.MemberInit(  
4.	    createObject,  
5.	    new []   
6.	    {  
7.	        Expression.Bind(sourceDestPropMap1.Key, propertyA),  
8.	            Expression.Bind(sourceDestPropMap2.Key, propertyBToString)  
9.	    });  

تا به حالا ما  ایجادکننده ی شی، فرمت property و مسیردهی property از expression از نوع T به SelectListItem را تعریف کردیم. مرحله ی پایانی ساختن expression است.

1.	var selectExpression = Expression.Lambda<Func<T, SelectListItem>>(InitializePropertiesOnObject, paramExpr);  

در اینجا نگاهی به نمایشگر Debug کننده میندازیم تا ببنیم که expression ما در آن چگونه نمایش داده میشود.

اکنون ما همه موارد برای select کردن  را ایجاد و آماده کرده ایم و همه چیز توسط LINQ انجام خواهد شد.

خط زیر را برای فراخوانی expression برای  Select کردن و برگرداندن list به کدتان اضافه کنید.

 return source.Select(selectExpression).ToList();

در اینجا یک متد کامل آورده شده است.

1.	public static class QueryableExtension  
2.	{  
3.	    public static IEnumerable < SelectListItem > GetTable < T > (this IQueryable < T > source)  
4.	    {  
5.	  
6.	  
7.	        KeyValuePair < PropertyInfo, PropertyInfo > sourceDestPropMap1 = new KeyValuePair < PropertyInfo, PropertyInfo > (  
8.	            // Text prop of selected item  
9.	            typeof(SelectListItem).GetProperty("Text"),  
10.	            // Name prop of T class  
11.	            typeof(T).GetProperty("Name"));  
12.	  
13.	        KeyValuePair < PropertyInfo, PropertyInfo > sourceDestPropMap2 = new KeyValuePair < PropertyInfo, PropertyInfo > (  
14.	            // Value prop of Selected Item  
15.	            typeof(SelectListItem).GetProperty("Value"),  
16.	            // Id prop from T class  
17.	            typeof(T).GetProperty("Id"));  
18.	  
19.	        var name = "i";  
20.	  
21.	        // i  
22.	        var paramExpr = Expression.Parameter(typeof(T), name);  
23.	  
24.	        // i.Name  
25.	        var propertyA = Expression.Property(paramExpr, sourceDestPropMap1.Value);  
26.	        // i.Id  
27.	        var propertyB = Expression.Property(paramExpr, sourceDestPropMap2.Value);  
28.	  
29.	        // i.Id.Tostring()  
30.	        var propertyBToString = Expression.Call(propertyB, typeof(object).GetMethod("ToString"));  
31.	  
32.	        // new SelectListItem()  
33.	        var createObject = Expression.New(typeof(SelectListItem));  
34.	  
35.	        // new SelectListItem() { Text = i.Name, Value = i.Id.ToString() }  
36.	        var InitializePropertiesOnObject = Expression.MemberInit(  
37.	            createObject,  
38.	            new []  
39.	            {  
40.	                Expression.Bind(sourceDestPropMap1.Key, propertyA),  
41.	                    Expression.Bind(sourceDestPropMap2.Key, propertyBToString)  
42.	            });  
43.	  
44.	        // i => new SelectListItem() { Text = i.Name, Value = i.Id.ToString() };  
45.	        var selectExpression = Expression.Lambda < Func < T,  
46.	            SelectListItem >> (InitializePropertiesOnObject, paramExpr);  
47.	  
48.	  
49.	        return source.Select(selectExpression).ToList();  
50.	    }  
51.	}  

اکنون به جای استفاده از if…else میتوانیم به راحتی این متد را فراخوانی کنیم.

1.	db.TestDemo1.GetList();  
2.	db.TestDemo2.GetList();  
3.	db.TestDemo3.GetList();  
4.	db.TestDemo4.GetList();  

به شکل مشابه میتوانیم هر نوع expression دیگر را بسازیم و از آن برای داینامیکLinq استفاده کنیم.

آموزش سی شارپ

برنامه نویسان

نویسنده 3355 مقاله در برنامه نویسان
  • C#.net
  • 2k بازدید
  • 2 تشکر

کاربرانی که از نویسنده این مقاله تشکر کرده اند

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