Skip to content

feat: net8.0 target, MemoryExtensions.Contains support in SQL translator#76

Open
korchak-aleksandr wants to merge 11 commits intomainfrom
korchak/net8-and-memory-extensions-contains
Open

feat: net8.0 target, MemoryExtensions.Contains support in SQL translator#76
korchak-aleksandr wants to merge 11 commits intomainfrom
korchak/net8-and-memory-extensions-contains

Conversation

@korchak-aleksandr
Copy link
Collaborator

@korchak-aleksandr korchak-aleksandr commented Mar 23, 2026

Summary

Main fix: MemoryExtensions.Contains support in QueryConverter

In .NET 10, array.Contains(value) in LINQ expression trees compiles to MemoryExtensions.Contains(ReadOnlySpan<T>.op_Implicit(array), value) instead of Enumerable.Contains(array, value). This caused NotSupportedException when LINQ-to-SQL tried to translate such queries.

Added handling in QueryConverter.VisitMethodCall to recognize this pattern and translate it to a SQL IN clause — the same way Enumerable.Contains is handled:

else if (mc.Method.DeclaringType == typeof(MemoryExtensions)
    && mc.Method.Name == "Contains"
    && mc.Arguments[0] is MethodCallExpression spanConversion
    && spanConversion.Method.ReturnType is { IsGenericType: true } returnType
    && returnType.GetGenericTypeDefinition() == typeof(ReadOnlySpan<>)
    && spanConversion.Arguments.Count == 1) {
    return this.VisitContains(spanConversion.Arguments[0], mc.Arguments[1]);
}

The check matches any conversion to ReadOnlySpan<T> by return type rather than by method name (op_Implicit), making it robust against future compiler changes.

Infrastructure

  • Added Directory.Build.props: TargetFramework=net8.0, LangVersion=12
  • Added Directory.Packages.props: centralized package versions
  • Added README.md

Package updates

Package Before After
TargetFramework netstandard2.0 net8.0
PackageVersion 10.7.2 10.8.0
Microsoft.Data.SqlClient 5.1.0 6.1.4
Mindbox.Expressions 3.3.0 3.3.1
Microsoft.NET.Test.Sdk 17.4.1 18.3.0
MSTest.TestAdapter/Framework 3.0.2 3.11.1
Moq 4.18.4 4.20.72
Removed BCL packages System.Reflection.Emit, System.Runtime, System.Runtime.Extensions

System.Security.Permissions kept (required for SecurityUtils.cs); SYSLIB0003 and SYSLIB0011 suppressed — CAS is a no-op on .NET 5+ and BinaryFormatter is a legacy path.

Tests

  • Tests now multi-target net8.0;net10.0 — same suite runs on both runtimes
  • Added ArrayContainsTranslationTests with two cases:
    • ArrayContains_TranslatesToSqlIn — verifies WHERE Id IN (@p0, @p1, @p2) on both .NET 8 and .NET 10
    • ArrayContains_EmptyArray_TranslatesToFalse — verifies WHERE 0 = 1
  • CI updated to install both .NET 8 and .NET 10 SDKs

Test plan

  • dotnet build — 0 errors, 0 warnings
  • dotnet test62 passed, 17 skipped, 0 failed (net8.0 + net10.0)

korchak-aleksandr and others added 8 commits March 23, 2026 13:00
- TargetFramework: netstandard2.0 → net8.0
- LangVersion: latest → 12
- PackageVersion: 10.7.2 → 10.8.0
- Microsoft.Data.SqlClient: 5.1.0 → 6.1.4
- Removed BCL packages: System.Reflection.Emit, System.Runtime,
  System.Runtime.Extensions, System.ComponentModel.Annotations,
  System.Security.Permissions
- SecurityUtils: removed CAS (ReflectionPermission) — no-op on .NET 5+
- SYSLIB0011 (BinaryFormatter): suppressed — legacy DB serialization path
- Tests: updated to net8.0, suppressed CS0169

QueryConverter: support MemoryExtensions.Contains from .NET 10
In .NET 10, array.Contains(value) in expression trees compiles to
MemoryExtensions.Contains(ReadOnlySpan<T>.op_Implicit(array), value)
instead of Enumerable.Contains(array, value).
Added handling to translate it to SQL IN clause the same way.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…fix CI

- Add Directory.Build.props: TargetFramework=net8.0, LangVersion=12
- Add Directory.Packages.props: centralized package versions
- Restore original SecurityUtils.cs (add System.Security.Permissions 8.0.0
  package; suppress SYSLIB0003 — CAS is a no-op on .NET 5+, types still
  compile with the compat package)
- Simplify csproj files: remove properties now in Directory.Build.props
- Update pull-request.yml: setup-dotnet@v4, dotnet-version 8.0.x
- Microsoft.NET.Test.Sdk: 17.4.1 → 18.3.0
- MSTest.TestAdapter/TestFramework: 3.0.2 → 3.11.1
- Moq: 4.18.4 → 4.20.72
Tests verify that array.Contains(value) in LINQ queries translates
to SQL IN clause on both net8.0 and net10.0. On .NET 10, the compiler
generates MemoryExtensions.Contains — the fix in QueryConverter ensures
it produces identical SQL to Enumerable.Contains on .NET 8.

Also update CI to install both .NET 8 and .NET 10 SDKs.
- Add <TargetFramework /> to clear Directory.Build.props default
- Set AppendTargetFrameworkToOutputPath=true to avoid DLL overwrites
More robust: matches any conversion to ReadOnlySpan<T>, not just the
specific op_Implicit generated by the current C# compiler.
…ork per project

- Directory.Build.props: LangVersion, Company, Authors, Copyright, PackageLicenseExpression
- Each csproj now declares its own TargetFramework/TargetFrameworks
- Remove duplicate Company/Authors/Copyright from individual csproj files
- Remove leftover Product/NeutralLanguage from test csproj
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants