@@ -535,41 +535,67 @@ private static SqlCommand SelectVectorWithVectorSearch<TRecord>(
535535 string distanceMetric ,
536536 string sorting )
537537 {
538- // VECTOR_SEARCH() currently only supports post-filtering (TOP_N candidates are returned first,
539- // then predicates are applied). Pre-filtering is not supported.
540- if ( options . Filter is not null )
541- {
542- throw new NotSupportedException (
543- "Filtering is not supported with approximate vector search (VECTOR_SEARCH). " +
544- "Remove the filter or use IndexKind.Flat for exact search with VECTOR_DISTANCE." ) ;
545- }
546-
547538 SqlCommand command = connection . CreateCommand ( ) ;
548539 command . Parameters . AddWithValue ( "@vector" , vector ) ;
549540
550541 StringBuilder sb = new ( 300 ) ;
551542
543+ // When skip > 0, we need a subquery since TOP and OFFSET/FETCH can't coexist in the same SELECT.
544+ bool needsSubquery = options . Skip > 0 ;
545+
546+ if ( needsSubquery )
547+ {
548+ sb . Append ( "SELECT * FROM (" ) ;
549+ }
550+
552551 // VECTOR_SEARCH returns all columns from the table plus a 'distance' column.
553552 // We select the needed columns from the table alias and alias 'distance' as 'score'.
554- sb . Append ( "SELECT " ) ;
553+ // The latest version vector indexes require SELECT TOP(N) WITH APPROXIMATE instead of the deprecated TOP_N parameter.
554+ sb . Append ( "SELECT TOP(" ) . Append ( top + options . Skip ) . Append ( ") WITH APPROXIMATE " ) ;
555555 sb . AppendIdentifiers ( model . Properties , prefix : "t." , includeVectors : options . IncludeVectors ) ;
556556 sb . AppendLine ( "," ) ;
557557 sb . AppendLine ( "s.[distance] AS [score]" ) ;
558558 sb . Append ( "FROM VECTOR_SEARCH(TABLE = " ) ;
559559 sb . AppendTableName ( schema , tableName ) ;
560560 sb . Append ( " AS t, COLUMN = " ) . AppendIdentifier ( vectorProperty . StorageName ) ;
561- sb . Append ( ", SIMILAR_TO = @vector, METRIC = '" ) . Append ( distanceMetric ) . Append ( '\' ' ) ;
562- sb . Append ( ", TOP_N = " ) . Append ( top + options . Skip ) . AppendLine ( ") AS s" ) ;
561+ sb . Append ( ", SIMILAR_TO = @vector, METRIC = '" ) . Append ( distanceMetric ) . AppendLine ( "') AS s" ) ;
562+
563+ // With latest version vector indexes, WHERE predicates are applied during the vector search process
564+ // (iterative filtering), not after retrieval.
565+ if ( options . Filter is not null )
566+ {
567+ int startParamIndex = command . Parameters . Count ;
568+
569+ SqlServerFilterTranslator translator = new ( model , options . Filter , sb , startParamIndex : startParamIndex , tableAlias : "t" ) ;
570+ translator . Translate ( appendWhere : true ) ;
571+ List < object > parameters = translator . ParameterValues ;
572+
573+ foreach ( object parameter in parameters )
574+ {
575+ command . AddParameter ( property : null , $ "@_{ startParamIndex ++ } ", parameter ) ;
576+ }
577+
578+ sb . AppendLine ( ) ;
579+ }
563580
564581 if ( options . ScoreThreshold is not null )
565582 {
566583 command . Parameters . AddWithValue ( "@scoreThreshold" , options . ScoreThreshold ! . Value ) ;
567- sb . AppendLine ( "WHERE s.[distance] <= @scoreThreshold" ) ;
584+ sb . Append ( options . Filter is not null ? "AND " : "WHERE " ) ;
585+ sb . AppendLine ( "s.[distance] <= @scoreThreshold" ) ;
568586 }
569587
570588 sb . AppendFormat ( "ORDER BY [score] {0}" , sorting ) ;
571- sb . AppendLine ( ) ;
572- sb . AppendFormat ( "OFFSET {0} ROWS FETCH NEXT {1} ROWS ONLY;" , options . Skip , top ) ;
589+
590+ if ( needsSubquery )
591+ {
592+ sb . AppendLine ( ) ;
593+ sb . Append ( ") AS [inner]" ) ;
594+ sb . AppendLine ( ) ;
595+ sb . AppendFormat ( "ORDER BY [score] {0}" , sorting ) ;
596+ sb . AppendLine ( ) ;
597+ sb . AppendFormat ( "OFFSET {0} ROWS FETCH NEXT {1} ROWS ONLY;" , options . Skip , top ) ;
598+ }
573599
574600 command . CommandText = sb . ToString ( ) ;
575601 return command ;
@@ -587,15 +613,6 @@ internal static SqlCommand SelectHybrid<TRecord>(
587613 {
588614 bool useVectorSearch = UseVectorSearch ( vectorProperty ) ;
589615
590- // VECTOR_SEARCH() currently only supports post-filtering (TOP_N candidates are returned first,
591- // then predicates are applied). Pre-filtering is not supported.
592- if ( useVectorSearch && options . Filter is not null )
593- {
594- throw new NotSupportedException (
595- "Filtering is not supported with approximate vector search (VECTOR_SEARCH). " +
596- "Remove the filter or use IndexKind.Flat for exact search with VECTOR_DISTANCE." ) ;
597- }
598-
599616 string distanceFunction = vectorProperty . DistanceFunction ?? DistanceFunction . CosineDistance ;
600617 ( string distanceMetric , _ ) = MapDistanceFunction ( distanceFunction ) ;
601618
@@ -652,16 +669,32 @@ internal static SqlCommand SelectHybrid<TRecord>(
652669 // CTE 2: Semantic/vector search
653670 if ( useVectorSearch )
654671 {
655- // Use VECTOR_SEARCH() for approximate nearest neighbor search with a vector index
672+ // Use VECTOR_SEARCH() for approximate nearest neighbor search with a vector index.
673+ // The latest version vector indexes require SELECT TOP(N) WITH APPROXIMATE instead of the deprecated TOP_N parameter.
656674 sb . AppendLine ( "semantic_search AS (" ) ;
657- sb . AppendLine ( " SELECT TOP(@candidateCount)" ) ;
675+ sb . AppendLine ( " SELECT TOP(@candidateCount) WITH APPROXIMATE " ) ;
658676 sb . Append ( " t." ) . AppendIdentifier ( model . KeyProperty . StorageName ) . AppendLine ( "," ) ;
659677 sb . AppendLine ( " RANK() OVER (ORDER BY s.[distance]) AS [rank]" ) ;
660678 sb . AppendLine ( " FROM VECTOR_SEARCH(TABLE = " ) ;
661679 sb . Append ( " " ) . AppendTableName ( schema , tableName ) ;
662680 sb . Append ( " AS t, COLUMN = " ) . AppendIdentifier ( vectorProperty . StorageName ) ;
663- sb . Append ( ", SIMILAR_TO = @vector, METRIC = '" ) . Append ( distanceMetric ) . Append ( '\' ' ) ;
664- sb . Append ( ", TOP_N = @candidateCount" ) . AppendLine ( ") AS s" ) ;
681+ sb . Append ( ", SIMILAR_TO = @vector, METRIC = '" ) . Append ( distanceMetric ) . AppendLine ( "') AS s" ) ;
682+
683+ // With latest version vector indexes, WHERE predicates are applied during the vector search process
684+ // (iterative filtering), not after retrieval.
685+ if ( options . Filter is not null )
686+ {
687+ int filterParamStart = command . Parameters . Count ;
688+ SqlServerFilterTranslator translator = new ( model , options . Filter , sb , startParamIndex : filterParamStart , tableAlias : "t" ) ;
689+ translator . Translate ( appendWhere : true ) ;
690+ foreach ( object parameter in translator . ParameterValues )
691+ {
692+ command . AddParameter ( property : null , $ "@_{ filterParamStart ++ } ", parameter ) ;
693+ }
694+ sb . AppendLine ( ) ;
695+ }
696+
697+ sb . AppendLine ( " ORDER BY s.[distance]" ) ;
665698 sb . AppendLine ( ")," ) ;
666699 }
667700 else
0 commit comments