عنوان:

‫آیا امکان استفاده از Extension Method در زمان Select وجود دارد؟


نویسنده: هادی مزارعی
تاریخ: ۱۴۰۳/۰۵/۰۶ ۱۰:۳۰
آدرس: www.dntips.ir

سلام

در زمان دریافت اطلاعات از بانک اطلاعاتی می خوام فقط همان ستون هایی که نیاز دارم را از بانک دریافت کنم. بنابراین Query را بصورت زیر نوشتم:

var result = await query.Select(x => new Models.Output.Piping.LineJoints.LineJoint2
{
    Id = x.Id,
    JointNo = x.JointNo
})
.ToListAsync();

حالا برای اینکه از تکرار جلوگیری کنم، یک Extension Method نوشتم که کار تبدیل رو انجام بده:

public static class Ext
{
    public static Models.Output.Piping.LineJoints.LineJoint2 ToModel(
        this Domain.Entities.Piping.LineJoints.LineJoint domain)
    {
        return new Models.Output.Piping.LineJoints.LineJoint2
        {
            Id = domain.Id,
            JointNo = domain.JointNo
        };
    }
}

در نهایت Query را بصورت زیر تغییر دادم:

var result = await query.Select(x => x.ToModel()).ToListAsync();

سوال:

در حالت اول که تمام ستون ها را تعریف میکنم، بانک اطلاعاتی دقیقا همان ستون ها را بر میگرداند ولی در حالتی که از Extension Methodاستفاده کردم، بانک اطلاعاتی تمامی ستون ها را بر می گرداند و در سمت Client تبدیل انجام می شود. آیا راهی وجود داره که بتونم از نوشتن نام تمام ستون ها همانند نمونه اولیه جلوگیری کنم و از نام یک کلاس و یا چیزی شبیه Extension استفاده کنم؟ تشکر

نظرات

  • هادی مزارعی در ۱۴۰۳/۰۵/۰۶ ۱۰:۳۷

    البته یک نمونه در اینترنت پیدا کردم (البته پاسخ Copilot بوده) که متد را بصورت Generic تعریف کرده بود که برای انجام Dynamic Select روش زیر را بکاربرده بود:

    IQueryable<TResult> results = query.SelectMany(x => selectProperties.Select(prop => prop.Compile()(x)));
    

    تا اینجا هیچ مشکلی وجود ندارد ولی به محض آنکه در خط بعدی متد ToList اجرا میشه با پیغام خطا مواجه میشم:

    An exception was thrown while attempting to evaluate a LINQ query parameter expression. See the inner exception for more information. To show additional information call 'DbContextOptionsBuilder.EnableSensitiveDataLogging'.
    

    کد کامل Copilot:

    public async Task<List<LineJoint2>> GetAsync2(
        Expression<Func<LineJoint, bool>> filter,
        List<Expression<Func<LineJoint, object>>> includes,
        List<Func<IIncludableQueryable<LineJoint, object>, IIncludableQueryable<LineJoint, object>>> thenIncludes,
        Expression<Func<IQueryable<LineJoint>, IOrderedQueryable<LineJoint>>> orderBy,
        int pageSize,
        int pageNumber,
        bool trackChanges = true,
        CancellationToken cancellationToken = default)
    {
        var query = context.LineJoints.AsQueryable();
    
        // Apply filter
        if (filter != null)
            query = query.Where(filter);
    
        // Include related entities
        foreach (var include in includes)
            query = query.Include(include);
    
        // Apply thenIncludes
        foreach (var thenInclude in thenIncludes)
            query = thenInclude(query);
    
        // Project properties dynamically
        var projectedQuery = query.Select(x => new LineJoint2
        {
            Id = x.Id,
            JointNo = x.JointNo
            // Add other properties from LineJoint as needed
        });
    
        // Apply ordering
        if (orderBy != null)
            projectedQuery = orderBy.Compile()(projectedQuery);
    
        // Apply pagination
        var pagedQuery = projectedQuery.Skip((pageNumber - 1) * pageSize).Take(pageSize);
    
        // Execute the query
        return await (trackChanges ? pagedQuery : pagedQuery.AsNoTracking()).ToListAsync(cancellationToken);
    }
    
  • وحید نصیری در ۱۴۰۳/۰۵/۰۶ ۱۲:۵۶

    قبلا مطلبی رو در مورد توانایی AutoMapper در تهیه‌ی یک چنین نگاشت‌هایی با استفاده از متد project to آن، تهیه کردم. البته کلیاتش فرقی نکرده؛ ولی syntax اش تغییر کرده که آخرین به روز رسانی اون رو در اینجا می‌تونید مطالعه کنید. به‌شما توصیه می‌کنم از همین روش استفاده کنید و سعی نکنید این متد رو خودتون بازنویسی کنید و بهتر از است از همان نمونه‌‌ای که سال‌هاست آزمایش شده و در حال استفاده‌است، کمک بگیرید.

    return context.OrderLines.Where(ol => ol.OrderId == orderId)
                 .ProjectTo<OrderLineDTO>(configuration).ToList();
    

    • هادی مزارعی در ۱۴۰۳/۰۵/۰۶ ۱۶:۵۱

      ضمن تشکر. جهت یادگیری میپرسم. اگرچه که AutoMapper میتونه مشکل را حل کنه ولی تمام ستون های مورد نیازم رو باید در configuration تعریف کنم. آیا اگر خودم بتونم یک Selector مانند نمونه زیر ایجاد کنم بهتر نیست؟ دلیل این کار کاهش وابستگی و راندمان بیشتره. آیا ایجاد Selectorی مانند زیر این دو هدف را برآورده میکنه؟

      private static Expression<Func<LineJoint, Models.Output.Piping.LineJoints.LineJoint2>> Selector()
      {
          //if (Condition())
          //    return x => new Models.Output.Piping.LineJoints.LineJoint2
          //    {
          //        ....
          //    };
      
          return x => new Models.Output.Piping.LineJoints.LineJoint2
          {
              Id = x.Id,
              JointNo = x.JointNo
          };
      }
      

      در نتیجه میتونم query را بصورت زیر اجرا کنم:

      var result = await query.Select(Selector()).ToListAsync();
      
      • هادی مزارعی در ۱۴۰۳/۰۵/۰۶ ۱۷:۲۸

        گویا نیازی به تعریف ستون ها نیست (تازه متوجه شدم) و به روش زیر هم میشه ساده انجام بشه:

        public class JoitnProfile : Profile
        {
            public JoitnProfile()
            {
                CreateMap<LineJoint, Models.Output.Piping.LineJoints.LineJoint2>();
            }
        }
        

        و استفاده از ConfigurationProvider پیش فرض:

        var result = await query.ProjectTo<Models.Output.Piping.LineJoints.LineJoint2>(_mapper.ConfigurationProvider).ToListAsync();
        

        پروژه Selector تعطیل و AutoMapper جایگزین میکنم. تشکر جناب نصیری