Skip to content

Commit 6e92827

Browse files
committed
Implement support for SQL Server vector indexes
Closes #37281
1 parent 37f5733 commit 6e92827

File tree

12 files changed

+651
-4
lines changed

12 files changed

+651
-4
lines changed

src/EFCore.SqlServer/Extensions/SqlServerEntityTypeBuilderExtensions.cs

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using Microsoft.EntityFrameworkCore.Metadata.Builders;
45
using Microsoft.EntityFrameworkCore.SqlServer.Metadata.Internal;
56

67
// ReSharper disable once CheckNamespace
@@ -291,6 +292,107 @@ public static bool CanSetIsTemporal(
291292
bool fromDataAnnotation = false)
292293
=> entityTypeBuilder.CanSetAnnotation(SqlServerAnnotationNames.IsTemporal, temporal, fromDataAnnotation);
293294

295+
/// <summary>
296+
/// Configures a vector index on the specified property for SQL Server.
297+
/// </summary>
298+
/// <remarks>
299+
/// See <see href="https://aka.ms/efcore-docs-modeling">Modeling entity types and relationships</see>, and
300+
/// <see href="https://aka.ms/efcore-docs-sqlserver">Accessing SQL Server and Azure SQL databases with EF Core</see>
301+
/// for more information and examples.
302+
/// </remarks>
303+
/// <typeparam name="TEntity">The entity type being configured.</typeparam>
304+
/// <param name="entityTypeBuilder">The builder for the entity type being configured.</param>
305+
/// <param name="indexExpression">
306+
/// A lambda expression representing the property to create the vector index on
307+
/// (<c>blog => blog.Vector</c>).
308+
/// </param>
309+
/// <param name="metric">
310+
/// <para>
311+
/// A string with the name of the distance metric to use to calculate the distance between the two given vectors.
312+
/// The following distance metrics are supported:
313+
/// </para>
314+
/// <list type="bullet">
315+
/// <item>
316+
/// <term>cosine</term>
317+
/// <description>Cosine distance</description>
318+
/// </item>
319+
/// <item>
320+
/// <term>euclidean</term>
321+
/// <description>Euclidean distance</description>
322+
/// </item>
323+
/// <item>
324+
/// <term>dot</term>
325+
/// <description>(Negative) Dot product</description>
326+
/// </item>
327+
/// </list>
328+
/// </param>
329+
/// <param name="name">The name to assign to the index.</param>
330+
/// <returns>A builder to further configure the vector index.</returns>
331+
public static VectorIndexBuilder<TEntity> HasVectorIndex<TEntity>(
332+
this EntityTypeBuilder<TEntity> entityTypeBuilder,
333+
Expression<Func<TEntity, object?>> indexExpression,
334+
string metric,
335+
string? name = null)
336+
where TEntity : class
337+
{
338+
Check.NotNull(indexExpression);
339+
340+
var indexBuilder = name is null
341+
? entityTypeBuilder.HasIndex(indexExpression)
342+
: entityTypeBuilder.HasIndex(indexExpression, name);
343+
indexBuilder.Metadata.SetVectorMetric(metric);
344+
345+
return new VectorIndexBuilder<TEntity>(indexBuilder);
346+
}
347+
348+
/// <summary>
349+
/// Configures a vector index on the specified property for SQL Server.
350+
/// </summary>
351+
/// <remarks>
352+
/// See <see href="https://aka.ms/efcore-docs-modeling">Modeling entity types and relationships</see>, and
353+
/// <see href="https://aka.ms/efcore-docs-sqlserver">Accessing SQL Server and Azure SQL databases with EF Core</see>
354+
/// for more information and examples.
355+
/// </remarks>
356+
/// <param name="entityTypeBuilder">The builder for the entity type being configured.</param>
357+
/// <param name="propertyName">The name of the property to create the vector index on.</param>
358+
/// <param name="metric">
359+
/// <para>
360+
/// A string with the name of the distance metric to use to calculate the distance between the two given vectors.
361+
/// The following distance metrics are supported:
362+
/// </para>
363+
/// <list type="bullet">
364+
/// <item>
365+
/// <term>cosine</term>
366+
/// <description>Cosine distance</description>
367+
/// </item>
368+
/// <item>
369+
/// <term>euclidean</term>
370+
/// <description>Euclidean distance</description>
371+
/// </item>
372+
/// <item>
373+
/// <term>dot</term>
374+
/// <description>(Negative) Dot product</description>
375+
/// </item>
376+
/// </list>
377+
/// </param>
378+
/// <param name="name">The name to assign to the index.</param>
379+
/// <returns>A builder to further configure the vector index.</returns>
380+
public static VectorIndexBuilder HasVectorIndex(
381+
this EntityTypeBuilder entityTypeBuilder,
382+
string propertyName,
383+
string metric,
384+
string? name = null)
385+
{
386+
Check.NotEmpty(propertyName);
387+
388+
var indexBuilder = name is null
389+
? entityTypeBuilder.HasIndex(propertyName)
390+
: entityTypeBuilder.HasIndex(propertyName, name);
391+
indexBuilder.Metadata.SetVectorMetric(metric);
392+
393+
return new VectorIndexBuilder(indexBuilder);
394+
}
395+
294396
/// <summary>
295397
/// Configures a history table name for the entity mapped to a temporal table.
296398
/// </summary>

src/EFCore.SqlServer/Extensions/SqlServerIndexExtensions.cs

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -431,4 +431,144 @@ public static void SetDataCompression(this IMutableIndex index, DataCompressionT
431431
/// <returns>The <see cref="ConfigurationSource" /> for the data compression the index uses.</returns>
432432
public static ConfigurationSource? GetDataCompressionConfigurationSource(this IConventionIndex index)
433433
=> index.FindAnnotation(SqlServerAnnotationNames.DataCompression)?.GetConfigurationSource();
434+
435+
/// <summary>
436+
/// Returns whether the index is a vector index.
437+
/// </summary>
438+
/// <param name="index">The index.</param>
439+
/// <returns>Whether the index is a vector index.</returns>
440+
public static bool IsVectorIndex(this IReadOnlyIndex index)
441+
=> index is RuntimeIndex
442+
? throw new InvalidOperationException(CoreStrings.RuntimeModelMissingData)
443+
: index[SqlServerAnnotationNames.VectorIndexMetric] is not null;
444+
445+
/// <summary>
446+
/// Returns the similarity metric for the vector index.
447+
/// </summary>
448+
/// <param name="index">The index.</param>
449+
/// <returns>The similarity metric for the vector index.</returns>
450+
public static string? GetVectorMetric(this IReadOnlyIndex index)
451+
=> index is RuntimeIndex
452+
? throw new InvalidOperationException(CoreStrings.RuntimeModelMissingData)
453+
: (string?)index[SqlServerAnnotationNames.VectorIndexMetric];
454+
455+
/// <summary>
456+
/// Returns the similarity metric for the vector index.
457+
/// </summary>
458+
/// <param name="index">The index.</param>
459+
/// <param name="storeObject">The identifier of the store object.</param>
460+
/// <returns>The similarity metric for the vector index.</returns>
461+
public static string? GetVectorMetric(this IReadOnlyIndex index, in StoreObjectIdentifier storeObject)
462+
{
463+
if (index is RuntimeIndex)
464+
{
465+
throw new InvalidOperationException(CoreStrings.RuntimeModelMissingData);
466+
}
467+
468+
var annotation = index.FindAnnotation(SqlServerAnnotationNames.VectorIndexMetric);
469+
if (annotation != null)
470+
{
471+
return (string?)annotation.Value;
472+
}
473+
474+
var sharedTableRootIndex = index.FindSharedObjectRootIndex(storeObject);
475+
return sharedTableRootIndex?.GetVectorMetric(storeObject);
476+
}
477+
478+
/// <summary>
479+
/// Sets the similarity metric for the vector index.
480+
/// </summary>
481+
/// <param name="index">The index.</param>
482+
/// <param name="metric">The value to set.</param>
483+
public static void SetVectorMetric(this IMutableIndex index, string? metric)
484+
=> index.SetAnnotation(SqlServerAnnotationNames.VectorIndexMetric, metric);
485+
486+
/// <summary>
487+
/// Sets the similarity metric for the vector index.
488+
/// </summary>
489+
/// <param name="index">The index.</param>
490+
/// <param name="metric">The value to set.</param>
491+
/// <param name="fromDataAnnotation">Indicates whether the configuration was specified using a data annotation.</param>
492+
/// <returns>The configured value.</returns>
493+
public static string? SetVectorMetric(
494+
this IConventionIndex index,
495+
string? metric,
496+
bool fromDataAnnotation = false)
497+
=> (string?)index.SetAnnotation(
498+
SqlServerAnnotationNames.VectorIndexMetric,
499+
metric,
500+
fromDataAnnotation)?.Value;
501+
502+
/// <summary>
503+
/// Returns the <see cref="ConfigurationSource" /> for the similarity metric of the vector index.
504+
/// </summary>
505+
/// <param name="index">The index.</param>
506+
/// <returns>The <see cref="ConfigurationSource" /> for the similarity metric of the vector index.</returns>
507+
public static ConfigurationSource? GetVectorMetricConfigurationSource(this IConventionIndex index)
508+
=> index.FindAnnotation(SqlServerAnnotationNames.VectorIndexMetric)?.GetConfigurationSource();
509+
510+
/// <summary>
511+
/// Returns the type of the vector index.
512+
/// </summary>
513+
/// <param name="index">The index.</param>
514+
/// <returns>The type of the vector index.</returns>
515+
public static string? GetVectorIndexType(this IReadOnlyIndex index)
516+
=> (index is RuntimeIndex)
517+
? throw new InvalidOperationException(CoreStrings.RuntimeModelMissingData)
518+
: (string?)index[SqlServerAnnotationNames.VectorIndexType];
519+
520+
/// <summary>
521+
/// Returns the type of the vector index.
522+
/// </summary>
523+
/// <param name="index">The index.</param>
524+
/// <param name="storeObject">The identifier of the store object.</param>
525+
/// <returns>The type of the vector index.</returns>
526+
public static string? GetVectorIndexType(this IReadOnlyIndex index, in StoreObjectIdentifier storeObject)
527+
{
528+
if (index is RuntimeIndex)
529+
{
530+
throw new InvalidOperationException(CoreStrings.RuntimeModelMissingData);
531+
}
532+
533+
var annotation = index.FindAnnotation(SqlServerAnnotationNames.VectorIndexType);
534+
if (annotation != null)
535+
{
536+
return (string?)annotation.Value;
537+
}
538+
539+
var sharedTableRootIndex = index.FindSharedObjectRootIndex(storeObject);
540+
return sharedTableRootIndex?.GetVectorIndexType(storeObject);
541+
}
542+
543+
/// <summary>
544+
/// Sets the type of the vector index.
545+
/// </summary>
546+
/// <param name="index">The index.</param>
547+
/// <param name="type">The value to set.</param>
548+
public static void SetVectorIndexType(this IMutableIndex index, string? type)
549+
=> index.SetAnnotation(SqlServerAnnotationNames.VectorIndexType, type);
550+
551+
/// <summary>
552+
/// Sets the type of the vector index.
553+
/// </summary>
554+
/// <param name="index">The index.</param>
555+
/// <param name="type">The value to set.</param>
556+
/// <param name="fromDataAnnotation">Indicates whether the configuration was specified using a data annotation.</param>
557+
/// <returns>The configured value.</returns>
558+
public static string? SetVectorIndexType(
559+
this IConventionIndex index,
560+
string? type,
561+
bool fromDataAnnotation = false)
562+
=> (string?)index.SetAnnotation(
563+
SqlServerAnnotationNames.VectorIndexType,
564+
type,
565+
fromDataAnnotation)?.Value;
566+
567+
/// <summary>
568+
/// Returns the <see cref="ConfigurationSource" /> for the type of the vector index.
569+
/// </summary>
570+
/// <param name="index">The index.</param>
571+
/// <returns>The <see cref="ConfigurationSource" /> for the type of the vector index.</returns>
572+
public static ConfigurationSource? GetVectorIndexTypeConfigurationSource(this IConventionIndex index)
573+
=> index.FindAnnotation(SqlServerAnnotationNames.VectorIndexType)?.GetConfigurationSource();
434574
}

src/EFCore.SqlServer/Infrastructure/Internal/SqlServerModelValidator.cs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ public SqlServerModelValidator(
4141
public override void Validate(IModel model, IDiagnosticsLogger<DbLoggerCategory.Model.Validation> logger)
4242
{
4343
ValidateIndexIncludeProperties(model, logger);
44+
ValidateVectorIndexes(model, logger);
4445

4546
base.Validate(model, logger);
4647

@@ -267,6 +268,28 @@ protected virtual void ValidateIndexIncludeProperties(
267268
}
268269
}
269270

271+
/// <summary>
272+
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
273+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
274+
/// any release. You should only use it directly in your code with extreme caution and knowing that
275+
/// doing so can result in application failures when updating to a new Entity Framework Core release.
276+
/// </summary>
277+
protected virtual void ValidateVectorIndexes(
278+
IModel model,
279+
IDiagnosticsLogger<DbLoggerCategory.Model.Validation> logger)
280+
{
281+
foreach (var index in model.GetEntityTypes().SelectMany(t => t.GetDeclaredIndexes()))
282+
{
283+
if (index.IsVectorIndex() && index.Properties.Count != 1)
284+
{
285+
throw new InvalidOperationException(
286+
SqlServerStrings.VectorIndexRequiresSingleColumn(
287+
index.DisplayName(),
288+
index.DeclaringEntityType.DisplayName()));
289+
}
290+
}
291+
}
292+
270293
/// <summary>
271294
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
272295
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace Microsoft.EntityFrameworkCore.Metadata.Builders;
5+
6+
/// <summary>
7+
/// Provides a simple API for configuring a vector index on SQL Server.
8+
/// </summary>
9+
/// <remarks>
10+
/// See <see href="https://aka.ms/efcore-docs-modeling">Modeling entity types and relationships</see>, and
11+
/// <see href="https://aka.ms/efcore-docs-sqlserver">Accessing SQL Server and Azure SQL databases with EF Core</see>
12+
/// for more information and examples.
13+
/// </remarks>
14+
/// <param name="indexBuilder">The index builder.</param>
15+
public class VectorIndexBuilder(IndexBuilder indexBuilder)
16+
{
17+
/// <summary>
18+
/// The index being configured.
19+
/// </summary>
20+
public virtual IMutableIndex Metadata
21+
=> indexBuilder.Metadata;
22+
23+
/// <summary>
24+
/// Configures the name of the index in the database when targeting a relational database.
25+
/// </summary>
26+
/// <remarks>
27+
/// See <see href="https://aka.ms/efcore-docs-indexes">Indexes</see> for more information and examples.
28+
/// </remarks>
29+
/// <param name="name">The name of the index.</param>
30+
/// <returns>A builder to further configure the index.</returns>
31+
public virtual IndexBuilder HasDatabaseName(string? name)
32+
{
33+
indexBuilder.Metadata.SetDatabaseName(name);
34+
35+
return indexBuilder;
36+
}
37+
38+
/// <summary>
39+
/// Configures the similarity metric for the vector index when targeting SQL Server.
40+
/// </summary>
41+
/// <remarks>
42+
/// See <see href="https://aka.ms/efcore-docs-modeling">Modeling entity types and relationships</see>, and
43+
/// <see href="https://aka.ms/efcore-docs-sqlserver">Accessing SQL Server and Azure SQL databases with EF Core</see>
44+
/// for more information and examples.
45+
/// </remarks>
46+
/// <param name="metric">The similarity metric for the vector index (e.g., "Cosine", "Euclidean", "DotProduct").</param>
47+
/// <returns>A builder to further configure the vector index.</returns>
48+
public virtual VectorIndexBuilder UseMetric(string metric)
49+
{
50+
Check.NotEmpty(metric);
51+
Metadata.SetVectorMetric(metric);
52+
return this;
53+
}
54+
55+
/// <summary>
56+
/// Configures the type of the vector index when targeting SQL Server.
57+
/// </summary>
58+
/// <remarks>
59+
/// See <see href="https://aka.ms/efcore-docs-modeling">Modeling entity types and relationships</see>, and
60+
/// <see href="https://aka.ms/efcore-docs-sqlserver">Accessing SQL Server and Azure SQL databases with EF Core</see>
61+
/// for more information and examples.
62+
/// </remarks>
63+
/// <param name="type">The type of the vector index (e.g., "DiskANN").</param>
64+
/// <returns>A builder to further configure the vector index.</returns>
65+
public virtual VectorIndexBuilder UseType(string? type)
66+
{
67+
Metadata.SetVectorIndexType(type);
68+
return this;
69+
}
70+
}

0 commit comments

Comments
 (0)