Skip to main content

Command Palette

Search for a command to run...

Sieve vs Gridify in .NET: Which Filtering Library to Use?

Updated
โ€ข11 min read

Every production ASP.NET Core API eventually needs the same thing: a way for clients to filter a list of resources, sort the results, and page through them efficiently. The naive approach โ€” hand-rolling query string parsing and building IQueryable chains yourself โ€” works once, then becomes a maintenance burden the moment a second endpoint needs the same treatment. That is where libraries like Sieve, Gridify, and OData step in. Choosing between them is not obvious. Each solves the same core problem with different philosophies, trade-offs, and ceilings โ€” and picking the wrong one shows up months later as hard-to-extend query logic or an API surface you did not intend to expose. The full production implementation โ€” with sorting, filtering, and a working PagedResult<T> wired into a real API โ€” is available on Patreon, ready to drop into your next project.

Seeing filtering and sorting work in isolation is useful. Understanding how they compose with EF Core queries, pagination wrappers, and IQueryable chaining inside a complete production API is what separates a working prototype from a production-ready codebase. Chapter 4 of the Zero to Production course covers exactly this โ€” filtering, sorting, and cursor-based pagination all wired together in the same codebase.

ASP.NET Core Web API: Zero to Production

What These Libraries Actually Solve

Before reaching for any library, it helps to understand what problem they are all solving at the same level. A client hits GET /products?name=widget&price>10&sort=name&page=2&pageSize=25. Somewhere in your API, you need to:

  1. Parse the query string into filtering predicates
  2. Translate those predicates into an IQueryable expression tree
  3. Apply a sort order to the same query
  4. Apply Skip and Take for pagination

All three libraries โ€” Sieve, Gridify, and OData โ€” handle this pipeline. They differ in how much control the client gets, how much configuration the developer must provide, and what the API contract looks like.


Sieve: Attribute-Driven, Explicit Control

Sieve takes a developer-first approach. You explicitly mark which properties on your domain entities are filterable and sortable using [Sieve(CanFilter = true, CanSort = true)] attributes. Nothing is filterable unless you explicitly opt in.

The query string convention is simple: ?filters=Name@=Widget&sorts=Price&page=1&pageSize=20. The @= operator is a case-insensitive contains, == is exact match, > and < are range operators. The client gets a well-defined, intentional surface.

What Sieve does well:

  • The attribute approach means the server is always in control. A client cannot filter on a field you have not marked โ€” there is no accidental data leakage through queries.
  • Works cleanly with EF Core IQueryable โ€” the filtering, sorting, and pagination all run database-side before materialisation.
  • The operator syntax is readable and easy to document. ?filters=Status==Active,CreatedAt>2026-01-01 reads clearly.
  • Custom sort/filter logic is supported via override methods in a custom SieveProcessor.
  • The library is mature, well-tested, and the Biarity/Sieve GitHub repository has been the standard reference for this pattern in the .NET ecosystem for years.

Where Sieve shows friction:

  • The attribute approach has a hidden cost: your domain entities start carrying infrastructure concerns. If you care about keeping your domain layer clean, decorating entity properties with [Sieve] attributes feels like a leak. You can work around this using a fluent configuration override, but it adds setup.
  • Multi-tenant scenarios where different clients should see different filterable fields require custom processors.
  • The library's maintainer has had periods of low activity โ€” check the issue tracker before committing to it in a long-lived codebase.
  • Complex nested filtering (filtering on a child collection's property) requires custom handling.

When to reach for Sieve:

  • You want a simple, explicit opt-in model
  • The team is comfortable decorating entities with attributes
  • API clients are mostly internal and the query surface is predictable
  • You want tight control over what is exposed via query parameters

Gridify: Fast, Convention-Based, LINQ-First

Gridify takes a different stance. It is a lightweight library that parses a filtering string expression into an IQueryable<T> expression tree dynamically, without requiring attribute decorations on your entities. The client sends ?filter=name=Widget AND price>10&orderBy=name asc&page=2&pageSize=25.

The parsing is expression-tree based, which keeps it close to native LINQ performance. Independent benchmarks on the Gridify documentation show it operating near the speed of a hand-written LINQ expression for common filter operations โ€” a meaningful advantage over reflection-heavy alternatives.

What Gridify does well:

  • Zero attribute decoration required โ€” works against any POCO, DTO, or entity
  • Clean expression-tree compilation means the filtering translates directly to SQL via EF Core with no N+1 risks
  • The filter syntax is natural and client-friendly: name=Widget AND price>10 OR category=Electronics
  • Mapper classes (GridifyMapper<T>) let you define which fields are mappable without touching entity classes โ€” keeping domain and API surface separate
  • Actively maintained (alirezanet/Gridify on GitHub) with regular releases

Where Gridify shows friction:

  • The filter syntax (name=Widget AND price>10) is more expressive than Sieve, which is a double-edged sword โ€” more powerful queries also means a larger attack surface if you are not careful about what is exposed through the mapper
  • Documentation for advanced scenarios (nested relationships, custom type converters) is thinner than Sieve
  • The default behaviour allows filtering on all public properties of the target type unless you explicitly configure a mapper โ€” the inverse of Sieve's opt-in model

When to reach for Gridify:

  • You want clean entity classes without attribute decoration
  • Performance is a priority and you want expression-tree-compiled filters
  • API clients need a flexible, expressive filter syntax
  • The team already works with DTOs as the query surface (not raw entities)

OData: Protocol-Level, Maximum Power, Maximum Overhead

OData is not a library โ€” it is a standardised protocol for building queryable APIs. The ASP.NET Core implementation (Microsoft.AspNetCore.OData) exposes the full OData query specification: \(filter, \)orderby, \(select, \)expand, \(top, \)skip, $count.

What this buys you is enormous power: clients can select individual fields, expand navigation properties (joins), apply nested filters on related entities, and get precise metadata about the query result. Tooling in Power BI, Excel, and enterprise reporting suites understand OData natively.

What OData does well:

  • The most capable query language of the three โ€” clients can express queries that would require multiple round-trips with the other libraries
  • $expand enables client-side relationship traversal without building custom endpoints
  • Native tooling integration โ€” BI tools, Excel data connections, and enterprise integration platforms understand OData out of the box
  • Standardised, versioned protocol โ€” behaviour is documented, predictable, and not library-specific
  • The Microsoft OData library for ASP.NET Core is production-grade and maintained by the OData team

Where OData shows friction:

  • Adds significant API surface area. A poorly configured OData endpoint can expose more of your data model than intended.
  • The $expand feature can generate expensive database queries if Eager Loading is not carefully controlled
  • OData's EDM (Entity Data Model) setup requires non-trivial configuration and tightly couples your API to your EF Core model
  • Response envelopes change shape (OData wraps results in @odata.context, value, etc.) โ€” clients and tests must handle this format
  • For most public REST APIs, OData is far more than what is needed and the overhead shows in onboarding cost
  • The learning curve is steep. Senior engineers encountering OData for the first time still take meaningful time to understand \(metadata, \)expand semantics, and query validation configuration

When OData makes sense:

  • Internal enterprise APIs consumed by BI tools, Power BI, or Excel
  • APIs that need to support ad-hoc querying from non-developer consumers
  • Integration scenarios where OData compatibility is a hard requirement

Side-by-Side Comparison

Sieve Gridify OData
Setup effort Low-medium Low High
Client syntax ?filters=Name==x ?filter=Name=x AND Price>10 ?$filter=Name eq 'x'
Opt-in/out model Attribute opt-in Mapper-controlled EDM-defined
Entity decoration required Yes (or custom processor) No No
Performance Good (IQueryable) Excellent (expression tree) Good (but $expand risks)
Complex filtering Limited without custom code Strong Very strong
Client tooling support None None Power BI, Excel, etc.
Maintenance status Stable, periodic updates Active Active (Microsoft)
API surface control High Medium (with mapper) Low without careful config
Best for Internal APIs, explicit control REST APIs, clean entities Enterprise BI / ad-hoc query

Does Your ASP.NET Core API Need a Library at All?

What Is the Best Approach for Filtering in ASP.NET Core?

For small APIs with three to five filterable endpoints, hand-rolled IQueryable filtering is perfectly reasonable. A typed ProductQueryParams record with explicit filter properties โ€” price range, category, name โ€” gives you full control with zero dependency. The cost shows up at scale: fifteen endpoints, fifty filterable fields, and inconsistent query string conventions across the team.

At that point, a library removes the duplication, standardises the query contract, and keeps individual endpoints focused on authorisation and business logic rather than filter plumbing.


The Clear Winner for Most Teams

Sieve is the right default for teams building internal or semi-public REST APIs where you want deliberate, audited control over what is filterable. The attribute model is explicit โ€” every filterable field is a conscious decision.

Gridify is the better choice when entity cleanliness matters, performance is a priority, or API clients need an expressive filter syntax. The mapper pattern keeps your domain layer free of infrastructure concerns while still giving you tight control.

OData is the right call only when enterprise tooling integration is a hard requirement or you are building an internal data API consumed by BI tools. For most REST APIs, OData is significant overhead for a problem that Sieve or Gridify solve more cleanly.

The recommendation: start with Gridify for new projects. The mapper pattern scales cleanly, the syntax is intuitive, and the near-native performance means you are not paying a penalty as query complexity grows. Switch to Sieve if your team strongly prefers the attribute-based opt-in model. Only reach for OData when the enterprise tooling requirement is explicit.


โ˜• Prefer a one-time tip? Buy us a coffee โ€” every bit helps keep the content coming!


FAQ

Is Sieve still actively maintained in 2026? The core library (Biarity/Sieve) has had periods of reduced activity. The fundamentals are stable but check the GitHub issue tracker for any recent .NET version compatibility concerns before committing to a new project. Community forks are available if the primary repo falls behind.

Can Gridify be used safely with EF Core without causing N+1 queries? Yes โ€” Gridify generates expression trees that EF Core translates directly to SQL. Filtering, sorting, and pagination all execute server-side before materialisation. The key is to ensure you are passing an IQueryable<T> (not an IEnumerable<T>) to the Gridify processor, so no premature client-side evaluation occurs.

Does OData work with Minimal APIs in ASP.NET Core? OData support in ASP.NET Core historically required Controllers and was not compatible with Minimal APIs. As of recent Microsoft.AspNetCore.OData releases, partial support has been added, but the integration is not as clean as with controller-based APIs. For OData use cases, Controllers remain the more reliable choice.

What is the difference between Sieve's @= operator and ==? == is an exact match (case-sensitive by default, configurable). @= is a case-insensitive contains โ€” equivalent to LIKE '%value%' in SQL. Sieve also supports _= (starts with), =@ (ends with), and _-= (case-insensitive starts with), giving clients a useful range of string matching without exposing raw SQL.

Can I use Sieve or Gridify with non-EF Core data sources? Both libraries work against any IQueryable<T> implementation. In practice, you can use them with Dapper by materialising data to a list first, but you lose the database-side execution benefit โ€” filtering will happen in memory. For non-relational or non-IQueryable data sources, manual filtering logic is often the better trade-off.

Which library handles multi-column sorting best? All three support multi-column sorting. Sieve uses a comma-separated sorts parameter (?sorts=Name,-Price where - prefix means descending). Gridify uses ?orderBy=Name asc, Price desc. OData uses ?$orderby=Name asc, Price desc. Gridify's syntax is arguably the most readable for multi-column cases.

Is Gridify's filter syntax safe from injection attacks? Gridify parses filter strings into expression trees โ€” it does not produce raw SQL strings. The expression tree is passed to the LINQ provider, which handles parameterisation. As long as you are using a mapper to restrict which properties are queryable, there is no SQL injection risk. Always define a GridifyMapper<T> rather than relying on the default open mapping.

More from this blog

C

Coding Droplets

225 posts

Coding Droplets is your go-to resource for .NET and ASP.NET Core development. Whether you're just starting out or building production systems, you'll find practical guides, real-world patterns, and clear explanations that actually make sense.

From beginner-friendly tutorials to advanced architecture decisions. We publish fresh .NET content every day to help you grow at every stage of your career.