This article introduces a free library that helps .NET developers to easily filter C# objects with object instances, to save them the effort of writing tedious filtering code.
Introduction
During implementation of a web application, searching for data according to certain criteria is a common requirement, that developers provide a searching panel to let business users provide some input values, then the input value gets sent to the server side, and the input values will be used to filter data in the database, at last the filter data will be sent back to web application and response of the search request.
This article introduces a free C# library that may save developers efforts of implementing such filtering logics.
Background
For simple searching use cases, the search logic is always simple and similar, yet for every search, developers have to repeat the tedious and brainless coding to fill it in.
Take a most simplified example of searching for books: Each Book
has a name property and a published year property.
public class Book
{
public string Name { get; set; } = null!;
public int PublishedYear { get; set; }
}
public class SearchForBookDto
{
public string? Name { get; set; }
public int? PublishedYear { get; set; }
}
To search for the book by either or both properties in the database, the code may be like the following:
IQueryable queryable = databaseContext.Set<Book>();
if (searchForBookDto.Name != null)
{
queryable = queryable.Where(book => book.Name == searchForBookDto.Name);
}
if (searchForBookDto.PublishedYear != null)
{
queryable = queryable.Where(book => book.PublishedYear == searchForBookDto.PublishedYear);
}
await queryable.ToListAsync();
It's easy to see in the use case that for each searching property, the logic is the same: if the property value is not null
, apply it to the queryable. This is quite tedious if there are a lot of such search fields to fill in for, or there are a lot of such searching features to implement.
Basic Use Case
Oasis.DynamicFilter
can help to simply the code to below:
var expressionMaker = new FilterBuilder().Register<Book, SearchForBookDto>().Build();
await databaseContext.Set<Book>()
.Where(expressionMaker.GetExpression<Book, SearchForBookDto>(searchForBookDto))
.ToListAsync();
In the two statements:
The first statement is the configuring process, to initialize a filter builder, then register the filtering on Book
with SearchForBookDto
, then it builds the FilterBuilder
instance into an instance of IFilter
interface, which developers can use to generate expressions or functions for filtering.
FilterBuilder
is a centralised class for developers to register all filtering pairs. Developers are expected to register all filtering cases with the same instance, and distribute the IFilter
interface instance it builds into all code that need the feature.
In the second statement, calling of expressionMaker.GetExpression<Book, SearchForBookDto>(searchForBookDto)
makes a Linq Expression
according to value of searchForBookDto
that filters Book
instances. Book
instances with Name
and PublishedYear
properties equal to properties of the same names from searchForBookDto
will be returned.
Support for More Complicated Expressions
To provide more flexibility to developers, Oasis.DynamicFilter
supports filtering entities with more complicated expressions, rather than only letting them visit direct properties. Check the following example:
public sealed class Book
{
public int PublishedYear { get; set; }
public string Name { get; set; } = null!;
public Author Author { get; set; } = null!;
}
public sealed class Author
{
public int BirthYear { get; set; }
public string Name { get; set; } = null!;
}
public sealed class AuthorFilter
{
public string? AuthorName { get; set; }
public int? Age { get; set; }
}
This time Book
has an navigation property named Author
, and to demonstrate the feature, the book searching use case becomes: search for all books that are written by author whose name contains string "John
", and are published before the author went 40
years old.
The following code generates the expression for this purpose:
var expressionMaker = new FilterBuilder()
.Configure<Book, AuthorFilter>()
.FilterByStringProperty(b => b.Author.Name, StringOperator.Contains, f => f.AuthorName)
.FilterByProperty(b => b.PublishedYear - b.Author.BirthYear, Operator.LessThan, f => f.Age)
.Finish()
.Build();
var filter = new AuthorFilter { Age = 40, AuthorName = "John" };
var exp = expressionMaker.GetExpression<Book, AuthorFilter>(filter);
It's apparent that expressions b => b.Author.Name
and b => b.PublishedYear - b.Author.BirthYear
are more expressive than simply visiting Name
or PublishedYear
of Book
, plus in this case, the comparison operators become string contains and integer less than, instead of equal. These features greatly enrich the options for developering to filter entities.
Summary
For a quick demonstration of the use cases mentioned, please download and check the sample code attached. Oasis.DynamicFilter
is implemented in .NET standard 2.1, and the downloadable sample code is a Xunit test library in .NET 6. Sqlite is used in it to prove that it works well with Linq to SQL.
Oasis.DynamicFilter
actually provides more configurable filtering options to developers so filtering with conditions like not equal, greater than, value type in collection or array, collection or array contains value type are possible. It also supports Contains
, StartsWith
and EndsWith
for string
types. To find more details, please visit its GitHub repository.
Should there be any inquiry or suggestion, please leave a comment here or submit a bug under the repository.
History
- 22nd October, 2023: Initial submission
- 28th October, 2023: Package version update to 0.2.1
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.