HomeNikola Knezevic

In this article

Banner

Hybrid Search in EF Core 10

22 Jan 2026
6 min

Special Thanks to Our Sponsors:

Sponsor Logo

EF Core is too slow? Discover how you can easily insert 14x faster (reducing saving time by 94%).

Boost your performance with our method within EF Core: Bulk Insert, update, delete and merge.

Thousands of satisfied customers have trusted our library since 2014.

👉 Learn more

Sponsor Newsletter

When building search functionality, you often face a trade-off between precision and semantic understanding.

Keyword search gives you precise matches, but it struggles with synonyms and context. Vector search understands meaning, but it can miss exact matches that matter.

Hybrid search combines both approaches, giving you the best of both worlds.

Hybrid search computes a final score by combining two signals:

  • Keyword relevance (e.g., BM25 / full‑text ranking)
  • Vector similarity (e.g., cosine similarity to an embedding)

Then it linearly blends them using weights: final = α · keyword + (1 − α) · vector.

Entity Configuration

EF Core 10 supports Azure Cosmos DB's native vector and full-text search capabilities.

First we need to install the required NuGet package:

powershell
Install-Package Microsoft.EntityFrameworkCore.Cosmos

Now we need to configure our entity to enable both search capabilities:

csharp
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Document>(entity =>
    {
        entity.ToContainer("DocumentsVector");

        entity.Property(x => x.Id)
            .ToJsonProperty("Id");

        entity.HasPartitionKey("Id");

        entity.Property(x => x.Content).EnableFullTextSearch();
        entity.HasIndex(x => x.Content).IsFullTextIndex();
        
        entity.Property(x => x.Embedding)
            .IsVectorProperty(DistanceFunction.Cosine, dimensions: 1024);
    });
}

This configuration enables full-text search on the Content property and vector search on the Embedding property.

NOTE: Ensure the vector dimensions and distance function match your Cosmos DB container's vector embedding policy configuration.

Azure Cosmos DB Setup

Configure the connection and register the DbContext:

csharp
builder.Services.AddDbContextPool<ApplicationDbContext>(options =>
{
    options.UseCosmos(
        builder.Configuration.GetConnectionString("CosmosDb"),
        builder.Configuration["CosmosDb:DatabaseName"]);
});

In your appsettings.json, provide the Cosmos DB connection details:

json
{
  "ConnectionStrings": {
    "CosmosDb": "AccountEndpoint=https://<your-account>.documents.azure.com:443/;AccountKey=<your-key>"
  },
  "CosmosDb": {
    "DatabaseName": "<your-database-name>"
  },
  "Ollama": {
    "Url": "http://localhost:11434/"
  }
}

Generating Embeddings

For generating the embeddings, I used local AI models powered by the Microsoft.Extensions.AI.Ollama package.

To use Ollama, we first need to download and install it.

Now we need to install the model which we plan to use for generating our embeddings. You can do this with the following command:

shell
ollama pull mxbai-embed-large

I ended up going with the mxbai-embed-large model.

Now, we just need to register our embedding generator to make it available throughout the application.

csharp
builder.Services.AddEmbeddingGenerator(
    new OllamaEmbeddingGenerator(builder.Configuration["Ollama:Url"], "mxbai-embed-large"));
json
"Ollama": {
    "Url": "http://localhost:11434/"
}

With this, we are ready to generate our embeddings. The model creates solid 1024-dimensional vectors perfect for semantic search.

csharp
app.MapPost("/api/search", async (
    string query,
    ApplicationDbContext db,
    IEmbeddingGenerator<string, Embedding<float>> embeddingGenerator) =>
{
    var embeddingResult = await embeddingGenerator.GenerateAsync([query]);
    var queryVector = embeddingResult.Single().Vector.ToArray();
});

You can generate them by injecting IEmbeddingGenerator<string, Embedding<float>> and using its GenerateAsync method.

Ollama handles all the work under the hood, so we don’t need any external services or additional infrastructure.

Now we can perform hybrid search entirely in LINQ. EF Core translates the full-text and vector operations natively to Cosmos DB's hybrid search capabilities.

csharp
app.MapPost("/api/hybrid-search", async (
    string query,
    ApplicationDbContext db,
    IEmbeddingGenerator<string, Embedding<float>> embeddingGenerator) =>
{
    var embeddingResult = await embeddingGenerator.GenerateAsync([query]);
    var queryVector = embeddingResult.Single().Vector.ToArray();
    
    var keywords = query.Split(' ', StringSplitOptions.RemoveEmptyEntries);
    
    var results = await db.Documents
        .OrderBy(x => EF.Functions.Rrf(
            EF.Functions.FullTextScore(x.Content, keywords),
            EF.Functions.VectorDistance(x.Embedding, queryVector)))
        .Take(20)
        .AsNoTracking()
        .ToListAsync();

    return Results.Ok(results);
});

The RRF function combines the BM25 full-text score with the vector similarity distance, producing a unified ranking that leverages both keyword precision and semantic understanding.

NOTE: I've written a dedicated blog post on Vector Similarity Search with EF 10 Core and SQL Server 2025 that you can check out here if you're interested: Vector Similarity Search in EF Core 10.

For more control over the balance between keyword and vector search, you can assign custom weights to each search method using RRF. This allows you to emphasize semantic understanding or keyword precision based on your use case.

csharp
// Hybrid search with custom weights
app.MapPost("/api/hybrid-search-weighted", async (
    string query,
    double[]? weights,
    ApplicationDbContext db,
    IEmbeddingGenerator<string, Embedding<float>> embeddingGenerator) =>
{
    var embeddingResult = await embeddingGenerator.GenerateAsync([query]);
    var queryVector = embeddingResult.Single().Vector.ToArray();
    
    var keywords = query.Split(' ', StringSplitOptions.RemoveEmptyEntries);
    
    var searchWeights = weights ?? [1.0, 1.0];
    
    var results = await db.Documents
        .OrderBy(x => EF.Functions.Rrf(
            new[] 
            {
                EF.Functions.FullTextScore(x.Content, query),
                EF.Functions.VectorDistance(x.Embedding, queryVector)
            },
            weights: searchWeights))
        .Take(20)
        .AsNoTracking()
        .ToListAsync();
    return Results.Ok(results);
});

By default, both search methods have equal weight (1.0, 1.0).

You can adjust these weights to give more importance to either full-text search or vector search, depending on which signal is more valuable for your specific scenario.

Conclusion

Hybrid search gives you the precision of keywords and the understanding of vectors.

EF Core 10 and Cosmos DB let you enable both full-text and vector search, you can generate embeddings with Ollama, and rank results using RRF

Optionally you can weight keywords or vectors so that you get the best of both worlds with minimal setup.

If you want to check out examples I created, you can find the source code here:

Source Code

I hope you enjoyed it, subscribe and get a notification when a new blog is up!

Subscribe

Stay tuned for valuable insights every Thursday morning.