ParadeDB Entity Framework Core

If you’ve ever tried to use ParadeDB‘s pg_search extension with EF Core, you know the pain. You want BM25 full-text search on PostgreSQL — which pg_search does brilliantly — but EF Core has no idea what a BM25 index is, so you end up hand-writing SQL in your migrations and scattering raw queries across your codebase. I got tired of that, so I built ParadeDB Entity Framework Core, a library that brings native BM25 index support and LINQ query methods to EF Core. No raw SQL, everything goes through migrations and strongly-typed C#.

Getting started takes three steps. First, you enable ParadeDB on your Npgsql options:

services.AddDbContext<MyDbContext>(options =>
    options.UseNpgsql(connectionString, npgsql => npgsql.UseParadeDb()));

Then you decorate your entity with a [Bm25Index] attribute. The first parameter is the key field (pg_search needs it internally for scoring), and the rest are the columns you want indexed:

[Bm25Index(nameof(Id), nameof(Title), nameof(Content))]
public class Article
{
    public Guid Id { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }
}

Finally, you run dotnet ef migrations add and dotnet ef database update. EF Core generates everything automatically — the pg_search extension, the BM25 index with the correct key_field storage parameter, all of it. No manual SQL files to maintain.

The part I’m most happy with is the query side. Instead of writing pg_search operators by hand, you use methods on EF.Functions that read like normal LINQ. A basic search looks like this:

var results = await dbContext.Articles
    .Where(a => EF.Functions.Matches(a.Content, "machine learning"))
    .Select(a => new
    {
        a.Title,
        Score = EF.Functions.Score(a.Id),
        Snippet = EF.Functions.Snippet(a.Content, "<mark>", "</mark>", 200)
    })
    .OrderByDescending(a => a.Score)
    .Take(10)
    .ToListAsync();

That gives you BM25-ranked results with relevance scores and highlighted snippets — and it composes with any other LINQ filters you want to add, like date ranges or category checks. Under the hood, the library translates it all into proper pg_search SQL with the |||, pdb.score(), and pdb.snippet() operators.

Beyond basic search, there’s support for conjunction matching (MatchesAll), exact phrase search with slop (MatchesPhrase), term-level matching without stemming (MatchesTerm), fuzzy search with configurable Levenshtein distance (MatchesFuzzy), relevance boosting (MatchesBoosted), full Tantivy query syntax via Parse, regex matching, phrase prefix for autocomplete, and a MoreLikeThis method for finding similar documents. Every variant also has a combined fuzzy+boosted version if you need both at once.

Internally, the library plugs into EF Core’s extension points cleanly. Index creation goes through IConventionSetPlugin — it scans your entities for [Bm25Index] attributes during model building and registers the indexes as part of the standard migration pipeline. Query translation uses IMethodCallTranslatorPlugin to map each C# method to the corresponding pg_search operator. No SQL string concatenation, no custom interceptors.

The library targets .NET 10+ and requires PostgreSQL with pg_search installed. It’s available on NuGet as Equibles.ParadeDB.EntityFrameworkCore. The project is open source under the MIT licence. If you find it useful, a ⭐ star on the repo would mean a lot, and feel free to drop a comment below if you have questions or ideas.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top