Open neozhu opened 1 year ago
We can create some adapter packages for those libraries so they can be used with autofilterer without a pain
thank you
Keep this issue open until the development is completed for Generics support :)
Hi @enisn ,
Thank you very much for your attention to this topic.
I have an idea and I'm wondering if it's possible to implement it in your project?
Let me first share my code snippet. Once you see it, you'll understand. I believe it's a way to save a lot of work and make the code more concise.
I plan to do three fixed queries in advance, such as 1. all (unconditional), 2. query records created by me (CreateBy == UserId Of Login), and 3. query records created today (Created == Today)
public class ProductsWithPaginationQuery : PaginationFilterBase, ICacheableRequest<PaginatedData<ProductDto>>
{
public string? Name { get; set; }
public string? Brand { get; set; }
public string? Unit { get; set; }
public Range<decimal> Price { get; set; } = new();
[CompareTo("Name", "Brand", "Description")] // <-- This filter will be applied to Name or Brand or Description.
[StringFilterOptions(StringFilterOption.Contains)]
public string? Keyword { get; set; }
[CompareTo(typeof(SearchProductsWithListView), "Name")]
public ProductListView ListView { get; set; } = ProductListView.All; //<-- When the user selects a different ListView,
// a custom query expression is executed on the backend.
// For example, if the user selects "My Products",
// the query will be x => x.CreatedBy == CurrentUser.UserId
public UserProfile? CurrentUser { get; set; } // <-- This CurrentUser property gets its value from the information of
// the currently logged in user
public override string ToString()
{
return $"CurrentUser:{CurrentUser?.UserId},ListView:{ListView},Search:{Keyword},Name:{Name},Brand:{Brand},Unit:{Unit},MinPrice:{Price?.Min},MaxPrice:{Price?.Max},Sort:{Sort},SortBy:{SortBy},{Page},{PerPage}";
}
public string CacheKey => ProductCacheKey.GetPaginationCacheKey($"{this}");
public MemoryCacheEntryOptions? Options => ProductCacheKey.MemoryCacheEntryOptions;
}
public enum ProductListView
{
[Description("All")]
All,
[Description("My Products")]
My,
[Description("Created Toady")]
CreatedToday,
}
public class SearchProductsWithListView : FilteringOptionsBaseAttribute
{
public override Expression BuildExpression(Expression expressionBody, PropertyInfo targetProperty, PropertyInfo filterProperty, object value)
{
var today = DateTime.Now.Date;
var start = Convert.ToDateTime(today.ToString("yyyy-MM-dd",CultureInfo.CurrentCulture) + " 00:00:00", CultureInfo.CurrentCulture);
var end = Convert.ToDateTime(today.ToString("yyyy-MM-dd",CultureInfo.CurrentCulture) + " 23:59:59", CultureInfo.CurrentCulture);
//var currentUser = filterProperty.CurrentUser;
var listview = (ProductListView)value;
return listview switch {
ProductListView.All => expressionBody,
//ProductListView.My=> Expression.Equal(Expression.Property(expressionBody, "CreatedBy"), Expression.Constant(currentUser?.UserId)),
ProductListView.CreatedToday => Expression.GreaterThanOrEqual(Expression.Property(expressionBody, "Created"),
Expression.Constant(start, typeof(DateTime?)))
.Combine(Expression.LessThanOrEqual(Expression.Property(expressionBody, "Created"),
Expression.Constant(end, typeof(DateTime?))),
CombineType.And),
_=> expressionBody
};
}
}
///// ProductListView.CreatedToday => Expression.GreaterThanOrEqual(Expression.Property(expressionBody, "Created"),
// Expression.Constant(start, typeof(DateTime?)))
// .Combine(Expression.LessThanOrEqual(Expression.Property(expressionBody, "Created"),
// Expression.Constant(end, typeof(DateTime?))),
// CombineType.And),
// this Expression is working.
However, the code snippet I shared is not yet compiling. I need your help to implement this feature. Do you have any good ideas on how to achieve this?
The problem I'm facing now is that I cannot get the property value of the current user of ProductsWithPaginationQuery in class SearchProductsWithListView : FilteringOptionsBaseAttribute{}, so I cannot build the query expression for the query "created by me".
The following code is my own way to implement the query expression made by the ListView: this is my project :https://github.com/neozhu/CleanArchitectureWithBlazorServer
public class DocumentsWithPaginationQuery : PaginationFilter, ICacheableRequest<PaginatedData<DocumentDto>>
{
public DocumentListView ListView { get; set; } = DocumentListView.All;
public required UserProfile CurrentUser { get; set; }
public override string ToString()
{
return $"CurrentUser:{CurrentUser?.UserId},ListView:{ListView},Search:{Keyword},OrderBy:{OrderBy} {SortDirection},{PageNumber},{PageSize}";
}
public string CacheKey => DocumentCacheKey.GetPaginationCacheKey($"{this}");
public MemoryCacheEntryOptions? Options => DocumentCacheKey.MemoryCacheEntryOptions;
}
public class DocumentsQueryHandler : IRequestHandler<DocumentsWithPaginationQuery, PaginatedData<DocumentDto>>
{
private readonly IApplicationDbContext _context;
private readonly IMapper _mapper;
public DocumentsQueryHandler(
IApplicationDbContext context,
IMapper mapper
)
{
_context = context;
_mapper = mapper;
}
public async Task<PaginatedData<DocumentDto>> Handle(DocumentsWithPaginationQuery request, CancellationToken cancellationToken)
{
var data = await _context.Documents
.Specify(new DocumentsQuery(request))
.OrderBy($"{request.OrderBy} {request.SortDirection}")
.ProjectTo<DocumentDto>(_mapper.ConfigurationProvider)
.PaginatedDataAsync(request.PageNumber, request.PageSize);
return data;
}
internal class DocumentsQuery : Specification<Document>
{
public DocumentsQuery(DocumentsWithPaginationQuery request)
{
Criteria = request.ListView switch
{
DocumentListView.All => p => (p.CreatedBy == request.CurrentUser.UserId && p.IsPublic == false) || (p.IsPublic == true && p.TenantId == request.CurrentUser.TenantId),
DocumentListView.My => p => (p.CreatedBy == request.CurrentUser.UserId && p.TenantId == request.CurrentUser.TenantId),
DocumentListView.CreatedToday => p => p.Created.Value.Date == DateTime.Now.Date,
_ => throw new NotImplementedException()
};
if (!string.IsNullOrEmpty(request.Keyword))
{
And(x => x.Title.Contains(request.Keyword) || x.Description.Contains(request.Keyword) || x.Content.Contains(request.Keyword));
}
}
}
}
public enum DocumentListView
{
[Description("All")]
All,
[Description("My Document")]
My,
[Description("Created Toady")]
CreatedToday,
}
@enisn Do you have a release plan for this?
Hello @enisn,
I have an idea related to the "Custom Expression for a Property" feature mentioned in your documentation. My idea is to make the FilteringOptionsBaseAttribute support generics, which would make writing LINQ expressions easier. Do you think this is possible?
For reference, here is another project that might be helpful: https://github.com/ClaveConsulting/Expressionify https://github.com/lukemcgregor/LinqExpander
Thank you for considering my suggestion.