pgvector + EF Core: Vector Search Without Leaving PostgreSQL
EF Core 11's vector search announcement was genuinely exciting, if you're on SQL Server. But according to Stack Overflow's 2024 Developer Survey, PostgreSQL is the most-used database among professional developers at 49%. Half of us watched that announcement and thought: "Cool. Doesn't apply to me."
It does apply to you. You just don't need to wait for it.
I run Postgres in a 50MB Docker container, pay exactly $0 in licensing, and have zero intention of migrating to SQL Server for one feature. pgvector paired with the Pgvector.EntityFrameworkCore package gives you vector search today. Same HNSW algorithm that powers Pinecone under the hood, same semantic search capabilities, same hybrid query patterns. Let me show you the setup in under 30 minutes.
Why Postgres Developers Shouldn't Switch
The economics alone make the case. SQL Server Enterprise licensing starts at €13,999 per core. PostgreSQL costs nothing. That's not a small margin, it's the difference between "fits in our budget" and "requires VP approval."
Then there's the operational weight. The postgres:17 Docker image is ~50MB compressed. SQL Server's Linux image exceeds 1.5GB. In a Kubernetes environment with horizontal scaling and CI/CD pipelines pulling images on every build, that difference compounds fast. Then you also have the memory advantage, postgres by design runs idle at 40mb. While mssql docker container runs idle at minimum 400mb / 500mb
pgvector itself is battle-tested. Version 0.8.0 shipped in early 2025, supporting PostgreSQL 13-17. It implements both HNSW (Hierarchical Navigable Small World) and IVFFlat indexing. HNSW is the same approximate nearest neighbour algorithm that Pinecone uses internally. You're not getting a lesser implementation by staying on Postgres.
Switching databases for a single feature is the opposite of pragmatic engineering. If you're already running Postgres, bring vector search to your existing stack. Don't rip out your stack to chase a feature.
Wiring Up pgvector + EF Core
Here's the full setup. I'll go step by step, Docker to query.
1. Docker Compose
The pgvector/pgvector image is just Postgres with the extension pre-installed:
services:
db:
image: pgvector/pgvector:pg17
environment:
POSTGRES_PASSWORD: postgres
ports:
- "5432:5432"
That's it. No extra configuration, no sidecar processes. The extension ships inside the standard Postgres image at ~80MB total.
2. NuGet Packages
Two packages. Both stable and actively maintained:
dotnet add package Npgsql.EntityFrameworkCore.PostgreSQL
dotnet add package Pgvector.EntityFrameworkCore
Pgvector.EntityFrameworkCore is maintained by Andrew Kane, who also maintains the pgvector libraries for Ruby, Python, and Node. It's the de facto .NET integration.
3. Enable the Extension
Either run this manually or include it in your first migration:
CREATE EXTENSION IF NOT EXISTS vector;
4. Entity Model
The Vector type from the Pgvector namespace maps directly to Postgres's vector column type:
using Pgvector;
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string Category { get; set; }
public bool InStock { get; set; }
public Vector Embedding { get; set; }
}
Clean. No special attributes, no wrapper types. Just a property on your entity.
5. DbContext Configuration
Wire up the vector extension in your DbContext:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseNpgsql(
"Host=localhost;Database=myapp;Username=postgres;Password=postgres",
o => o.UseVector()
);
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.HasPostgresExtension("vector");
modelBuilder.Entity<Product>(entity =>
{
entity.Property(e => e.Embedding)
.HasColumnType("vector(1536)");
});
}
The 1536 dimension matches OpenAI's text-embedding-3-small. Adjust to 768 for smaller models or 3072 for text-embedding-3-large.
6. HNSW Index via Migration
Here's where the DX gap shows. You need raw SQL in your migration. But it's three lines, and you write it once:
public partial class AddEmbeddingIndex : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql(
@"CREATE INDEX ix_products_embedding
ON ""Products""
USING hnsw (""Embedding"" vector_cosine_ops)
WITH (m = 16, ef_construction = 64)");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql(@"DROP INDEX ix_products_embedding");
}
}
The m and ef_construction parameters control index quality vs. build speed. The defaults above are solid for datasets up to a few million rows. For larger datasets, bump ef_construction to 128 or 200.
The operator class vector_cosine_ops tells pgvector to optimise for cosine distance. Other options: vector_l2_ops (Euclidean) and vector_ip_ops (inner product/dot).
7. Querying: Semantic Search
Vector search uses the <=> operator for cosine distance. You'll use FromSqlRaw since there's no LINQ integration yet:
var queryVector = new Vector(await embeddingGenerator.GenerateVectorAsync("wireless headphones"));
var results = await context.Products
.FromSqlRaw(
@"SELECT * FROM ""Products""
ORDER BY ""Embedding"" <=> {0}
LIMIT {1}",
queryVector, 10)
.ToListAsync();
That <=> operator uses your HNSW index automatically. Postgres's query planner is smart enough to pick it up when you combine ORDER BY <=> with LIMIT.
8. Hybrid Search: Filtering + Similarity
This is the real power. Combine traditional WHERE clauses with vector similarity in a single query:
var results = await context.Products
.FromSqlRaw(
@"SELECT * FROM ""Products""
WHERE ""InStock"" = true AND ""Category"" = 'Electronics'
ORDER BY ""Embedding"" <=> {0}
LIMIT {1}",
queryVector, 5)
.ToListAsync();
Filter first, rank by similarity second. One round trip. One database. The HNSW index still kicks in for the ordering step.
The DX Gap, and When It Closes
Let's be honest about what you're giving up compared to EF Core 11's native SQL Server support:
| Feature | SQL Server (EF Core 11) | PostgreSQL (pgvector) |
|---|---|---|
| Vector type | SqlVector<float> (built-in) |
Vector (community package) |
| Index config | HasVectorIndex("cosine") |
Raw SQL in migrations |
| Index algorithm | DiskANN | HNSW / IVFFlat |
| Query API | VectorSearch() LINQ method |
FromSqlRaw with <=> operator |
| Hybrid search | .Where().VectorSearch() |
SQL WHERE + ORDER BY |
| Status | Preview (June 2026) | Production-ready now |
The gap is real but narrow. You're writing 3 lines of raw SQL instead of fluent LINQ. Your index lives in a migrationBuilder.Sql() call instead of a HasVectorIndex() builder. That's the entire inconvenience.
What's missing from the Postgres side: fluent index configuration, LINQ similarity operators, and automatic vector exclusion from SELECT. These are developer experience niceties, not capability gaps. The underlying search performance is equivalent.
When will it close? The pressure is mounting. SQL Server getting first-class EF Core vector support creates direct demand on the Npgsql side. PostgreSQL 17 ships performance improvements to pgvector that treat it as near-core functionality. My prediction: expect first-class Npgsql fluent vector support by late 2026 or early 2027.
The pragmatic take: ship with raw SQL today, refactor to fluent API when it lands. Your data doesn't change. Your indexes don't change. The migration is a code cleanup, not a rewrite.
When to Choose What
Already on PostgreSQL - Use pgvector. Don't switch databases for a DX improvement that's 3 lines of SQL. You'll get fluent support eventually, and your production data stays put.
Already on SQL Server - Use EF Core 11's native vector search. It's genuinely excellent and the LINQ integration is unmatched right now.
Greenfield project - PostgreSQL if you value free licensing, lightweight containers, and Kubernetes-native deployments. SQL Server if you're in an enterprise Microsoft shop with existing SA licensing.
"Should I add Pinecone or Qdrant?" - Not unless you're operating at billion-vector scale with multi-region requirements. For datasets in the millions (which covers 95% of applications I've built or consulted on), pgvector handles it fine.
Ship It This Week
You don't need first-class Npgsql vector support to deliver AI-powered search features. You need Pgvector.EntityFrameworkCore, a 50MB Docker image, and about 20 minutes.
The setup is:
- 2 NuGet packages
- 1 Docker image swap (if you're not already on
pgvector/pgvector) - 3 lines of SQL for the index
- Zero new infrastructure, zero licensing costs, zero additional services to monitor
When the fluent API lands, it'll be a refactor, not a rewrite. Your HNSW indexes, your vector columns, your data: all unchanged. You'll just swap FromSqlRaw for a .VectorSearch() call and delete the raw SQL.
That's the 75% easier path. Not waiting for perfect tooling. Shipping with what works today, knowing the upgrade path is trivial.
Start with one entity. Add a Vector property, create the HNSW index, run a similarity query. You'll have semantic search working before your afternoon coffee gets cold.