diff --git a/dotnet/src/VectorData/AzureAISearch/AzureAISearchFilterTranslator.cs b/dotnet/src/VectorData/AzureAISearch/AzureAISearchFilterTranslator.cs
index df8ab46e3d15..f6b724eb1340 100644
--- a/dotnet/src/VectorData/AzureAISearch/AzureAISearchFilterTranslator.cs
+++ b/dotnet/src/VectorData/AzureAISearch/AzureAISearchFilterTranslator.cs
@@ -187,9 +187,42 @@ private void TranslateMethodCall(MethodCallExpression methodCall)
this.TranslateContains(source, item);
return;
+ // C# 14 made changes to overload resolution to prefer Span-based overloads when those exist ("first-class spans");
+ // this makes MemoryExtensions.Contains() be resolved rather than Enumerable.Contains() (see above).
+ // MemoryExtensions.Contains() also accepts a Span argument for the source, adding an implicit cast we need to remove.
+ // See https://github.com/dotnet/runtime/issues/109757 for more context.
+ // Note that MemoryExtensions.Contains has an optional 3rd ComparisonType parameter; we only match when
+ // it's null.
+ case { Method.Name: nameof(MemoryExtensions.Contains), Arguments: [var spanArg, var item, ..] } contains
+ when contains.Method.DeclaringType == typeof(MemoryExtensions)
+ && (contains.Arguments.Count is 2
+ || (contains.Arguments.Count is 3 && contains.Arguments[2] is ConstantExpression { Value: null }))
+ && TryUnwrapSpanImplicitCast(spanArg, out var source):
+ this.TranslateContains(source, item);
+ return;
+
default:
throw new NotSupportedException($"Unsupported method call: {methodCall.Method.DeclaringType?.Name}.{methodCall.Method.Name}");
}
+
+ static bool TryUnwrapSpanImplicitCast(Expression expression, [NotNullWhen(true)] out Expression? result)
+ {
+ if (expression is UnaryExpression
+ {
+ NodeType: ExpressionType.Convert,
+ Method: { Name: "op_Implicit", DeclaringType: { IsGenericType: true } implicitCastDeclaringType },
+ Operand: var unwrapped
+ }
+ && implicitCastDeclaringType.GetGenericTypeDefinition() is var genericTypeDefinition
+ && (genericTypeDefinition == typeof(Span<>) || genericTypeDefinition == typeof(ReadOnlySpan<>)))
+ {
+ result = unwrapped;
+ return true;
+ }
+
+ result = null;
+ return false;
+ }
}
private void TranslateContains(Expression source, Expression item)
diff --git a/dotnet/src/VectorData/Common/SqlFilterTranslator.cs b/dotnet/src/VectorData/Common/SqlFilterTranslator.cs
index 086efc010f55..1cd25d808dfe 100644
--- a/dotnet/src/VectorData/Common/SqlFilterTranslator.cs
+++ b/dotnet/src/VectorData/Common/SqlFilterTranslator.cs
@@ -239,9 +239,42 @@ private void TranslateMethodCall(MethodCallExpression methodCall, bool isSearchC
this.TranslateContains(source, item);
return;
+ // C# 14 made changes to overload resolution to prefer Span-based overloads when those exist ("first-class spans");
+ // this makes MemoryExtensions.Contains() be resolved rather than Enumerable.Contains() (see above).
+ // MemoryExtensions.Contains() also accepts a Span argument for the source, adding an implicit cast we need to remove.
+ // See https://github.com/dotnet/runtime/issues/109757 for more context.
+ // Note that MemoryExtensions.Contains has an optional 3rd ComparisonType parameter; we only match when
+ // it's null.
+ case { Method.Name: nameof(MemoryExtensions.Contains), Arguments: [var spanArg, var item, ..] } contains
+ when contains.Method.DeclaringType == typeof(MemoryExtensions)
+ && (contains.Arguments.Count is 2
+ || (contains.Arguments.Count is 3 && contains.Arguments[2] is ConstantExpression { Value: null }))
+ && TryUnwrapSpanImplicitCast(spanArg, out var source):
+ this.TranslateContains(source, item);
+ return;
+
default:
throw new NotSupportedException($"Unsupported method call: {methodCall.Method.DeclaringType?.Name}.{methodCall.Method.Name}");
}
+
+ static bool TryUnwrapSpanImplicitCast(Expression expression, [NotNullWhen(true)] out Expression? result)
+ {
+ if (expression is UnaryExpression
+ {
+ NodeType: ExpressionType.Convert,
+ Method: { Name: "op_Implicit", DeclaringType: { IsGenericType: true } implicitCastDeclaringType },
+ Operand: var unwrapped
+ }
+ && implicitCastDeclaringType.GetGenericTypeDefinition() is var genericTypeDefinition
+ && (genericTypeDefinition == typeof(Span<>) || genericTypeDefinition == typeof(ReadOnlySpan<>)))
+ {
+ result = unwrapped;
+ return true;
+ }
+
+ result = null;
+ return false;
+ }
}
private void TranslateContains(Expression source, Expression item)
diff --git a/dotnet/src/VectorData/CosmosMongoDB/CosmosMongoFilterTranslator.cs b/dotnet/src/VectorData/CosmosMongoDB/CosmosMongoFilterTranslator.cs
index d14c6b37a73f..6f53a9a9e525 100644
--- a/dotnet/src/VectorData/CosmosMongoDB/CosmosMongoFilterTranslator.cs
+++ b/dotnet/src/VectorData/CosmosMongoDB/CosmosMongoFilterTranslator.cs
@@ -155,7 +155,8 @@ private BsonDocument TranslateNot(UnaryExpression not)
}
private BsonDocument TranslateMethodCall(MethodCallExpression methodCall)
- => methodCall switch
+ {
+ return methodCall switch
{
// Enumerable.Contains()
{ Method.Name: nameof(Enumerable.Contains), Arguments: [var source, var item] } contains
@@ -171,11 +172,45 @@ private BsonDocument TranslateMethodCall(MethodCallExpression methodCall)
},
Object: Expression source,
Arguments: [var item]
- } when declaringType.GetGenericTypeDefinition() == typeof(List<>) => this.TranslateContains(source, item),
+ } when declaringType.GetGenericTypeDefinition() == typeof(List<>)
+ => this.TranslateContains(source, item),
+
+ // C# 14 made changes to overload resolution to prefer Span-based overloads when those exist ("first-class spans");
+ // this makes MemoryExtensions.Contains() be resolved rather than Enumerable.Contains() (see above).
+ // MemoryExtensions.Contains() also accepts a Span argument for the source, adding an implicit cast we need to remove.
+ // See https://github.com/dotnet/runtime/issues/109757 for more context.
+ // Note that MemoryExtensions.Contains has an optional 3rd ComparisonType parameter; we only match when
+ // it's null.
+ { Method.Name: nameof(MemoryExtensions.Contains), Arguments: [var spanArg, var item, ..] } contains
+ when contains.Method.DeclaringType == typeof(MemoryExtensions)
+ && (contains.Arguments.Count is 2
+ || (contains.Arguments.Count is 3 && contains.Arguments[2] is ConstantExpression { Value: null }))
+ && TryUnwrapSpanImplicitCast(spanArg, out var source)
+ => this.TranslateContains(source, item),
_ => throw new NotSupportedException($"Unsupported method call: {methodCall.Method.DeclaringType?.Name}.{methodCall.Method.Name}")
};
+ static bool TryUnwrapSpanImplicitCast(Expression expression, [NotNullWhen(true)] out Expression? result)
+ {
+ if (expression is UnaryExpression
+ {
+ NodeType: ExpressionType.Convert,
+ Method: { Name: "op_Implicit", DeclaringType: { IsGenericType: true } implicitCastDeclaringType },
+ Operand: var unwrapped
+ }
+ && implicitCastDeclaringType.GetGenericTypeDefinition() is var genericTypeDefinition
+ && (genericTypeDefinition == typeof(Span<>) || genericTypeDefinition == typeof(ReadOnlySpan<>)))
+ {
+ result = unwrapped;
+ return true;
+ }
+
+ result = null;
+ return false;
+ }
+ }
+
private BsonDocument TranslateContains(Expression source, Expression item)
{
switch (source)
diff --git a/dotnet/src/VectorData/CosmosNoSql/CosmosNoSqlFilterTranslator.cs b/dotnet/src/VectorData/CosmosNoSql/CosmosNoSqlFilterTranslator.cs
index deb629f94813..4ac5715d237a 100644
--- a/dotnet/src/VectorData/CosmosNoSql/CosmosNoSqlFilterTranslator.cs
+++ b/dotnet/src/VectorData/CosmosNoSql/CosmosNoSqlFilterTranslator.cs
@@ -230,9 +230,42 @@ private void TranslateMethodCall(MethodCallExpression methodCall)
this.TranslateContains(source, item);
return;
+ // C# 14 made changes to overload resolution to prefer Span-based overloads when those exist ("first-class spans");
+ // this makes MemoryExtensions.Contains() be resolved rather than Enumerable.Contains() (see above).
+ // MemoryExtensions.Contains() also accepts a Span argument for the source, adding an implicit cast we need to remove.
+ // See https://github.com/dotnet/runtime/issues/109757 for more context.
+ // Note that MemoryExtensions.Contains has an optional 3rd ComparisonType parameter; we only match when
+ // it's null.
+ case { Method.Name: nameof(MemoryExtensions.Contains), Arguments: [var spanArg, var item, ..] } contains
+ when contains.Method.DeclaringType == typeof(MemoryExtensions)
+ && (contains.Arguments.Count is 2
+ || (contains.Arguments.Count is 3 && contains.Arguments[2] is ConstantExpression { Value: null }))
+ && TryUnwrapSpanImplicitCast(spanArg, out var source):
+ this.TranslateContains(source, item);
+ return;
+
default:
throw new NotSupportedException($"Unsupported method call: {methodCall.Method.DeclaringType?.Name}.{methodCall.Method.Name}");
}
+
+ static bool TryUnwrapSpanImplicitCast(Expression expression, [NotNullWhen(true)] out Expression? result)
+ {
+ if (expression is UnaryExpression
+ {
+ NodeType: ExpressionType.Convert,
+ Method: { Name: "op_Implicit", DeclaringType: { IsGenericType: true } implicitCastDeclaringType },
+ Operand: var unwrapped
+ }
+ && implicitCastDeclaringType.GetGenericTypeDefinition() is var genericTypeDefinition
+ && (genericTypeDefinition == typeof(Span<>) || genericTypeDefinition == typeof(ReadOnlySpan<>)))
+ {
+ result = unwrapped;
+ return true;
+ }
+
+ result = null;
+ return false;
+ }
}
private void TranslateContains(Expression source, Expression item)
diff --git a/dotnet/src/VectorData/Directory.Build.props b/dotnet/src/VectorData/Directory.Build.props
index 44a8810c0a8e..6e99a038778f 100644
--- a/dotnet/src/VectorData/Directory.Build.props
+++ b/dotnet/src/VectorData/Directory.Build.props
@@ -3,6 +3,7 @@
+ latest
$(NoWarn);MEVD9000,MEVD9001
diff --git a/dotnet/src/VectorData/MongoDB/MongoFilterTranslator.cs b/dotnet/src/VectorData/MongoDB/MongoFilterTranslator.cs
index 98ae37a4311a..50f7082050c6 100644
--- a/dotnet/src/VectorData/MongoDB/MongoFilterTranslator.cs
+++ b/dotnet/src/VectorData/MongoDB/MongoFilterTranslator.cs
@@ -161,7 +161,8 @@ private BsonDocument TranslateNot(UnaryExpression not)
}
private BsonDocument TranslateMethodCall(MethodCallExpression methodCall)
- => methodCall switch
+ {
+ return methodCall switch
{
// Enumerable.Contains()
{ Method.Name: nameof(Enumerable.Contains), Arguments: [var source, var item] } contains
@@ -179,9 +180,42 @@ private BsonDocument TranslateMethodCall(MethodCallExpression methodCall)
Arguments: [var item]
} when declaringType.GetGenericTypeDefinition() == typeof(List<>) => this.TranslateContains(source, item),
+ // C# 14 made changes to overload resolution to prefer Span-based overloads when those exist ("first-class spans");
+ // this makes MemoryExtensions.Contains() be resolved rather than Enumerable.Contains() (see above).
+ // MemoryExtensions.Contains() also accepts a Span argument for the source, adding an implicit cast we need to remove.
+ // See https://github.com/dotnet/runtime/issues/109757 for more context.
+ // Note that MemoryExtensions.Contains has an optional 3rd ComparisonType parameter; we only match when
+ // it's null.
+ { Method.Name: nameof(MemoryExtensions.Contains), Arguments: [var spanArg, var item, ..] } contains
+ when contains.Method.DeclaringType == typeof(MemoryExtensions)
+ && (contains.Arguments.Count is 2
+ || (contains.Arguments.Count is 3 && contains.Arguments[2] is ConstantExpression { Value: null }))
+ && TryUnwrapSpanImplicitCast(spanArg, out var source)
+ => this.TranslateContains(source, item),
+
_ => throw new NotSupportedException($"Unsupported method call: {methodCall.Method.DeclaringType?.Name}.{methodCall.Method.Name}")
};
+ static bool TryUnwrapSpanImplicitCast(Expression expression, [NotNullWhen(true)] out Expression? result)
+ {
+ if (expression is UnaryExpression
+ {
+ NodeType: ExpressionType.Convert,
+ Method: { Name: "op_Implicit", DeclaringType: { IsGenericType: true } implicitCastDeclaringType },
+ Operand: var unwrapped
+ }
+ && implicitCastDeclaringType.GetGenericTypeDefinition() is var genericTypeDefinition
+ && (genericTypeDefinition == typeof(Span<>) || genericTypeDefinition == typeof(ReadOnlySpan<>)))
+ {
+ result = unwrapped;
+ return true;
+ }
+
+ result = null;
+ return false;
+ }
+ }
+
private BsonDocument TranslateContains(Expression source, Expression item)
{
switch (source)
diff --git a/dotnet/src/VectorData/Pinecone/PineconeFilterTranslator.cs b/dotnet/src/VectorData/Pinecone/PineconeFilterTranslator.cs
index 34c5fb39d7d7..bfe02e9b013a 100644
--- a/dotnet/src/VectorData/Pinecone/PineconeFilterTranslator.cs
+++ b/dotnet/src/VectorData/Pinecone/PineconeFilterTranslator.cs
@@ -158,7 +158,8 @@ private Metadata TranslateNot(UnaryExpression not)
}
private Metadata TranslateMethodCall(MethodCallExpression methodCall)
- => methodCall switch
+ {
+ return methodCall switch
{
// Enumerable.Contains()
{ Method.Name: nameof(Enumerable.Contains), Arguments: [var source, var item] } contains
@@ -176,9 +177,42 @@ private Metadata TranslateMethodCall(MethodCallExpression methodCall)
Arguments: [var item]
} when declaringType.GetGenericTypeDefinition() == typeof(List<>) => this.TranslateContains(source, item),
+ // C# 14 made changes to overload resolution to prefer Span-based overloads when those exist ("first-class spans");
+ // this makes MemoryExtensions.Contains() be resolved rather than Enumerable.Contains() (see above).
+ // MemoryExtensions.Contains() also accepts a Span argument for the source, adding an implicit cast we need to remove.
+ // See https://github.com/dotnet/runtime/issues/109757 for more context.
+ // Note that MemoryExtensions.Contains has an optional 3rd ComparisonType parameter; we only match when
+ // it's null.
+ { Method.Name: nameof(MemoryExtensions.Contains), Arguments: [var spanArg, var item, ..] } contains
+ when contains.Method.DeclaringType == typeof(MemoryExtensions)
+ && (contains.Arguments.Count is 2
+ || (contains.Arguments.Count is 3 && contains.Arguments[2] is ConstantExpression { Value: null }))
+ && TryUnwrapSpanImplicitCast(spanArg, out var source)
+ => this.TranslateContains(source, item),
+
_ => throw new NotSupportedException($"Unsupported method call: {methodCall.Method.DeclaringType?.Name}.{methodCall.Method.Name}")
};
+ static bool TryUnwrapSpanImplicitCast(Expression expression, [NotNullWhen(true)] out Expression? result)
+ {
+ if (expression is UnaryExpression
+ {
+ NodeType: ExpressionType.Convert,
+ Method: { Name: "op_Implicit", DeclaringType: { IsGenericType: true } implicitCastDeclaringType },
+ Operand: var unwrapped
+ }
+ && implicitCastDeclaringType.GetGenericTypeDefinition() is var genericTypeDefinition
+ && (genericTypeDefinition == typeof(Span<>) || genericTypeDefinition == typeof(ReadOnlySpan<>)))
+ {
+ result = unwrapped;
+ return true;
+ }
+
+ result = null;
+ return false;
+ }
+ }
+
private Metadata TranslateContains(Expression source, Expression item)
{
switch (source)
diff --git a/dotnet/src/VectorData/Qdrant/QdrantFilterTranslator.cs b/dotnet/src/VectorData/Qdrant/QdrantFilterTranslator.cs
index bc82b366e7b0..1cf26de9c072 100644
--- a/dotnet/src/VectorData/Qdrant/QdrantFilterTranslator.cs
+++ b/dotnet/src/VectorData/Qdrant/QdrantFilterTranslator.cs
@@ -267,7 +267,8 @@ private Filter TranslateNot(Expression expression)
#endregion Logical operators
private Filter TranslateMethodCall(MethodCallExpression methodCall)
- => methodCall switch
+ {
+ return methodCall switch
{
// Enumerable.Contains()
{ Method.Name: nameof(Enumerable.Contains), Arguments: [var source, var item] } contains
@@ -286,9 +287,42 @@ private Filter TranslateMethodCall(MethodCallExpression methodCall)
} when declaringType.GetGenericTypeDefinition() == typeof(List<>)
=> this.TranslateContains(source, item),
+ // C# 14 made changes to overload resolution to prefer Span-based overloads when those exist ("first-class spans");
+ // this makes MemoryExtensions.Contains() be resolved rather than Enumerable.Contains() (see above).
+ // MemoryExtensions.Contains() also accepts a Span argument for the source, adding an implicit cast we need to remove.
+ // See https://github.com/dotnet/runtime/issues/109757 for more context.
+ // Note that MemoryExtensions.Contains has an optional 3rd ComparisonType parameter; we only match when
+ // it's null.
+ { Method.Name: nameof(MemoryExtensions.Contains), Arguments: [var spanArg, var item, ..] } contains
+ when contains.Method.DeclaringType == typeof(MemoryExtensions)
+ && (contains.Arguments.Count is 2
+ || (contains.Arguments.Count is 3 && contains.Arguments[2] is ConstantExpression { Value: null }))
+ && TryUnwrapSpanImplicitCast(spanArg, out var source)
+ => this.TranslateContains(source, item),
+
_ => throw new NotSupportedException($"Unsupported method call: {methodCall.Method.DeclaringType?.Name}.{methodCall.Method.Name}")
};
+ static bool TryUnwrapSpanImplicitCast(Expression expression, [NotNullWhen(true)] out Expression? result)
+ {
+ if (expression is UnaryExpression
+ {
+ NodeType: ExpressionType.Convert,
+ Method: { Name: "op_Implicit", DeclaringType: { IsGenericType: true } implicitCastDeclaringType },
+ Operand: var unwrapped
+ }
+ && implicitCastDeclaringType.GetGenericTypeDefinition() is var genericTypeDefinition
+ && (genericTypeDefinition == typeof(Span<>) || genericTypeDefinition == typeof(ReadOnlySpan<>)))
+ {
+ result = unwrapped;
+ return true;
+ }
+
+ result = null;
+ return false;
+ }
+ }
+
private Filter TranslateContains(Expression source, Expression item)
{
switch (source)
diff --git a/dotnet/src/VectorData/Redis/RedisFilterTranslator.cs b/dotnet/src/VectorData/Redis/RedisFilterTranslator.cs
index eec5ae6f3da5..f71873e8f87f 100644
--- a/dotnet/src/VectorData/Redis/RedisFilterTranslator.cs
+++ b/dotnet/src/VectorData/Redis/RedisFilterTranslator.cs
@@ -173,9 +173,42 @@ private void TranslateMethodCall(MethodCallExpression methodCall)
this.TranslateContains(source, item);
return;
+ // C# 14 made changes to overload resolution to prefer Span-based overloads when those exist ("first-class spans");
+ // this makes MemoryExtensions.Contains() be resolved rather than Enumerable.Contains() (see above).
+ // MemoryExtensions.Contains() also accepts a Span argument for the source, adding an implicit cast we need to remove.
+ // See https://github.com/dotnet/runtime/issues/109757 for more context.
+ // Note that MemoryExtensions.Contains has an optional 3rd ComparisonType parameter; we only match when
+ // it's null.
+ case { Method.Name: nameof(MemoryExtensions.Contains), Arguments: [var spanArg, var item, ..] } contains
+ when contains.Method.DeclaringType == typeof(MemoryExtensions)
+ && (contains.Arguments.Count is 2
+ || (contains.Arguments.Count is 3 && contains.Arguments[2] is ConstantExpression { Value: null }))
+ && TryUnwrapSpanImplicitCast(spanArg, out var source):
+ this.TranslateContains(source, item);
+ return;
+
default:
throw new NotSupportedException($"Unsupported method call: {methodCall.Method.DeclaringType?.Name}.{methodCall.Method.Name}");
}
+
+ static bool TryUnwrapSpanImplicitCast(Expression expression, [NotNullWhen(true)] out Expression? result)
+ {
+ if (expression is UnaryExpression
+ {
+ NodeType: ExpressionType.Convert,
+ Method: { Name: "op_Implicit", DeclaringType: { IsGenericType: true } implicitCastDeclaringType },
+ Operand: var unwrapped
+ }
+ && implicitCastDeclaringType.GetGenericTypeDefinition() is var genericTypeDefinition
+ && (genericTypeDefinition == typeof(Span<>) || genericTypeDefinition == typeof(ReadOnlySpan<>)))
+ {
+ result = unwrapped;
+ return true;
+ }
+
+ result = null;
+ return false;
+ }
}
private void TranslateContains(Expression source, Expression item)
diff --git a/dotnet/src/VectorData/Weaviate/WeaviateFilterTranslator.cs b/dotnet/src/VectorData/Weaviate/WeaviateFilterTranslator.cs
index 4d2f1177ba4e..610f77869b9b 100644
--- a/dotnet/src/VectorData/Weaviate/WeaviateFilterTranslator.cs
+++ b/dotnet/src/VectorData/Weaviate/WeaviateFilterTranslator.cs
@@ -213,9 +213,42 @@ private void TranslateMethodCall(MethodCallExpression methodCall)
this.TranslateContains(source, item);
return;
+ // C# 14 made changes to overload resolution to prefer Span-based overloads when those exist ("first-class spans");
+ // this makes MemoryExtensions.Contains() be resolved rather than Enumerable.Contains() (see above).
+ // MemoryExtensions.Contains() also accepts a Span argument for the source, adding an implicit cast we need to remove.
+ // See https://github.com/dotnet/runtime/issues/109757 for more context.
+ // Note that MemoryExtensions.Contains has an optional 3rd ComparisonType parameter; we only match when
+ // it's null.
+ case { Method.Name: nameof(MemoryExtensions.Contains), Arguments: [var spanArg, var item, ..] } contains
+ when contains.Method.DeclaringType == typeof(MemoryExtensions)
+ && (contains.Arguments.Count is 2
+ || (contains.Arguments.Count is 3 && contains.Arguments[2] is ConstantExpression { Value: null }))
+ && TryUnwrapSpanImplicitCast(spanArg, out var source):
+ this.TranslateContains(source, item);
+ return;
+
default:
throw new NotSupportedException($"Unsupported method call: {methodCall.Method.DeclaringType?.Name}.{methodCall.Method.Name}");
}
+
+ static bool TryUnwrapSpanImplicitCast(Expression expression, [NotNullWhen(true)] out Expression? result)
+ {
+ if (expression is UnaryExpression
+ {
+ NodeType: ExpressionType.Convert,
+ Method: { Name: "op_Implicit", DeclaringType: { IsGenericType: true } implicitCastDeclaringType },
+ Operand: var unwrapped
+ }
+ && implicitCastDeclaringType.GetGenericTypeDefinition() is var genericTypeDefinition
+ && (genericTypeDefinition == typeof(Span<>) || genericTypeDefinition == typeof(ReadOnlySpan<>)))
+ {
+ result = unwrapped;
+ return true;
+ }
+
+ result = null;
+ return false;
+ }
}
private void TranslateContains(Expression source, Expression item)
diff --git a/dotnet/test/VectorData/Directory.Build.props b/dotnet/test/VectorData/Directory.Build.props
index e58e3eb681b5..1553ed4cb332 100644
--- a/dotnet/test/VectorData/Directory.Build.props
+++ b/dotnet/test/VectorData/Directory.Build.props
@@ -3,6 +3,8 @@
+ latest
+
$(NoWarn);MEVD9000,MEVD9001
$(NoWarn);CA1515
$(NoWarn);CA1707
diff --git a/dotnet/test/VectorData/Redis.ConformanceTests/Filter/RedisBasicFilterTests.cs b/dotnet/test/VectorData/Redis.ConformanceTests/Filter/RedisBasicFilterTests.cs
index 80efcf42978b..0f144cdae20c 100644
--- a/dotnet/test/VectorData/Redis.ConformanceTests/Filter/RedisBasicFilterTests.cs
+++ b/dotnet/test/VectorData/Redis.ConformanceTests/Filter/RedisBasicFilterTests.cs
@@ -123,6 +123,19 @@ public override Task Contains_over_field_string_array()
public override Task Contains_over_field_string_List()
=> Assert.ThrowsAsync(() => base.Contains_over_field_string_List());
+ public override Task Contains_with_Enumerable_Contains()
+ => Assert.ThrowsAsync(() => base.Contains_with_Enumerable_Contains());
+
+#if !NETFRAMEWORK
+ public override Task Contains_with_MemoryExtensions_Contains()
+ => Assert.ThrowsAsync(() => base.Contains_with_MemoryExtensions_Contains());
+#endif
+
+#if NET10_0_OR_GREATER
+ public override Task Contains_with_MemoryExtensions_Contains_with_null_comparer()
+ => Assert.ThrowsAsync(() => base.Contains_with_MemoryExtensions_Contains_with_null_comparer());
+#endif
+
[Obsolete("Legacy filter support")]
public override Task Legacy_AnyTagEqualTo_array()
=> Assert.ThrowsAsync(() => base.Legacy_AnyTagEqualTo_array());
diff --git a/dotnet/test/VectorData/Redis.ConformanceTests/Filter/RedisBasicQueryTests.cs b/dotnet/test/VectorData/Redis.ConformanceTests/Filter/RedisBasicQueryTests.cs
index 9eb77d58ee71..9cf5eac40e14 100644
--- a/dotnet/test/VectorData/Redis.ConformanceTests/Filter/RedisBasicQueryTests.cs
+++ b/dotnet/test/VectorData/Redis.ConformanceTests/Filter/RedisBasicQueryTests.cs
@@ -123,6 +123,19 @@ public override Task Contains_over_field_string_array()
public override Task Contains_over_field_string_List()
=> Assert.ThrowsAsync(() => base.Contains_over_field_string_List());
+ public override Task Contains_with_Enumerable_Contains()
+ => Assert.ThrowsAsync(() => base.Contains_with_Enumerable_Contains());
+
+#if !NETFRAMEWORK
+ public override Task Contains_with_MemoryExtensions_Contains()
+ => Assert.ThrowsAsync(() => base.Contains_with_MemoryExtensions_Contains());
+#endif
+
+#if NET10_0_OR_GREATER
+ public override Task Contains_with_MemoryExtensions_Contains_with_null_comparer()
+ => Assert.ThrowsAsync(() => base.Contains_with_MemoryExtensions_Contains_with_null_comparer());
+#endif
+
public new class Fixture : BasicQueryTests.QueryFixture
{
public override TestStore TestStore => RedisTestStore.HashSetInstance;
diff --git a/dotnet/test/VectorData/SqlServer.ConformanceTests/Filter/SqlServerBasicFilterTests.cs b/dotnet/test/VectorData/SqlServer.ConformanceTests/Filter/SqlServerBasicFilterTests.cs
index aa065e5104b6..d6656cf9d2a4 100644
--- a/dotnet/test/VectorData/SqlServer.ConformanceTests/Filter/SqlServerBasicFilterTests.cs
+++ b/dotnet/test/VectorData/SqlServer.ConformanceTests/Filter/SqlServerBasicFilterTests.cs
@@ -42,6 +42,19 @@ public override Task Contains_over_field_string_array()
public override Task Contains_over_field_string_List()
=> Assert.ThrowsAsync(() => base.Contains_over_field_string_List());
+ public override Task Contains_with_Enumerable_Contains()
+ => Assert.ThrowsAsync(() => base.Contains_with_Enumerable_Contains());
+
+#if !NETFRAMEWORK
+ public override Task Contains_with_MemoryExtensions_Contains()
+ => Assert.ThrowsAsync(() => base.Contains_with_MemoryExtensions_Contains());
+#endif
+
+#if NET10_0_OR_GREATER
+ public override Task Contains_with_MemoryExtensions_Contains_with_null_comparer()
+ => Assert.ThrowsAsync(() => base.Contains_with_MemoryExtensions_Contains_with_null_comparer());
+#endif
+
[Fact(Skip = "Not supported")]
[Obsolete("Legacy filters are not supported")]
public override Task Legacy_And() => throw new NotSupportedException();
diff --git a/dotnet/test/VectorData/SqlServer.ConformanceTests/Filter/SqlServerBasicQueryTests.cs b/dotnet/test/VectorData/SqlServer.ConformanceTests/Filter/SqlServerBasicQueryTests.cs
index 866b7fe0da26..b3c1acc13f60 100644
--- a/dotnet/test/VectorData/SqlServer.ConformanceTests/Filter/SqlServerBasicQueryTests.cs
+++ b/dotnet/test/VectorData/SqlServer.ConformanceTests/Filter/SqlServerBasicQueryTests.cs
@@ -42,6 +42,19 @@ public override Task Contains_over_field_string_array()
public override Task Contains_over_field_string_List()
=> Assert.ThrowsAsync(() => base.Contains_over_field_string_List());
+ public override Task Contains_with_Enumerable_Contains()
+ => Assert.ThrowsAsync(() => base.Contains_with_Enumerable_Contains());
+
+#if !NETFRAMEWORK
+ public override Task Contains_with_MemoryExtensions_Contains()
+ => Assert.ThrowsAsync(() => base.Contains_with_MemoryExtensions_Contains());
+#endif
+
+#if NET10_0_OR_GREATER
+ public override Task Contains_with_MemoryExtensions_Contains_with_null_comparer()
+ => Assert.ThrowsAsync(() => base.Contains_with_MemoryExtensions_Contains_with_null_comparer());
+#endif
+
public new class Fixture : BasicQueryTests.QueryFixture
{
private static readonly string s_uniqueName = Guid.NewGuid().ToString();
diff --git a/dotnet/test/VectorData/VectorData.ConformanceTests/Filter/BasicFilterTests.cs b/dotnet/test/VectorData/VectorData.ConformanceTests/Filter/BasicFilterTests.cs
index b429059b73b2..2bded06ec7e1 100644
--- a/dotnet/test/VectorData/VectorData.ConformanceTests/Filter/BasicFilterTests.cs
+++ b/dotnet/test/VectorData/VectorData.ConformanceTests/Filter/BasicFilterTests.cs
@@ -333,6 +333,37 @@ public virtual Task Contains_over_captured_string_array()
r => array.Contains(r["String"]));
}
+#pragma warning disable RCS1196 // Call extension method as instance method
+
+ // C# 14 made changes to overload resolution to prefer Span-based overloads when those exist ("first-class spans");
+ // this makes MemoryExtensions.Contains() be resolved rather than Enumerable.Contains() (see above).
+ // See https://github.com/dotnet/runtime/issues/109757 for more context.
+ // The following tests the various Contains variants directly, without using extension syntax, to ensure everything's
+ // properly supported.
+ [ConditionalFact]
+ public virtual Task Contains_with_Enumerable_Contains()
+ => this.TestFilterAsync(
+ r => Enumerable.Contains(r.StringArray, "x"),
+ r => ((string[])r["StringArray"]!).Contains("x"));
+
+#if !NETFRAMEWORK
+ [ConditionalFact]
+ public virtual Task Contains_with_MemoryExtensions_Contains()
+ => this.TestFilterAsync(
+ r => MemoryExtensions.Contains(r.StringArray, "x"),
+ r => ((string[])r["StringArray"]!).Contains("x"));
+#endif
+
+#if NET10_0_OR_GREATER
+ [ConditionalFact]
+ public virtual Task Contains_with_MemoryExtensions_Contains_with_null_comparer()
+ => this.TestFilterAsync(
+ r => MemoryExtensions.Contains(r.StringArray, "x", comparer: null),
+ r => ((string[])r["StringArray"]!).Contains("x"));
+#endif
+
+#pragma warning restore RCS1196 // Call extension method as instance method
+
#endregion Contains
#region Variable types