diff --git a/Mindbox.Data.Linq.Tests/SqlGeneration/ArrayContainsTranslationTests.cs b/Mindbox.Data.Linq.Tests/SqlGeneration/ArrayContainsTranslationTests.cs
deleted file mode 100644
index 74d4c85..0000000
--- a/Mindbox.Data.Linq.Tests/SqlGeneration/ArrayContainsTranslationTests.cs
+++ /dev/null
@@ -1,112 +0,0 @@
-using System;
-using System.Data.Linq;
-using System.Linq;
-using System.Linq.Expressions;
-using System.Reflection;
-using Microsoft.VisualStudio.TestTools.UnitTesting;
-
-namespace Mindbox.Data.Linq.Tests
-{
- ///
- /// Tests that array.Contains(value) in LINQ queries is correctly translated to SQL IN clause.
- /// In .NET 10, the C# compiler generates MemoryExtensions.Contains(ReadOnlySpan, value)
- /// for this pattern instead of Enumerable.Contains — this must produce the same SQL.
- /// Two compiler patterns exist:
- /// 1. MethodCallExpression(op_Implicit, array) — simple closure capture
- /// 2. InvocationExpression(ConstantExpression(delegate), []) — pre-compiled closure
- /// Both must be handled.
- ///
- [TestClass]
- public class ArrayContainsTranslationTests
- {
- [TestMethod]
- public void ArrayContains_TranslatesToSqlIn()
- {
- var ids = new[] { 1, 2, 3 };
-
- using var connection = new DbConnectionStub();
- using var context = new DataContext(connection);
-
- var query = context.GetTable().Where(t => ids.Contains(t.Id));
-
- using var command = context.GetCommand(query);
-
- Assert.AreEqual(
- "SELECT [t0].[Id], [t0].[Discriminator], [t0].[X]" + Environment.NewLine +
- "FROM [SimpleTable] AS [t0]" + Environment.NewLine +
- "WHERE [t0].[Id] IN (@p0, @p1, @p2)",
- command.CommandText);
- }
-
- ///
- /// Directly constructs the InvocationExpression(ConstantExpression(delegate), []) pattern
- /// using Expression API — guaranteed to exercise Pattern 2 regardless of compiler version.
- /// The MemoryExtensions.Contains call is built by hand with a pre-compiled zero-arg delegate
- /// as the span argument, matching exactly what .NET 10 generates in nested closure contexts.
- ///
- [TestMethod]
- public void ArrayContains_InvocationExpressionPattern_TranslatesToSqlIn()
- {
- var ids = new[] { 1, 2, 3 };
-
- // Build MemoryExtensions.Contains(ReadOnlySpan, int) method
- var containsMethod = typeof(MemoryExtensions)
- .GetMethods(BindingFlags.Public | BindingFlags.Static)
- .Single(m => m.Name == "Contains"
- && m.IsGenericMethod
- && m.GetParameters() is [{ ParameterType: var p0 }, { ParameterType: { IsGenericParameter: true } }]
- && p0.IsGenericType
- && p0.GetGenericTypeDefinition() == typeof(ReadOnlySpan<>))
- .MakeGenericMethod(typeof(int));
-
- // Build op_Implicit: int[] → ReadOnlySpan
- var opImplicit = typeof(ReadOnlySpan)
- .GetMethod("op_Implicit", new[] { typeof(int[]) });
-
- // Pattern 2: the compiler pre-compiles the implicit conversion into a zero-arg delegate
- // InvocationExpression(ConstantExpression(Func>), [])
- // Build: Func> spanFactory = () => op_Implicit(ids)
- var spanFactoryBody = Expression.Call(opImplicit, Expression.Constant(ids));
- var spanFactory = Expression.Lambda(spanFactoryBody).Compile();
- var spanExpr = Expression.Invoke(Expression.Constant(spanFactory));
-
- // Build: WHERE t.Id IN (ids) via MemoryExtensions.Contains(spanExpr, t.Id)
- var tParam = Expression.Parameter(typeof(SimpleEntity), "t");
- var idMember = Expression.Property(tParam, nameof(SimpleEntity.Id));
- var containsCall = Expression.Call(containsMethod, spanExpr, idMember);
- var predicate = Expression.Lambda>(containsCall, tParam);
-
- using var connection = new DbConnectionStub();
- using var context = new DataContext(connection);
-
- var query = context.GetTable().Where(predicate);
-
- using var command = context.GetCommand(query);
-
- Assert.AreEqual(
- "SELECT [t0].[Id], [t0].[Discriminator], [t0].[X]" + Environment.NewLine +
- "FROM [SimpleTable] AS [t0]" + Environment.NewLine +
- "WHERE [t0].[Id] IN (@p0, @p1, @p2)",
- command.CommandText);
- }
-
- [TestMethod]
- public void ArrayContains_EmptyArray_TranslatesToFalse()
- {
- var ids = Array.Empty();
-
- using var connection = new DbConnectionStub();
- using var context = new DataContext(connection);
-
- var query = context.GetTable().Where(t => ids.Contains(t.Id));
-
- using var command = context.GetCommand(query);
-
- Assert.AreEqual(
- "SELECT [t0].[Id], [t0].[Discriminator], [t0].[X]" + Environment.NewLine +
- "FROM [SimpleTable] AS [t0]" + Environment.NewLine +
- "WHERE 0 = 1",
- command.CommandText);
- }
- }
-}
diff --git a/Mindbox.Data.Linq/Mindbox.Data.Linq.csproj b/Mindbox.Data.Linq/Mindbox.Data.Linq.csproj
index fc5788e..df1aeb4 100644
--- a/Mindbox.Data.Linq/Mindbox.Data.Linq.csproj
+++ b/Mindbox.Data.Linq/Mindbox.Data.Linq.csproj
@@ -8,7 +8,7 @@
true
true
snupkg
- 10.8.1$(VersionTag)
+ 10.8.2$(VersionTag)
SYSLIB0003;SYSLIB0011
diff --git a/Mindbox.Data.Linq/SqlClient/Query/QueryConverter.cs b/Mindbox.Data.Linq/SqlClient/Query/QueryConverter.cs
index 557a3a2..bf2f79d 100644
--- a/Mindbox.Data.Linq/SqlClient/Query/QueryConverter.cs
+++ b/Mindbox.Data.Linq/SqlClient/Query/QueryConverter.cs
@@ -1774,54 +1774,6 @@ private SqlExpression GetAggregate(SqlNodeType aggType, Type clrType, SqlExpress
return new SqlUnary(aggType, clrType, sqlType, exp, this.dominatingExpression);
}
- ///
- /// Searches for an array of the given type in a delegate's closure.
- /// Handles direct T[] fields (display class) and Object[] fields (runtime Closure),
- /// including delegates nested inside Object[] fields (up to depth 3).
- ///
- private static object FindArrayInClosure(object target, Type arrayType, int depth = 0) {
- if (target == null || depth > 3) {
- return null;
- }
- foreach (var f in target.GetType().GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)) {
- var val = f.GetValue(target);
- if (val?.GetType() == arrayType) {
- return val;
- }
- if (val is object[] items) {
- foreach (var item in items) {
- if (item?.GetType() == arrayType) {
- return item;
- }
- if (item is Delegate nested && nested.Target != null) {
- var found = FindArrayInClosure(nested.Target, arrayType, depth + 1);
- if (found != null) {
- return found;
- }
- }
- }
- }
- }
- return null;
- }
-
- private static Expression TryExtractArrayFromSpanExpression(Expression spanExpr) {
- if (spanExpr is MethodCallExpression { Arguments: [var arrayExpr] }) {
- return arrayExpr;
- }
-
- if (spanExpr is InvocationExpression { Arguments.Count: 0, Expression: ConstantExpression { Value: Delegate { Target: { } target } } }
- && spanExpr.Type.GetGenericArguments() is [var elementType]) {
- var arrayType = elementType.MakeArrayType();
- var array = FindArrayInClosure(target, arrayType);
- if (array != null) {
- return Expression.Constant(array, arrayType);
- }
- }
-
- return null;
- }
-
private SqlNode VisitContains(Expression sequence, Expression value) {
Type elemType = TypeSystem.GetElementType(sequence.Type);
SqlNode seqNode = this.Visit(sequence);
@@ -1927,21 +1879,6 @@ private SqlNode VisitMethodCall(MethodCallExpression mc) {
if (this.IsSequenceOperatorCall(mc)) {
return this.VisitSequenceOperatorCall(mc);
}
- // In .NET 10, array.Contains(value) in expression trees compiles to
- // MemoryExtensions.Contains(ReadOnlySpan span, value).
- // Two compiler patterns exist for the span argument:
- // 1. MethodCallExpression(op_Implicit, [array]) — simple local capture
- // 2. InvocationExpression(ConstantExpression(pre_compiled_delegate), []) — nested closure
- // Both must be unwrapped to extract the underlying array for SQL IN translation.
- else if (mc.Method.DeclaringType == typeof(MemoryExtensions)
- && mc.Method.Name == "Contains"
- && mc.Arguments[0].Type is { IsGenericType: true } spanType
- && spanType.GetGenericTypeDefinition() == typeof(ReadOnlySpan<>)) {
- var arrayExpr = TryExtractArrayFromSpanExpression(mc.Arguments[0]);
- if (arrayExpr != null) {
- return this.VisitContains(arrayExpr, mc.Arguments[1]);
- }
- }
else if (IsDataManipulationCall(mc)) {
return this.VisitDataManipulationCall(mc);
}