enisn / AutoFilterer

AutoFilterer is a mini filtering framework library for dotnet. The main purpose of the library is to generate LINQ expressions for Entities over DTOs automatically. The first aim is to be compatible with Open API 3.0 Specifications
MIT License
458 stars 37 forks source link

Composite search filter can not search for Enums. #58

Open Cubody opened 1 year ago

Cubody commented 1 year ago

I have such filter:

public class StudentInfoShortResponseFilter : PaginationFilterBase
{
    [CompareTo(nameof(StudentInfoShortResponse.FullName),
        nameof(StudentInfoShortResponse.AcademicGroup),
        nameof(StudentInfoShortResponse.StudentIdCard),
        nameof(StudentInfoShortResponse.Institute))]
    [StringFilterOptions(StringFilterOption.Contains)]
    [ToLowerContainsComparison]
    public string? Search { get; set; }
}

And when I'm trying to write in 'search' anything - it always gives me all records, not filtering it. StudentInfoShortResponse.Institute - enum type. If I'll delete this from attribute - it will search for other properties.

With such enum property in filter attribute it stops write filter in query.

If I'll write

public Institute? Institute {get;set;}

in filter - all will be fine too.

Cubody commented 1 year ago

System.ArgumentException: Method 'Boolean Contains(System.String)' declared on type 'System.String' cannot be called with instance of type 'ItPos.DataAccess.Common.Models.User.Institute' at System.Linq.Expressions.Expression.ValidateCallInstanceType(Type instanceType, MethodInfo method) at System.Linq.Expressions.Expression.ValidateStaticOrInstanceMethod(Expression instance, MethodInfo method) at System.Linq.Expressions.Expression.ValidateMethodAndGetParameters(Expression instance, MethodInfo method) at System.Linq.Expressions.Expression.Call(Expression instance, MethodInfo method, Expression arg0) at System.Linq.Expressions.Expression.Call(Expression instance, MethodInfo method, IEnumerable`1 arguments) at System.Linq.Expressions.Expression.Call(Expression instance, MethodInfo method, Expression[] arguments) at AutoFilterer.Attributes.StringFilterOptionsAttribute.BuildExpressionWithoutComparison(StringFilterOption option, Expression expressionBody, PropertyInfo property, Object value) at AutoFilterer.Attributes.StringFilterOptionsAttribute.BuildExpression(Expression expressionBody, PropertyInfo targetProperty, PropertyInfo filterProperty, Object value) at AutoFilterer.Attributes.CompareToAttribute.BuildExpressionForProperty(Expression expressionBody, PropertyInfo targetProperty, PropertyInfo filterProperty, Object value) at AutoFilterer.Types.FilterBase.BuildExpression(Type entityType, Expression body)

enisn commented 1 year ago

Institute is a different type than string, so string and Institute can't be compared. You should compare Search parameter to one of the Institute parameter.

Something like"Institute.Name" should be working:

public class StudentInfoShortResponseFilter : PaginationFilterBase
{
    [CompareTo(nameof(StudentInfoShortResponse.FullName),
        nameof(StudentInfoShortResponse.AcademicGroup),
        nameof(StudentInfoShortResponse.StudentIdCard),
        "Institute.Name")]
    [StringFilterOptions(StringFilterOption.Contains)]
    [ToLowerContainsComparison]
    public string? Search { get; set; }
}
enisn commented 1 year ago

Oh, sorry, I missed that Institute is an enum.

Enums are numeric fields and stored as numeric value types in databases by default. If you want to compare strings to enums, you should project enum values as string value in a different field and filter that field with autofilterer. That projection might be done with Select() LINQ method.

myDataSource.Select(x => new MyProjectedResult
{
    FullName = x.FullName,
    AcademicGroup = x. AcademicGroup,
    StudentIdCard = x. StudentIdCard,
    InstituteName = x.Institute?.ToString()
}).ApplyFilter(...)

And you can use InstituteName string field for filtering: Institute is a different type than string, so string and Institute can't be compared. You should compare Search parameter to one of the Institute parameter.

Something like"Institute.Name" should be working:

public class StudentInfoShortResponseFilter : PaginationFilterBase
{
    [CompareTo(nameof(StudentInfoShortResponse.FullName),
        nameof(StudentInfoShortResponse.AcademicGroup),
        nameof(StudentInfoShortResponse.StudentIdCard),
        "InstituteName")] // 👈 here
    [StringFilterOptions(StringFilterOption.Contains)]
    [ToLowerContainsComparison]
    public string? Search { get; set; }
}
Cubody commented 1 year ago

It says then that it can not do query with toString(), like not supported, but I'm using mapster, so Idk.

Cubody commented 1 year ago

Thanks for the your answer btw! It's possible via straight property in Filter like

public Institute? Institute {get;set;}

But not via CompareToAttribute - it's strange thing. But I see why it works like it works.

Cubody commented 1 year ago

When you see that a separate field is working, there is a feeling that the complex variable will also work. I would consider this as a possible vector for improving your solution. If I have the time/opportunity, I will take a look and try to come up with a solution.