From e9d87d7f41ef88847c8b9b12e351a9c3c184db62 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Fri, 16 Oct 2026 15:50:50 +0200 Subject: [PATCH 1/2] Prepare Pgvector.EntityFrameworkCore 10.0.0 for EF Core 10 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 6523471f-aaf2-4f26-87b0-d9889079c81c --- README.md | 2 +- src/Pgvector.EntityFrameworkCore/CHANGELOG.md | 5 +++++ .../Pgvector.EntityFrameworkCore.csproj | 6 +++--- .../Pgvector.EntityFrameworkCore.targets | 0 4 files changed, 9 insertions(+), 4 deletions(-) rename src/Pgvector.EntityFrameworkCore/build/{net8.0 => net10.0}/Pgvector.EntityFrameworkCore.targets (100%) diff --git a/README.md b/README.md index 0f3b7b2..d486e1a 100644 --- a/README.md +++ b/README.md @@ -194,7 +194,7 @@ Run dotnet add package Pgvector.EntityFrameworkCore ``` -The latest version works with Entity Framework Core 9 and 10. For Entity Framework Core 8, use version 0.2.2 and [this readme](https://github.com/pgvector/pgvector-dotnet/blob/efcore-v0.2.2/README.md#entity-framework-core). +The latest version requires .NET 10 and works with Entity Framework Core 10. For Entity Framework Core 9, use version 0.3.0. For Entity Framework Core 8, use version 0.2.2 and [this readme](https://github.com/pgvector/pgvector-dotnet/blob/efcore-v0.2.2/README.md#entity-framework-core). Import the library diff --git a/src/Pgvector.EntityFrameworkCore/CHANGELOG.md b/src/Pgvector.EntityFrameworkCore/CHANGELOG.md index c08d9f7..cf935d9 100644 --- a/src/Pgvector.EntityFrameworkCore/CHANGELOG.md +++ b/src/Pgvector.EntityFrameworkCore/CHANGELOG.md @@ -1,3 +1,8 @@ +## 10.0.0 (unreleased) + +- Updated Npgsql.EntityFrameworkCore.PostgreSQL to 10 +- Dropped support for .NET < 10 + ## 0.3.0 (2025-12-20) - Improved type registration diff --git a/src/Pgvector.EntityFrameworkCore/Pgvector.EntityFrameworkCore.csproj b/src/Pgvector.EntityFrameworkCore/Pgvector.EntityFrameworkCore.csproj index 34a1ec5..022e1bc 100644 --- a/src/Pgvector.EntityFrameworkCore/Pgvector.EntityFrameworkCore.csproj +++ b/src/Pgvector.EntityFrameworkCore/Pgvector.EntityFrameworkCore.csproj @@ -2,7 +2,7 @@ Pgvector.EntityFrameworkCore - 0.3.0 + 10.0.0 ankane pgvector support for Entity Framework Core MIT @@ -10,7 +10,7 @@ README.md icon.png - net8.0 + net10.0 enable enable latest @@ -22,7 +22,7 @@ - + diff --git a/src/Pgvector.EntityFrameworkCore/build/net8.0/Pgvector.EntityFrameworkCore.targets b/src/Pgvector.EntityFrameworkCore/build/net10.0/Pgvector.EntityFrameworkCore.targets similarity index 100% rename from src/Pgvector.EntityFrameworkCore/build/net8.0/Pgvector.EntityFrameworkCore.targets rename to src/Pgvector.EntityFrameworkCore/build/net10.0/Pgvector.EntityFrameworkCore.targets From fb05a0e0b640b989c334aef2309bad7199f34c3a Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Fri, 16 Oct 2026 15:57:53 +0200 Subject: [PATCH 2/2] Prepare EF Core 11 release and vector auto-load convention Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: ae01fae1-3e57-4d55-ad2c-f746a2d1084d --- .github/workflows/build.yml | 3 + README.md | 21 +++-- global.json | 4 +- src/Pgvector.EntityFrameworkCore/CHANGELOG.md | 6 ++ .../Pgvector.EntityFrameworkCore.csproj | 6 +- .../VectorConventionSetPlugin.cs | 31 ++++++++ .../VectorDbContextOptionsExtension.cs | 2 + .../Pgvector.EntityFrameworkCore.targets | 0 .../EntityFrameworkCoreTests.cs | 78 ++++++++++++++++++- .../Pgvector.CSharp.Tests.csproj | 2 +- 10 files changed, 141 insertions(+), 12 deletions(-) create mode 100644 src/Pgvector.EntityFrameworkCore/VectorConventionSetPlugin.cs rename src/Pgvector.EntityFrameworkCore/build/{net10.0 => net11.0}/Pgvector.EntityFrameworkCore.targets (100%) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b9b6a88..9e9f0b5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -6,6 +6,9 @@ jobs: steps: - uses: actions/checkout@v7 - uses: actions/setup-dotnet@v5 + with: + dotnet-version: 11.0.x + dotnet-quality: preview - uses: ankane/setup-postgres@v1 with: database: pgvector_dotnet_test diff --git a/README.md b/README.md index d486e1a..8821ed7 100644 --- a/README.md +++ b/README.md @@ -194,7 +194,7 @@ Run dotnet add package Pgvector.EntityFrameworkCore ``` -The latest version requires .NET 10 and works with Entity Framework Core 10. For Entity Framework Core 9, use version 0.3.0. For Entity Framework Core 8, use version 0.2.2 and [this readme](https://github.com/pgvector/pgvector-dotnet/blob/efcore-v0.2.2/README.md#entity-framework-core). +The latest version requires .NET 11 and works with Entity Framework Core 11 (currently Npgsql.EntityFrameworkCore.PostgreSQL 11.0.0-rc.1.1). For Entity Framework Core 10, use version 10.0.0. For Entity Framework Core 9, use version 0.3.0. For Entity Framework Core 8, use version 0.2.2 and [this readme](https://github.com/pgvector/pgvector-dotnet/blob/efcore-v0.2.2/README.md#entity-framework-core). Import the library @@ -247,6 +247,20 @@ public class Item Also supports `HalfVector` and `SparseVector` +Vector properties are not automatically loaded when querying entities. They remain available for filtering, ordering, and explicit projections: + +```csharp +var embeddings = await ctx.Items.Select(x => x.Embedding).ToListAsync(); +``` + +To automatically load a vector property, configure it explicitly in `OnModelCreating`: + +```csharp +modelBuilder.Entity().Property(x => x.Embedding).Metadata.IsAutoLoaded = true; +``` + +This also applies to `HalfVector` and `SparseVector`, but not `BitArray` properties. + Insert a vector ```csharp @@ -265,10 +279,7 @@ var items = await ctx.Items foreach (Item item in items) { - if (item.Embedding != null) - { - Console.WriteLine(item.Embedding); - } + Console.WriteLine(item.Id); } ``` diff --git a/global.json b/global.json index 0b2a45c..b40b33e 100644 --- a/global.json +++ b/global.json @@ -1,7 +1,7 @@ { "sdk": { - "version": "10.0.100", + "version": "11.0.100-rc.2.26504.105", "rollForward": "latestMajor", - "allowPrerelease": "false" + "allowPrerelease": true } } diff --git a/src/Pgvector.EntityFrameworkCore/CHANGELOG.md b/src/Pgvector.EntityFrameworkCore/CHANGELOG.md index cf935d9..f07385b 100644 --- a/src/Pgvector.EntityFrameworkCore/CHANGELOG.md +++ b/src/Pgvector.EntityFrameworkCore/CHANGELOG.md @@ -1,3 +1,9 @@ +## 11.0.0 (unreleased) + +- Updated Npgsql.EntityFrameworkCore.PostgreSQL to 11.0.0-rc.1.1 +- Dropped support for .NET < 11 +- Disabled automatic loading of `Vector`, `HalfVector`, and `SparseVector` properties by default + ## 10.0.0 (unreleased) - Updated Npgsql.EntityFrameworkCore.PostgreSQL to 10 diff --git a/src/Pgvector.EntityFrameworkCore/Pgvector.EntityFrameworkCore.csproj b/src/Pgvector.EntityFrameworkCore/Pgvector.EntityFrameworkCore.csproj index 022e1bc..2bbca86 100644 --- a/src/Pgvector.EntityFrameworkCore/Pgvector.EntityFrameworkCore.csproj +++ b/src/Pgvector.EntityFrameworkCore/Pgvector.EntityFrameworkCore.csproj @@ -2,7 +2,7 @@ Pgvector.EntityFrameworkCore - 10.0.0 + 11.0.0 ankane pgvector support for Entity Framework Core MIT @@ -10,7 +10,7 @@ README.md icon.png - net10.0 + net11.0 enable enable latest @@ -22,7 +22,7 @@ - + diff --git a/src/Pgvector.EntityFrameworkCore/VectorConventionSetPlugin.cs b/src/Pgvector.EntityFrameworkCore/VectorConventionSetPlugin.cs new file mode 100644 index 0000000..4da8f78 --- /dev/null +++ b/src/Pgvector.EntityFrameworkCore/VectorConventionSetPlugin.cs @@ -0,0 +1,31 @@ +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Metadata.Conventions; +using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure; + +namespace Pgvector.EntityFrameworkCore; + +public class VectorConventionSetPlugin : IConventionSetPlugin +{ + private readonly ProviderConventionSetBuilderDependencies _dependencies; + + public VectorConventionSetPlugin(ProviderConventionSetBuilderDependencies dependencies) + { + _dependencies = dependencies; + } + + public virtual ConventionSet ModifyConventions(ConventionSet conventionSet) + { + conventionSet.Add(new VectorAutoLoadConvention(_dependencies)); + return conventionSet; + } + + private class VectorAutoLoadConvention : AutoLoadConvention + { + public VectorAutoLoadConvention(ProviderConventionSetBuilderDependencies dependencies) : base(dependencies) { } + + protected override bool ShouldBeAutoLoaded(IConventionProperty property) + => property.ClrType != typeof(Vector) + && property.ClrType != typeof(HalfVector) + && property.ClrType != typeof(SparseVector); + } +} diff --git a/src/Pgvector.EntityFrameworkCore/VectorDbContextOptionsExtension.cs b/src/Pgvector.EntityFrameworkCore/VectorDbContextOptionsExtension.cs index 5da3dca..5a72904 100644 --- a/src/Pgvector.EntityFrameworkCore/VectorDbContextOptionsExtension.cs +++ b/src/Pgvector.EntityFrameworkCore/VectorDbContextOptionsExtension.cs @@ -1,4 +1,5 @@ using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure; using Microsoft.EntityFrameworkCore.Query; using Microsoft.EntityFrameworkCore.Storage; using Microsoft.Extensions.DependencyInjection; @@ -16,6 +17,7 @@ public void ApplyServices(IServiceCollection services) { new EntityFrameworkNpgsqlServicesBuilder(services) .TryAdd() + .TryAdd() .TryAdd(); services.AddSingleton(); diff --git a/src/Pgvector.EntityFrameworkCore/build/net10.0/Pgvector.EntityFrameworkCore.targets b/src/Pgvector.EntityFrameworkCore/build/net11.0/Pgvector.EntityFrameworkCore.targets similarity index 100% rename from src/Pgvector.EntityFrameworkCore/build/net10.0/Pgvector.EntityFrameworkCore.targets rename to src/Pgvector.EntityFrameworkCore/build/net11.0/Pgvector.EntityFrameworkCore.targets diff --git a/tests/Pgvector.CSharp.Tests/EntityFrameworkCoreTests.cs b/tests/Pgvector.CSharp.Tests/EntityFrameworkCoreTests.cs index a896f6b..96b16f9 100644 --- a/tests/Pgvector.CSharp.Tests/EntityFrameworkCoreTests.cs +++ b/tests/Pgvector.CSharp.Tests/EntityFrameworkCoreTests.cs @@ -50,10 +50,21 @@ public class Item public class EntityFrameworkCoreTests { + private class AutoLoadedItemContext : ItemContext + { + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + modelBuilder.Entity().Property(x => x.Embedding).Metadata.IsAutoLoaded = true; + modelBuilder.Entity().Property(x => x.HalfEmbedding).Metadata.IsAutoLoaded = true; + modelBuilder.Entity().Property(x => x.SparseEmbedding).Metadata.IsAutoLoaded = true; + } + } + [Fact] public async Task Main() { - await using var ctx = new ItemContext(); + await using var ctx = new AutoLoadedItemContext(); ctx.Database.ExecuteSql($"DROP TABLE IF EXISTS efcore_items"); var databaseCreator = ctx.GetService(); @@ -63,6 +74,7 @@ public async Task Main() ctx.Items.Add(new Item { Embedding = new Vector(new float[] { 2, 2, 2 }), HalfEmbedding = new HalfVector(new Half[] { (Half)2, (Half)2, (Half)2 }), BinaryEmbedding = new BitArray(new bool[] { true, false, true }), SparseEmbedding = new SparseVector(new float[] { 2, 2, 2 }) }); ctx.Items.Add(new Item { Embedding = new Vector(new float[] { 1, 1, 2 }), HalfEmbedding = new HalfVector(new Half[] { (Half)1, (Half)1, (Half)2 }), BinaryEmbedding = new BitArray(new bool[] { true, true, true }), SparseEmbedding = new SparseVector(new float[] { 1, 1, 2 }) }); ctx.SaveChanges(); + ctx.ChangeTracker.Clear(); var embedding = new Vector(new float[] { 1, 1, 1 }); var items = await ctx.Items.FromSql($"SELECT * FROM efcore_items ORDER BY embedding <-> {embedding} LIMIT 5").ToListAsync(); @@ -142,6 +154,70 @@ public async Task Main() Assert.Equal(new double[] { 0, 1, Math.Sqrt(3) }, neighbors.Select(v => v.Distance).ToArray()); } + [Fact] + public async Task VectorPropertiesAreNotAutoLoaded() + { + await using var ctx = new ItemContext(); + var entityType = ctx.Model.FindEntityType(typeof(Item))!; + Assert.False(entityType.FindProperty(nameof(Item.Embedding))!.IsAutoLoaded); + Assert.False(entityType.FindProperty(nameof(Item.HalfEmbedding))!.IsAutoLoaded); + Assert.False(entityType.FindProperty(nameof(Item.SparseEmbedding))!.IsAutoLoaded); + Assert.True(entityType.FindProperty(nameof(Item.Id))!.IsAutoLoaded); + Assert.True(entityType.FindProperty(nameof(Item.BinaryEmbedding))!.IsAutoLoaded); + + await ctx.Database.ExecuteSqlAsync($"DROP TABLE IF EXISTS efcore_items"); + ctx.GetService().CreateTables(); + + var embedding = new Vector(new float[] { 1, 2, 3 }); + var halfEmbedding = new HalfVector(new Half[] { (Half)1, (Half)2, (Half)3 }); + var sparseEmbedding = new SparseVector(new float[] { 1, 2, 3 }); + var binaryEmbedding = new BitArray(new bool[] { true, false, true }); + ctx.Items.Add(new Item + { + Embedding = embedding, + HalfEmbedding = halfEmbedding, + SparseEmbedding = sparseEmbedding, + BinaryEmbedding = binaryEmbedding + }); + await ctx.SaveChangesAsync(); + ctx.ChangeTracker.Clear(); + + var sql = ctx.Items.ToQueryString(); + Assert.DoesNotContain(".embedding", sql); + Assert.DoesNotContain(".half_embedding", sql); + Assert.DoesNotContain(".sparse_embedding", sql); + Assert.Contains(".binary_embedding", sql); + + var untracked = await ctx.Items.AsNoTracking().SingleAsync(); + Assert.Null(untracked.Embedding); + Assert.Null(untracked.HalfEmbedding); + Assert.Null(untracked.SparseEmbedding); + Assert.Equal(binaryEmbedding, untracked.BinaryEmbedding); + + var neighbor = await ctx.Items + .Where(x => x.Embedding!.L2Distance(embedding) < 1) + .OrderBy(x => x.Embedding!.L2Distance(embedding)) + .Select(x => new { Entity = x, Distance = x.Embedding!.L2Distance(embedding) }) + .SingleAsync(); + Assert.Equal(untracked.Id, neighbor.Entity.Id); + Assert.Equal(0, neighbor.Distance); + Assert.Null(neighbor.Entity.Embedding); + Assert.Null(neighbor.Entity.HalfEmbedding); + Assert.Null(neighbor.Entity.SparseEmbedding); + Assert.Equal(binaryEmbedding, neighbor.Entity.BinaryEmbedding); + + neighbor.Entity.BinaryEmbedding = new BitArray(new bool[] { false, true, false }); + await ctx.SaveChangesAsync(); + + var projected = await ctx.Items + .Select(x => new { x.Embedding, x.HalfEmbedding, x.SparseEmbedding, x.BinaryEmbedding }) + .SingleAsync(); + Assert.Equal(embedding, projected.Embedding); + Assert.Equal(halfEmbedding, projected.HalfEmbedding); + Assert.Equal(sparseEmbedding, projected.SparseEmbedding); + Assert.Equal(neighbor.Entity.BinaryEmbedding, projected.BinaryEmbedding); + } + [Theory] [InlineData(typeof(Vector), null, "vector")] [InlineData(typeof(Vector), 3, "vector(3)")] diff --git a/tests/Pgvector.CSharp.Tests/Pgvector.CSharp.Tests.csproj b/tests/Pgvector.CSharp.Tests/Pgvector.CSharp.Tests.csproj index f25be28..522ee1a 100644 --- a/tests/Pgvector.CSharp.Tests/Pgvector.CSharp.Tests.csproj +++ b/tests/Pgvector.CSharp.Tests/Pgvector.CSharp.Tests.csproj @@ -1,7 +1,7 @@ - net10.0 + net11.0 enable enable