Repository: microsoft/semantic-kernel
File: dotnet/src/VectorDataIntegrationTests/SqlServerIntegrationTests/SqlServerVectorStoreTests.cs (C#)

Code before:

            TestModel inserted = new()
            {
                Id = "MyId",
                Text = "NotAnInt",
                Number = 100,
                Floats = Enumerable.Range(0, 10).Select(i => (float)i).ToArray()
            };
            await collection.UpsertAsync(inserted);

            // Let's use a model with different storage names to trigger an SQL exception
            // which should be mapped to VectorStoreException.
            var differentNamesCollection = testStore.DefaultVectorStore.GetCollection<string, DifferentStorageNames>(collectionName);
            VectorStoreException operationEx = await Assert.ThrowsAsync<VectorStoreException>(() => differentNamesCollection.GetAsync(inserted.Id));
            Assert.IsType<SqlException>(operationEx.InnerException);

            // Let's use a model with the same storage names, but different types
            // to trigger a mapping exception (casting a string to an int).
            var sameNameDifferentModelCollection = testStore.DefaultVectorStore.GetCollection<string, SameStorageNameButDifferentType>(collectionName);
            InvalidOperationException mappingEx = await Assert.ThrowsAsync<InvalidOperationException>(() => sameNameDifferentModelCollection.GetAsync(inserted.Id));
            Assert.IsType<InvalidCastException>(mappingEx.InnerException);

            // Let's use a model with the same storage names, but different types
            // to trigger a mapping exception (deserializing a string to Memory<float>).
            var invalidJsonCollection = testStore.DefaultVectorStore.GetCollection<string, SameStorageNameButInvalidVector>(collectionName);
            await Assert.ThrowsAsync<JsonException>(() => invalidJsonCollection.GetAsync(inserted.Id, new() { IncludeVectors = true }));
        }
        finally
        {
            await collection.EnsureCollectionDeletedAsync();
        }
    }

    [ConditionalFact]
    public async Task BatchCRUD()
    {
        string collectionName = GetUniqueCollectionName();
        var testStore = fixture.TestStore;
        var collection = testStore.DefaultVectorStore.GetCollection<string, TestModel>(collectionName);

        try
        {
            await collection.EnsureCollectionExistsAsync();

            var keys = Enumerable.Range(0, 10).Select(i => $"MyId{i}").ToArray();
            TestModel[] inserted = Enumerable.Range(0, 10).Select(i => new TestModel()
            {
                Id = keys[i],
                Number = 100 + i,
                Floats = Enumerable.Range(0, 10).Select(j => (float)(i + j)).ToArray()
            }).ToArray();

Review comment (suggestion):
Letting a Json exception be thrown from the connector feels like leaking to me.  Should the user now know about the type of serialization used by each connector they are using and try to catch every exception type from those serialization mechanisms?  I would be in favour of wrapping these exceptions in InvalidOperation and having an error message saying that field x couldn't be mapped since it's not valid with JsonException as the inner exception.

Code after:
            TestModel inserted = new()
            {
                Id = "MyId",
                Text = "NotAnInt",
                Number = 100,
                Floats = Enumerable.Range(0, 10).Select(i => (float)i).ToArray()
            };
            await collection.UpsertAsync(inserted);

            // Let's use a model with different storage names to trigger an SQL exception
            // which should be mapped to VectorStoreException.
            var differentNamesCollection = testStore.DefaultVectorStore.GetCollection<string, DifferentStorageNames>(collectionName);
            VectorStoreException operationEx = await Assert.ThrowsAsync<VectorStoreException>(() => differentNamesCollection.GetAsync(inserted.Id));
            Assert.IsType<SqlException>(operationEx.InnerException);

            // Let's use a model with the same storage names, but different types
            // to trigger a mapping exception (casting a string to an int).
            var sameNameDifferentModelCollection = testStore.DefaultVectorStore.GetCollection<string, SameStorageNameButDifferentType>(collectionName);
            InvalidOperationException mappingEx = await Assert.ThrowsAsync<InvalidOperationException>(() => sameNameDifferentModelCollection.GetAsync(inserted.Id));
            Assert.IsType<InvalidCastException>(mappingEx.InnerException);

            // Let's use a model with the same storage names, but different types
            // to trigger a mapping exception (deserializing a string to Memory<float>).
            var invalidJsonCollection = testStore.DefaultVectorStore.GetCollection<string, SameStorageNameButInvalidVector>(collectionName);
            await Assert.ThrowsAsync<InvalidOperationException>(() => invalidJsonCollection.GetAsync(inserted.Id, new() { IncludeVectors = true }));
        }
        finally
        {
            await collection.EnsureCollectionDeletedAsync();
        }
    }

    [ConditionalFact]
    public async Task BatchCRUD()
    {
        string collectionName = GetUniqueCollectionName();
        var testStore = fixture.TestStore;
        var collection = testStore.DefaultVectorStore.GetCollection<string, TestModel>(collectionName);

        try
        {
            await collection.EnsureCollectionExistsAsync();

            var keys = Enumerable.Range(0, 10).Select(i => $"MyId{i}").ToArray();
            TestModel[] inserted = Enumerable.Range(0, 10).Select(i => new TestModel()
            {
                Id = keys[i],
                Number = 100 + i,
                Floats = Enumerable.Range(0, 10).Select(j => (float)(i + j)).ToArray()
            }).ToArray();