備註
向量支援於 EF Core 10.0 引入,僅支援 SQL Server 2025 及以上版本。
SQL Server 的向量資料類型允許儲存嵌入向量,這些是對意義的表示,可以有效地進行相似性搜尋,支持 AI 工作負載,如語義搜尋和檢索輔助生成(RAG)。
設定向量屬性
要使用 vector 資料型態,只需在實體類型中加入 .NET 屬性 SqlVector<float>,並指定以下尺寸:
public class Blog
{
// ...
[Column(TypeName = "vector(1536)")]
public SqlVector<float> Embedding { get; set; }
}
新增屬性並在資料庫中建立對應的欄後,您就可以開始插入內嵌。 內嵌產生是在資料庫外部完成,通常是透過服務,執行此動作的詳細資料超出本檔的範圍。 然而,the .NET Microsoft.Extensions.AI 函式庫包含 IEmbeddingGenerator,這是一個針對嵌入生成器的抽象層,並且對主要提供者進行了實作。
當你選擇並設定好嵌入產生器後,使用它產生嵌入並以以下方式插入:
IEmbeddingGenerator<string, Embedding<float>> embeddingGenerator = /* Set up your preferred embedding generator */;
var embedding = await embeddingGenerator.GenerateVectorAsync("Some text to be vectorized");
context.Blogs.Add(new Blog
{
Name = "Some blog",
Embedding = new SqlVector<float>(embedding)
});
await context.SaveChangesAsync();
一旦你將嵌入資料儲存到資料庫,就可以對它們進行向量相似性搜尋。
備註
從 EF Core 11 開始,查詢實體時預設不會載入向量屬性,因為向量通常很大,且很少需要回讀。 在 EF Core 11 之前,向量屬性總是像其他屬性一樣載入。
精確搜尋使用VECTOR_DISTANCE()
該 EF.Functions.VectorDistance() 函數計算兩個向量之間的 精確 距離。 利用它對特定使用者查詢進行相似度搜尋:
var sqlVector = new SqlVector<float>(await embeddingGenerator.GenerateVectorAsync("Some user query to be vectorized"));
var topSimilarBlogs = await context.Blogs
.OrderBy(b => EF.Functions.VectorDistance("cosine", b.Embedding, sqlVector))
.Take(3)
.ToListAsync();
此函式計算查詢向量與表格中每一列之間的距離,然後回傳最接近的匹配點。 雖然這能提供完全準確的結果,但對於大型資料集來說,速度會較慢,因為 SQL Server 必須掃描所有資料列並計算每列的距離。
備註
EF 10 中的內建支援取代了先前的 EFCore.SqlServer.VectorSearch 延伸模組,這允許在引進資料類型之前執行 vector 向量搜尋。 在升級至 EF 10 的過程中,請從專案中移除延伸模組。
用 VECTOR_SEARCH() 搜尋
警告
VECTOR_SEARCH() 與向量指標目前仍為實驗特徵,SQL Server中可能會變更。 EF Core 中這些功能的 API 也可能有所變動。
SQL Server 的 VECTOR_SEARCH() 表值函數會根據向量相似度檢索資料列。 與計算兩個特定向量間距離的 VECTOR_DISTANCE() — 不同,後者 VECTOR_SEARCH() 會搜尋整個表格中與特定查詢向量最相似的向量。
在你的 VectorSearch() 上使用 DbSet 擴充方法,並串接 OrderBy()、Take() 和 WithApproximate(),以執行使用 向量索引的近似最近鄰(ANN)搜尋:
var results = await context.Blogs
.VectorSearch(b => b.Embedding, embedding, "cosine")
.OrderBy(r => r.Distance)
.Take(5)
.WithApproximate()
.ToListAsync();
foreach (var result in results)
{
Console.WriteLine($"Blog {result.Value.Id} with distance {result.Distance}");
}
它會轉譯為下列 SQL:
SELECT TOP(@__p_1) WITH APPROXIMATE [b].[Id], [b].[Name], [v].[Distance]
FROM VECTOR_SEARCH(
TABLE = [Blogs] AS [b],
COLUMN = [Embedding],
SIMILAR_TO = @__embedding_0,
METRIC = 'cosine'
) AS [v]
ORDER BY [v].[Distance]
VectorSearch() 返回 VectorSearchResult<TEntity>,允許你同時存取實體及計算出的距離:
var searchResults = await context.Blogs
.VectorSearch(b => b.Embedding, embedding, "cosine")
.Where(r => r.Distance < 0.05)
.OrderBy(r => r.Distance)
.Select(r => new { Blog = r.Value, Distance = r.Distance })
.Take(3)
.WithApproximate()
.ToListAsync();
這讓你能篩選相似度分數,並呈現給使用者等。
WithApproximate()
WithApproximate() 指示SQL Server使用向量索引進行近似最近鄰(ANN)搜尋,這在大型資料集中提供了顯著更好的效能。 這會使 WITH APPROXIMATE 加入至 SQL 的 TOP 子句中。
WithApproximate() 必須在 Take()後呼叫,指定要回傳的結果數量。
若無 WithApproximate(),查詢將進行精確的 k 最近鄰(kNN)搜尋,掃描所有列,且不使用向量索引:
// Exact kNN search (no vector index used)
var blogs = await context.Blogs
.VectorSearch(b => b.Embedding, embedding, "cosine")
.OrderBy(r => r.Distance)
.Take(5)
.ToListAsync();
向量索引
若要使用 WithApproximate() 進行近似搜尋,您必須為向量資料行建立向量索引。 在你的模型配置中使用此 HasVectorIndex() 方法:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.HasVectorIndex(b => b.Embedding, "cosine");
}
這將產生以下 SQL 遷移:
CREATE VECTOR INDEX [IX_Blogs_Embedding]
ON [Blogs] ([Embedding])
WITH (METRIC = COSINE)
以下距離度量支援向量索引:
| Metric | 說明 |
|---|---|
cosine |
餘弦相似度(角距離) |
euclidean |
歐幾里得距離(L2 範數) |
dot |
點積(負內積) |
選擇最適合你嵌入模型和使用情境的指標。 餘弦相似度常用於文字嵌入,而歐氏距離則常用於影像嵌入。
混合式搜尋
混合式搜尋 結合向量相似性搜尋與傳統 全文搜尋 ,以提供更相關的結果。 向量搜尋擅長尋找語意相似的內容,而全文搜尋則更擅長精確的關鍵字匹配。 結合兩種方法並使用互惠排名融合(Reciprocal Rank Fusion,RRF)合併結果,您可以打造更智慧的搜尋體驗。
以下範例展示了如何利用 EF Core 實現混合式搜尋,將 與 FreeTextTable()VectorSearch() 合併於單一查詢中:
var k = 20;
string textualQuery = ...;
SqlVector<float> queryEmbedding = ...;
var results = await context.Articles
// Perform full-text search
.FreeTextTable<Article, int>(textualQuery, topN: k)
.Join(
context.Articles,
fts => fts.Key,
a => a.Id,
(fts, a) => new { Article = a, fts.Rank })
// Perform vector (semantic) search, joining the results of both searches together
.FullJoin(
context.Articles.VectorSearch(b => b.Embedding, queryEmbedding, "cosine")
.OrderBy(r => r.Distance)
.Take(k)
.WithApproximate(),
fts => fts.Article.Id,
vs => vs.Value.Id,
(fts, vs) => new
{
Article = fts != null ? fts.Article : vs.Value,
FullTextRank = fts == null ? null : (int?)fts.Rank,
VectorDistance = vs == null ? null : (double?)vs.Distance
})
// Apply Reciprocal Rank Fusion (RRF) to combine the results
.Select(x => new
{
x.Article,
RrfScore = (x.FullTextRank == null ? 0.0 : 1.0 / (k + x.FullTextRank.Value))
+ (x.VectorDistance == null ? 0.0 : 1.0 / (k + x.VectorDistance.Value))
})
.OrderByDescending(x => x.RrfScore)
.Take(10)
.Select(x => x.Article)
.ToListAsync();
此查詢:
- 對
Article進行全文搜尋 - 對
Article執行向量搜尋,並透過 FULL JOIN 將結果與全文搜尋結果合併 - 透過結合全文與語意排名來計算 RRF 分數
- 按 RRF 分數排序,取出所需數量的結果並推算出原始
Article實體。
查詢會產生以下 SQL:
SELECT TOP(@__p_4) COALESCE([a].[Id], [t].[Id]) AS [Id], COALESCE([a].[Content], [t].[Content]) AS [Content], COALESCE([a].[Title], [t].[Title]) AS [Title]
FROM FREETEXTTABLE([Articles], *, @__textualQuery_0, @__k_1) AS [f]
INNER JOIN [Articles] AS [a] ON [f].[KEY] = [a].[Id]
FULL JOIN (
SELECT TOP(@__k_1) WITH APPROXIMATE [a].[Id], [a].[Content], [a].[Title], [v].[Distance]
FROM VECTOR_SEARCH(
TABLE = [Articles] AS [a],
COLUMN = [Embedding],
SIMILAR_TO = @__queryEmbedding_2,
METRIC = 'cosine'
) AS [v]
ORDER BY [v].[Distance]
) AS [t] ON [a].[Id] = [t].[Id]
ORDER BY ISNULL(1.0E0 / CAST(@__k_1 + [f].[RANK] AS float), 0.0E0) + ISNULL(1.0E0 / (CAST(@__k_1 AS float) + [t].[Distance]), 0.0E0) DESC