آیا امکان استفاده از 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 جایگزین میکنم. تشکر جناب نصیری