Skip to content

Fix NullReferenceException in SqlServerStringMethodTranslator.TranslateIndexOf for casted parameters#37956

Merged
roji merged 3 commits intomainfrom
copilot/fix-nullreferenceexception-indexof
Mar 25, 2026
Merged

Fix NullReferenceException in SqlServerStringMethodTranslator.TranslateIndexOf for casted parameters#37956
roji merged 3 commits intomainfrom
copilot/fix-nullreferenceexception-indexof

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Mar 19, 2026

TranslateIndexOf in the SQL Server provider crashes with a NullReferenceException when ExpressionExtensions.InferTypeMapping() returns null — which occurs when neither the instance nor the search expression has a type mapping assigned, e.g. when using a value-converter property with a double cast:

// Dictionary<string, string> property with value converter to string
.Select(s => ((string)(object)s.Parameters).IndexOf("..."))
// NullReferenceException at stringTypeMapping.StoreType

The crash is SQL Server–specific: TranslateIndexOf dereferences the type mapping to check whether the store type is nvarchar(max)/varchar(max) (to pick bigint vs int as the CHARINDEX return type), while other providers pass null through to ApplyTypeMapping which handles it gracefully.

Changes

  • Fix (SqlServerStringMethodTranslator): Remove the null-forgiving ! from InferTypeMapping and use ?.StoreType — when the type mapping is unknown we fall through to the else branch returning int, a safe default.
  • Tests (StringTranslationsRelationalTestBase): Add IndexOf_with_non_string_column_using_double_cast and Replace_with_non_string_column_using_double_cast tests (the (string)(object)b.Int pattern) with SQL baseline assertions for both SQL Server and SQLite.
Original prompt

This section details on the original issue you should resolve

<issue_title>IndexOf translation throws NullReferenceException for casted parameters</issue_title>
<issue_description>Related #17389

I have a Dictionary<string, string> property with a value converter to a string
And if I have a query like this:

.Select(s => ((string)(object)s.Parameters).IndexOf("..."))

it throws a NullReferenceException:

Object reference not set to an instance of an object.
in Microsoft.EntityFrameworkCore.SqlServer.Query.Internal.SqlServerStringMethodTranslator.TranslateIndexOf(SqlExpression instance, MethodInfo method, SqlExpression searchExpression, SqlExpression startIndex) in /_/src/EFCore.SqlServer/Query/Internal/SqlServerStringMethodTranslator.cs: riga 315

Generally, if ExpressionExtensions.InferTypeMapping returns null here:

var stringTypeMapping = ExpressionExtensions.InferTypeMapping(instance, searchExpression)!;

no default type mapping is created/retrieved, and it's not null-checked</issue_description>

Comments on the Issue (you are @copilot in this section)

@roji # AI Triage

The below is an AI-generated analysis and may contain inaccuracies.

Analysis

Confirmed bug. The NullReferenceException occurs in SqlServerStringMethodTranslator.TranslateIndexOf when ExpressionExtensions.InferTypeMapping() returns null. This happens because neither instance nor searchExpression has a TypeMapping assigned — which can occur when expressions involve casts like ((string)(object)s.Parameters).

At line 223, the null-forgiving operator ! suppresses the compiler warning, but the value is still null at runtime. The crash occurs at line 242 when accessing stringTypeMapping.StoreType.

The same pattern also exists in TranslateReplace() in the same file (line 162), which is likely also vulnerable.

Not a regression — the bug reproduces on both EF Core 8.0.13 and 9.0.3. The SQLite provider does not crash for the same query (it passes null type mapping through to ApplyTypeMapping, which handles it gracefully).

The bug is SQL Server–specific because TranslateIndexOf dereferences the type mapping to check StoreType for nvarchar(max) / varchar(max) to decide between CHARINDEX returning long vs int.

Still reproduces on current main.

Possible duplicates

No exact duplicates found. Related issue: #17389 (same cast pattern with EF.Functions.Like, fixed in 3.1.0).

Minimal repro
using System;
using System.Collections.Generic;
using System.Text.Json;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;

await using var context = new TestContext();
await context.Database.EnsureDeletedAsync();
await context.Database.EnsureCreatedAsync();

var result = await context.Entities
    .Select(e => ((string)(object)e.Parameters).IndexOf("test"))
    .ToListAsync();

Console.WriteLine($"Results: {result.Count}");

public class TestContext : DbContext
{
    public DbSet<MyEntity> Entities { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        => optionsBuilder
            .UseSqlServer(Environment.GetEnvironmentVariable("Test__SqlServer__DefaultConnection"))
            .LogTo(Console.WriteLine, LogLevel.Information)
            .EnableSensitiveDataLogging();

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<MyEntity>()
            .Property(e => e.Parameters)
            .HasConversion(
                v => JsonSerializer.Serialize(v, (JsonSerializerOptions?)null),
                v => JsonSerializer.Deserialize<Dictionary<string, string>>(v, (JsonSerializerOptions?)null)!);
    }
}

public class MyEntity
{
    public int Id { get; set; }
    public Dictionary<string, string> Parameters { get; set; } = new();
}

Throws:

System.NullReferenceException: Object reference not set to an instance of an object.
   at Microsoft.EntityFrameworkCore.SqlServer.Query.Internal.SqlServerStringMethodTranslator.TranslateIndexOf(...)

📍 Connect Copilot coding agent with Jira, Azure Boards or Linear to delegate work to Copilot in one click without leaving your project management tool.

…returns null

Co-authored-by: roji <1862641+roji@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix IndexOf translation NullReferenceException for casted parameters Fix NullReferenceException in SqlServerStringMethodTranslator.TranslateIndexOf for casted parameters Mar 19, 2026
Copilot AI requested a review from roji March 19, 2026 10:25
Copilot finished work on behalf of roji March 19, 2026 10:25
@roji roji marked this pull request as ready for review March 19, 2026 10:50
@roji roji requested a review from a team as a code owner March 19, 2026 10:50
Copilot AI review requested due to automatic review settings March 19, 2026 10:50
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR targets a SQL Server–specific crash in SqlServerStringMethodTranslator.TranslateIndexOf when no type mapping can be inferred (e.g., casted expressions), and adds coverage for double-cast string operations across relational providers.

Changes:

  • Adjust TranslateIndexOf to avoid dereferencing a null-inferred type mapping.
  • Add relational-spec tests for IndexOf/Replace over double-cast non-string columns.
  • Add SQL baselines for the new tests in SQL Server and SQLite functional test suites.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.

File Description
src/EFCore.SqlServer/Query/Internal/Translators/SqlServerStringMethodTranslator.cs Avoids a null dereference when inferred type mapping is missing during IndexOf translation.
test/EFCore.Relational.Specification.Tests/Query/Translations/StringTranslationsRelationalTestBase.cs Introduces new relational-spec test cases for double-cast IndexOf/Replace.
test/EFCore.SqlServer.FunctionalTests/Query/Translations/StringTranslationsSqlServerTest.cs Adds SQL Server baselines for the new relational-spec tests.
test/EFCore.Sqlite.FunctionalTests/Query/Translations/StringTranslationsSqliteTest.cs Adds SQLite baselines for the new relational-spec tests.

You can also share your feedback on Copilot code review. Take the survey.

@roji roji marked this pull request as draft March 19, 2026 11:22
…ping fallback

- Use ApplyDefaultTypeMapping fallback when InferTypeMapping returns null,
  ensuring the nvarchar(max)/varchar(max) branch is correctly taken for
  CHARINDEX return type decisions.
- Parameterize the IndexOf test pattern to force parameterization, which
  exercises the code path where InferTypeMapping returns null.
- Update SQL baselines to match parameterized form.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@roji roji marked this pull request as ready for review March 19, 2026 13:03
Copilot AI review requested due to automatic review settings March 19, 2026 13:03
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes a SQL Server–specific NullReferenceException in SqlServerStringMethodTranslator.TranslateIndexOf when type mapping inference returns null for double-casted non-string expressions, and adds coverage to prevent regressions across providers.

Changes:

  • Ensure TranslateIndexOf always has a non-null string type mapping by falling back to ApplyDefaultTypeMapping(instance).
  • Add relational-spec tests covering IndexOf and Replace over ((string)(object)b.Int) patterns.
  • Add SQL baseline assertions for the new tests in both SQL Server and SQLite functional test suites.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

File Description
src/EFCore.SqlServer/Query/Internal/Translators/SqlServerStringMethodTranslator.cs Prevents NRE in SQL Server IndexOf translation by guaranteeing a type mapping is available.
test/EFCore.Relational.Specification.Tests/Query/Translations/StringTranslationsRelationalTestBase.cs Adds new relational tests for double-cast IndexOf and Replace scenarios.
test/EFCore.SqlServer.FunctionalTests/Query/Translations/StringTranslationsSqlServerTest.cs Adds SQL Server baselines for the new relational tests.
test/EFCore.Sqlite.FunctionalTests/Query/Translations/StringTranslationsSqliteTest.cs Adds SQLite baselines for the new relational tests.
Comments suppressed due to low confidence (1)

src/EFCore.SqlServer/Query/Internal/Translators/SqlServerStringMethodTranslator.cs:228

  • ApplyDefaultTypeMapping(instance) is called only to extract TypeMapping, and then instance is type-mapped again a few lines later. Consider assigning instance = sqlExpressionFactory.ApplyDefaultTypeMapping(instance)!; first and then using instance.TypeMapping! as stringTypeMapping to avoid redundant expression rewriting (and to ensure any nested object-convert unwrapping applies to the instance you later use).
        var stringTypeMapping = ExpressionExtensions.InferTypeMapping(instance, searchExpression)
            ?? sqlExpressionFactory.ApplyDefaultTypeMapping(instance)!.TypeMapping!;
        searchExpression = sqlExpressionFactory.ApplyTypeMapping(
            searchExpression, searchExpression.Type == typeof(char) ? CharTypeMapping.Default : stringTypeMapping);

        instance = sqlExpressionFactory.ApplyTypeMapping(instance, stringTypeMapping);

You can also share your feedback on Copilot code review. Take the survey.

@roji roji enabled auto-merge (squash) March 21, 2026 15:11
@roji roji merged commit 365cbc3 into main Mar 25, 2026
14 checks passed
@roji roji deleted the copilot/fix-nullreferenceexception-indexof branch March 25, 2026 01:45
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.

IndexOf translation throws NullReferenceException for casted parameters

4 participants