You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
.Net Improve type safety: Return TRecord instead of object in ITextSearch.GetSearchResultsAsync (#13318)
This PR enhances the type safety of the `ITextSearch<TRecord>` interface
by changing the `GetSearchResultsAsync` method to return
`KernelSearchResults<TRecord>` instead of `KernelSearchResults<object>`.
This improvement eliminates the need for manual casting and provides
better IntelliSense support for consumers.
## Motivation and Context
The current implementation of
`ITextSearch<TRecord>.GetSearchResultsAsync` returns
`KernelSearchResults<object>`, which requires consumers to manually cast
results to the expected type. This reduces type safety and degrades the
developer experience by losing compile-time type checking and
IntelliSense support.
This change aligns the return type with the generic type parameter
`TRecord`, providing the expected strongly-typed results that users of a
generic interface would anticipate.
## Changes Made
### Interface (ITextSearch.cs)
- Changed `ITextSearch<TRecord>.GetSearchResultsAsync` return type from
`KernelSearchResults<object>` to `KernelSearchResults<TRecord>`
- Updated XML documentation to reflect strongly-typed return value
- Legacy `ITextSearch` interface (non-generic) remains unchanged,
continuing to return `KernelSearchResults<object>` for backward
compatibility
### Implementation (VectorStoreTextSearch.cs)
- Added new `GetResultsAsTRecordAsync` helper method returning
`IAsyncEnumerable<TRecord>`
- Updated generic interface implementation to use the new strongly-typed
helper
- Retained `GetResultsAsRecordAsync` method for the legacy non-generic
interface
### Tests (VectorStoreTextSearchTests.cs)
- Updated 3 unit tests to use strongly-typed `DataModel` or
`DataModelWithRawEmbedding` instead of `object`
- Improved test assertions to leverage direct property access without
casting
- All 19 tests pass successfully
## Breaking Changes
**Interface Change (Experimental API):**
- `ITextSearch<TRecord>.GetSearchResultsAsync` now returns
`KernelSearchResults<TRecord>` instead of `KernelSearchResults<object>`
- This interface is marked with `[Experimental("SKEXP0001")]`,
indicating that breaking changes are expected during the preview period
- Legacy `ITextSearch` interface (non-generic) is unaffected and
maintains full backward compatibility
## Benefits
- **Improved Type Safety**: Eliminates runtime casting errors by
providing compile-time type checking
- **Enhanced Developer Experience**: Full IntelliSense support for
TRecord properties and methods
- **Cleaner Code**: Consumers no longer need to cast results from object
to the expected type
- **Consistent API Design**: Generic interface now behaves as expected,
returning strongly-typed results
- **Zero Impact on Legacy Code**: Non-generic ITextSearch interface
remains unchanged
## Testing
- All 19 existing unit tests pass
- Updated tests demonstrate improved type safety with direct property
access
- Verified both generic and legacy interfaces work correctly
- Confirmed zero breaking changes to non-generic ITextSearch consumers
## Related Work
This PR is part of the Issue #10456 multi-PR chain for modernizing
ITextSearch with LINQ-based filtering:
- PR #13175: Foundation (ITextSearch<TRecord> interface) - Merged
- PR #13179: VectorStoreTextSearch + deprecation pattern - In Review
- **This PR (2.1)**: API refinement for improved type safety
- PR #13188-13191: Connector migrations (Bing, Google, Tavily, Brave) -
Pending
- PR #13194: Samples and documentation - Pending
All PRs target the `feature-text-search-linq` branch for coordinated
release.
## Migration Guide for Consumers
### Before (Previous API)
```csharp
ITextSearch<DataModel> search = ...;
KernelSearchResults<object> results = await search.GetSearchResultsAsync("query", options);
foreach (var obj in results.Results)
{
var record = (DataModel)obj; // Manual cast required
Console.WriteLine(record.Name);
}
```
### After (Improved API)
```csharp
ITextSearch<DataModel> search = ...;
KernelSearchResults<DataModel> results = await search.GetSearchResultsAsync("query", options);
foreach (var record in results.Results) // Strongly typed!
{
Console.WriteLine(record.Name); // Direct property access with IntelliSense
}
```
## Checklist
- [x] Changes build successfully
- [x] All unit tests pass (19/19)
- [x] XML documentation updated
- [x] Breaking change documented (experimental API only)
- [x] Legacy interface backward compatibility maintained
- [x] Code follows project coding standards
Co-authored-by: Alexander Zarei <alzarei@users.noreply.github.com>
/// Perform a search for content related to the specified query and return <see cref="object"/> values representing the search results.
39
+
/// Perform a search for content related to the specified query and return strongly-typed <typeparamref name="TRecord"/> values representing the search results.
40
40
/// </summary>
41
41
/// <param name="query">What to search for.</param>
42
42
/// <param name="searchOptions">Options used when executing a text search.</param>
43
43
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
Copy file name to clipboardExpand all lines: dotnet/src/SemanticKernel.UnitTests/Data/VectorStoreTextSearchTests.cs
+9-6Lines changed: 9 additions & 6 deletions
Original file line number
Diff line number
Diff line change
@@ -78,12 +78,14 @@ public async Task CanGetSearchResultAsync()
78
78
{
79
79
// Arrange.
80
80
varsut=awaitCreateVectorStoreTextSearchAsync();
81
+
ITextSearch<DataModel>typeSafeInterface=sut;
81
82
82
83
// Act.
83
-
KernelSearchResults<object>searchResults=awaitsut.GetSearchResultsAsync("What is the Semantic Kernel?",new(){Top=2,Skip=0});
84
+
KernelSearchResults<DataModel>searchResults=awaittypeSafeInterface.GetSearchResultsAsync("What is the Semantic Kernel?",newTextSearchOptions<DataModel>{Top=2,Skip=0});
KernelSearchResults<object>searchResults=awaitsut.GetSearchResultsAsync("What is the Semantic Kernel?",new(){Top=2,Skip=0});
125
+
KernelSearchResults<DataModelWithRawEmbedding>searchResults=awaittypeSafeInterface.GetSearchResultsAsync("What is the Semantic Kernel?",newTextSearchOptions<DataModelWithRawEmbedding>{Top=2,Skip=0});
0 commit comments