AutoFilter simplifies data filtering using Expression Trees. Reduces the need for writing filtering logic on backend side.
Inspired by Max Arshinov's article.
All operations were tested against EF Core and PostgreSql.
Any issues or criticism are welcome :)
Create a model
class Product
{
public Guid Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public bool IsInStock { get; set; }
public bool IsForSale { get; set; }
public DateTime ExpireDate { get; set; }
}
Build a filter
var filter = new AutoFilter
(
Filter: new FilterRule
(
Conditions: new[]
{
new Condition
(
Name: "Name",
SearchOperator: SearchOperator.Contains,
Value: new[]{ "Snickers" }
),
new Condition
(
LogicOperator: LogicOperator.And,
Name: "IsForSale",
SearchOperator: SearchOperator.Equals,
Value: new[] { "true" }
),
}
)
);
Apply filter to queryable
queryable.ApplyFilter(filter);
Under the hood filter transforms into call
queryable.Where(x => x.Name.Contains("Snickers") && x.IsForSale);
queryable.Where(x => ((x.Name.StartsWith("Snickers") || x.Name.Contains("Mars")) && x.ExpireDate >= DateTime.UtcNow) && (x.IsForSale || x.IsInStock))
var filter = new AutoFilter
(
Filter: new FilterRule
(
new Condition[]
{
new
(
Name: nameof(Product.Name),
SearchOperator: SearchOperator.StartsWith,
Value: new[]{ "Snickers" }
),
new
(
LogicOperator: LogicOperator.Or,
Name: nameof(Product.Name),
SearchOperator: SearchOperator.Contains,
Value: new[]{ "Mars" }
),
new
(
LogicOperator: LogicOperator.And,
Name: nameof(Product.ExpireDate),
SearchOperator: SearchOperator.GreaterOrEqual,
Value: new[] { DateTime.UtcNow.ToString("u") }
),
new
(
LogicOperator: LogicOperator.And,
Name: nameof(Product.IsForSale),
SearchOperator: SearchOperator.Equals,
Value: new[] { "true" }
),
new
(
LogicOperator: LogicOperator.Or,
Name: nameof(Product.IsInStock),
SearchOperator: SearchOperator.Equals,
Value: new[] { "true" }
),
},
new Group[]
{
new
(
Start: 1,
End: 2,
Level: 1
),
new
(
Start: 1,
End: 3,
Level: 2
),
new
(
Start: 4,
End: 5,
Level: 2
)
}
)
);
queryable.ApplyFilter(filter);
queryable.Distinct();
var filter = new AutoFilter
(
DistinctBy: ""
);
queryable.ApplyFilter(filter);
queryable.DistinctBy(x => x.Name);
var filter = new AutoFilter
(
DistinctBy: "Name"
);
queryable.ApplyFilter(filter);
queryable.OrderBy(x => x.ExpireDate);
var filter = new AutoFilter
(
Sorting: new SortingRule
(
PropertyName: "ExpireDate"
)
);
queryable.ApplyFilter(filter);
queryable.OrderBy(x => x.ExpireDate).ThenByDescending(x => x.Price);
var filters = new AutoFilter[]
{
new
(
Sorting: new SortingRule
(
PropertyName: "ExpireDate"
)
),
new
(
Sorting: new SortingRule
(
PropertyName: "Price",
ThenBy: true,
IsDescending: true
)
),
};
queryable.ApplyFilters(filters);
queryable.Skip(5).Take(1);
var filters = new[]
{
new AutoFilter(Skip: 5),
new AutoFilter(Top: 1)
};
queryable.ApplyFilters(filters);
queryable.Select(x => new Dictionary<string, object>()
{
{ "Id", x.Id },
{ "Name", x.Name }
})
var filter = new AutoFilter
(
Select: new[] { "Id", "Name" }
);
queryable.ApplyFilterAndSelect(filter);
This implementation of "Select" was made due to the limitations of anonymous types and should be used as the last operation and only to reduce the final number of properties to be fetched from the database.