بخش دوم - بررسی امکانات (کلاس ها و متدهای) کتابخانه Gridify
اندازه‌ی قلم متن
تخمین مدت زمان مطالعه‌ی مطلب: سه دقیقه

در بخش قبل، به چند نمونه کلی از امکانات کتابخانه Gridify اشاره کردیم. در این مقاله به معرفی کلاس‌ها و متدهای این کتابخانه میپردازیم.

GridifyQuery
از این کلاس برای اعمال تنظیمات مورد نیاز در متدهای ارائه شده توسط Gridify استفاده میشود. در ادامه به خصیصه (پراپرتی)های این کلاس میپردازیم.
  • Filter : یک پراپرتی از نوع string است که درصورت مقداردهی آن، بر روی لیست خروجی ما عملیات فیلترینگ اعمال میشود. مثال :
Filter = "Name==Ali,Age>>10";
  • SortBy : یک پراپرتی از نوع string است که درصورت مقداردهی آن، بر روی لیست خروجی ما عملیات چیدمان یا سورتینگ با استفاده از نام فیلد انجام میشود. مثال : 
SortBy = "Age";
  • IsSortAsc: یک پراپرتی از نوع bool است که مشخص کننده چیدمان به صورت نزولی و یا صعودی است.
  • Page : یک پراپرتی از نوع عددی short است. از این پراپرتی برای عملیات Pagination یا صفحه بندی استفاده میشود که مشخص کننده شماره صفحه درخواستی است.
  • PageSize : یک پراپرتی از نوع عددی int است که برای مشخص کردن تعداد رکورد در هر صفحه استفاده میشود.
وارد کردن این اطلاعات هنگام استفاده از کتابخانه Gridify الزامی نیست؛ به همین جهت تنها در صورت مقداردهی، از این اطلاعات استفاده میشود. درصورتیکه هیچ اطلاعاتی در این پراپرتی‌ها وجود نداشته باشد، به صورت پیش فرض توسط Gridify نادیده گرفته میشود. البته استثنایی برای اکستنشن متد Gridify و GridifyAsync وجود دارد، به دلیل اینکه خروجی این دو متد یک کلاس <Paging<T است. در صورت اینکه مقداری برای پراپرتی‌های Page و PageSize وارد نشده باشد، به صورت پیش فرض اطلاعات Page 1 با DefaultPageSize را بازمیگرداند که مقدار پیش فرض آن 10 میباشد. با استفاده از تغییر فیلد استاتیک DefaultPageSize میتوان این عدد نیز را تغییر داد.


متد‌های الحاقی یا IQueryable  Extensions 
برای استفاده از کتابخانه Gridify نیازی به ساخت هیچ کلاسی نیست و صرفا امکانات اینترفیس IQueryable را گسترش میدهد.
 ApplyFiltering  از این متد برای اعمال فیلترینگ روی یک IQueryable استفاده میشود. این متد یک رشته متنی (string) ویا یک GridifyQuery دریافت کرده و پس از اعمال فیلترینگ یک IQueryable بازمیگرداند.
 ApplyOrdering  از این متد برای اعمال چیدمان یا Sorting روی یک IQueryable استفاده میشود. پس از اعمال چیدمان، یک IQueryable را بازمیگرداند.
 ApplyPaging از این متد برای اعمال صفحه بندی (Pagination) استفاده میشود. پس از اعمال صفحه بندی یک IQueryable را بازمیگرداند. 
 ApplyOrderingAndPaging   از این متد برای اعمال همزمان چیدمان و صفحه بندی استفاده میشود که یک IQueryable را باز میگرداند. 
ApplyFilterAndOrdering  از این متد برای اعمال همزمان فیلترینگ و چیدمان استفاده میشود که یک IQueryalbe را باز میگرداند.
 ApplyEverything   از این متد برای اعمال عملیات صفحه بندی، چیدمان و فیلترینگ استفاده میشود که یک IQueryable را باز میگرداند.
 GridifyQueryable   این متد مشابه ApplyEverything است که مقدار یک <QueryablePaging<T را برمیگرداند و دارای یک خصیصه اضافی TotalItems است که در عملیات صفحه بندی عموما نیاز داریم. (تعداد کل رکورد‌های موجود در پایگاه داده، با توجه به فیلتر اعمال شده)
 Gridify  متدهای قبلی فقط به query موجود ما یکسری شرط را اضافه میکردند. ولی مسئولیت اجرای query به عهده ما بود. (مثلا با استفاده از ToList.). متد Gridify تمامی شرط‌ها را باتوجه به GridifyQuery دریافتی اعمال کرده، سپس اطلاعات را بارگذاری کرده و یک <Paging<T را بازمیگرداند که کاملا قابل استفاده و بهینه شده برای دیتاگرید‌ها میباشد.


متد‌های الحاقی GridifyQuery:
 GetFilteringExpression   این متد expression معادل فیلتر string نوشته شده شما را برمیگرداند که میتوانید از آن به طور مثال در متد Where در Linq استفاده نمایید. 
 GetOrderingExpression   این متد expression انتخاب فیلد برای Orderby و OrderByDescending را باتوجه به مقدار وارد شده در فیلد SortBy بازمیگرداند.

Filtering Operators:
با علائم پشتیبانی شده در Gridify برای اعمال فیلترینگ در زیر آشنا میشویم.

همانطور که در تصویر بالا مشاهده میکنید، برای اعمال فیلترینگ‌های پیچیده میتوانیم از چهار اپراتور , | ( )  استفاده کنیم. به همین جهت اگر نیاز داشتید که در مقدار جستجوی خود از این علائم استفاده کنید، باید قبل از هرکدام از آنها، علامت \ را اضافه کنید. 

 مثال رج‌اکس escape character در JavaScript  : 

let esc = (v) => v.replace(/([(),|])/g, '\\$1')

مثال #‍C : 

var value = "(test,test2)";
var esc = Regex.Replace(value, "([(),|])", "\\$1" ); // esc = \(test\,test2\)  

در بخش بعد با امکانات Mapper توکار Gridify و شخصی سازی آن آشنا خواهیم شد.

  • #
    ‫۳ سال و ۱ ماه قبل، پنجشنبه ۷ مرداد ۱۴۰۰، ساعت ۰۲:۲۲
    بنا به درخواست دوستان اپراتورهای << و >> به نوع استاندارد آن < و > در ورژن 1.3.5 تغییر کرد. 

  • #
    ‫۱۱ ماه قبل، سه‌شنبه ۲۵ مهر ۱۴۰۲، ساعت ۱۷:۵۳
    من قصد پیاده سازی یک صفحه برای جستجوی پیشرفته بین همه جداول رو دارم. برای این کار یک کلاس تعریف کردم و در ورودی تابع یک لیست از این نوع را بهش پاس میدم. این کلاس اینطور تعریف شده:
    public class SearchDTO
    {
        public string TableName { get; set; }
        public string ColumnName { get; set; }
        public string searchPhrase { get; set; } = string.Empty;
        public DateTimeOffset? searchDateFrom { get; set; }
        public DateTimeOffset? searchDateTo { get; set; }
        public int Include { get; set; } = 1;
    }
    کاربر اسم جدول و اون ستونی که میخواد شرط رو براش اعمال کنه رو انتخاب میکنه و بعدش عبارت یا محدوده تاریخ رو وارد میکنه. این که شامل بشه یا نشه رو هم با Include میتونه مشخص کنه. 
    من هر چی داکومنت رو خوندم توی همشون اسم جدول رو نمیشد به صورت string وارد کرد. اگه اشتباه میکنم لطفا اصلاح کنید.
    رویه ای که مدنظرم هست اینه که داخل یه حلقه for یا foreach یک کوئری بنویسم که همه‌ی جداولی که کاربر انتخاب کرده رو با هم join کنه. بعدش توی یه حلقه دیگه شرط‌ها رو روی ستون هایی که انتخاب کرده اعمال کنم.
    در نهایت ستون‌های نتیحه نهایی رو Select کنم تا اون ستون هایی که مجاز هستند به سمت کلاینت برگشت داده بشه. اسامی این ستون‌ها رو توی یه فایل .resx ذخیره کردم.
    یعنی یه چیزی شبیه به این کد:(ولی این کد درست و قابل اجرا نیست)
    // Join the tables dynamically based on the table names
    for (int i = 1; i < filterList.Count; i++)
    {
        var joinEntityType = entityTypes.FirstOrDefault(t => t.ClrType.Name == filterList[0].TableName)?.ClrType;
        if (entityType == null)
        {
            return (null, 0, 0);
        }
        var joinEntityQuery = (IQueryable<object>)Activator.CreateInstance(typeof(DbSet<>).MakeGenericType(joinEntityType), _dbContext);
        query = query.Join(joinEntityQuery.ToList(),
                                        x => x.GetType().GetProperty($"{filterList[i - 1].TableName}.{filterList[i - 1].TableName}Id").GetValue(x),
                                        y => y.GetType().GetProperty($"{filterList[i].TableName}.{filterList[i].TableName}Id").GetValue(y),
                                        (x, y) => x);
    }
    
    // Apply the conditions dynamically based on the column names and conditions
    for (int i = 0; i < filterList.Count; i++)
    {
        if (!string.IsNullOrEmpty(filterList[i].searchPhrase))
        {
            var parameter = Expression.Parameter(entityType, "x");
            var condition = Expression.Call(
                typeof(string).GetMethod("Contains", new[] { typeof(string) }),
                Expression.PropertyOrField(parameter, filterList[i].ColumnName),
                Expression.Constant(filterList[i].searchPhrase)
            );
            var lambda = Expression.Lambda<Func<object, bool>>(condition, parameter);
    
            query = query.Where(lambda);
        }
        if (filterList[i].searchDateFrom.HasValue)
        {
           //must write expression for date constraint
        }
    }
    
    // Select the specified columns dynamically
    ResourceManager resourceManager = new ResourceManager(typeof(TablePropertiesResources));
    var columnNames = resourceManager.GetResourceSet(CultureInfo.CurrentCulture, true, true)
        .OfType<DictionaryEntry>()
        .Select(entry => entry.Key.ToString())
        .ToList();
    var selectColumns = columnNames.ToArray();
    var selectedData = query
        .Select(x => new
        {
            // Dynamically select the desired properties
            Result = selectColumns.ToDictionary(column => column, column => x.GetType().GetProperty(column).GetValue(x))
        })
        .ToList();
    آیا اینکار با gridify امکان پذیر می‌باشد؟
    • #
      ‫۱۱ ماه قبل، پنجشنبه ۲۷ مهر ۱۴۰۲، ساعت ۰۲:۱۰
      با استفاده از Gridify شما میتونید query خودتون رو تولید کنید ولی امکان ارسال نام جدول وجود ندارد. ,ولی حدودا 70 درصد از چیزی که نیاز دارید رو در اختیارتون میگذاره.
      یک نکته که شاید کار رو براتون آسون‌تر کنه اینکه: کلاس `GridifyQuery` یک متد به نام `GetFilteringExpression` داره که query رو در اختیارتون قرار میده. فقط به دلیل اینکه generic هست باید در runtime ایجادش کنید. (همینطور کلاس QueryBuilder )

      اگر بخوام یک مثال بزنم, فرض کنید شما قصد تولید چنین کدی در runtime برای  ("TableName= "Users ) دارید
      var result = _db.Users.Where(filteringExpression).ToList();
      با استفاده از Gridify میتونید filteringExpression رو تولید کنید. 
      -----
      راه حل دومی که بنظرم میرسه اینکه یک Dictionary بین نام جداول و Gridify.QueryBuilder تولید کنید و بسته به نام جدول درخواست شده توسط کاربر از QueryBuilder از پیش تعریف شده برای اعمال آن استفاده کنید.
      • #
        ‫۱۱ ماه قبل، سه‌شنبه ۲ آبان ۱۴۰۲، ساعت ۱۲:۱۸
        آیا شما قصد توسعه Gridify رو ندارید؟ طوری که بشه برای این کار هم ازش استفاده کرد
        • #
          ‫۱۱ ماه قبل، چهارشنبه ۳ آبان ۱۴۰۲، ساعت ۰۵:۲۷
          Gridify همچنان در حال توسعه است ولی کاربرد این کتابخانه تبدیل string به Linq هست و صرفا مختص کار با EF و دیتابیس نیست. به همین جهت فکر نمیکنم چنین فیچری رو بهش اضافه کنم. البته شاید در قالب یک Extension library برای EF بشه بهش فکر کرد, ولی در حال حاضر جزء roadmap کتابخانه نیست.