Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhancement request: Support Generics for FilteringOptionsBaseAttribute in Custom Expression for a Property #64

Open
neozhu opened this issue Mar 16, 2023 · 7 comments
Labels

Comments

@neozhu
Copy link

neozhu commented Mar 16, 2023

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.

@enisn enisn added the feature label Mar 16, 2023
@enisn
Copy link
Owner

enisn commented Mar 16, 2023

We can create some adapter packages for those libraries so they can be used with autofilterer without a pain

@neozhu
Copy link
Author

neozhu commented Mar 16, 2023

thank you

@neozhu neozhu closed this as completed Mar 16, 2023
@enisn enisn reopened this Mar 16, 2023
@enisn
Copy link
Owner

enisn commented Mar 16, 2023

Keep this issue open until the development is completed for Generics support :)

@neozhu
Copy link
Author

neozhu commented Mar 17, 2023

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".

#65

@neozhu
Copy link
Author

neozhu commented Mar 17, 2023

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,
}

@neozhu
Copy link
Author

neozhu commented Mar 20, 2023

@enisn
Do you have a release plan for this?

@neozhu
Copy link
Author

neozhu commented Apr 4, 2023

Hello @enisn,

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants