Skip to content

EF.Parameter or setting ParameterTranslationMode.Parameter doesn't work with IEnumerable with nullable type #37204

@karl-sjogren

Description

@karl-sjogren

Bug description

This builds on the problem discovered in #37185. The solution there to go back to the "old" way with a single JSON parameter using EF.Parameter (or setting ParameterTranslationMode.Parameter) doesn't work if you have an IEnumerable with a nullable type as parameter.

We were hoping to use options.UseParameterizedCollectionMode(ParameterTranslationMode.Parameter) to go back to the old way for the whole project to get around #37185 to avoid going through and possibly updating every single query that uses a .Contains() to be sure.

This worked fine in .EFCore 9. So even if the other issue gets a fix this might cause further problems.

Your code

// PS: I love single file apps for this kind of thing

#:package Microsoft.EntityFrameworkCore.SqlServer@10.0.0
#:property PublishAot=false

using Microsoft.EntityFrameworkCore;

List<QueryObj> list = [];

for (int i = 0; i < 1500; i++)
{
    list.Add(new QueryObj { OtherId = i.ToString() });
}

using var dbContext = new BloggingContext();
dbContext.Database.EnsureDeleted();
dbContext.Database.EnsureCreated();

var blogs = await dbContext.Set<Blog>()
    .Where(b => EF.Parameter(list.Select(x => x.OtherId)).Contains(b.OtherId))
    // Using the below line with ToList() works around the issue
    //.Where(b => EF.Parameter(list.Select(x => x.OtherId).ToList()).Contains(b.OtherId))
    .ToListAsync();

class BloggingContext : DbContext
{
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        => optionsBuilder
        .UseSqlServer("Server=(localdb)\\mssqllocaldb;Database=ParamBug;Trusted_Connection=True;")
        .LogTo(Console.WriteLine);

    public DbSet<Blog> Blogs { get; set; }
}

public class Blog
{
    public int BlogId { get; set; }

    public string? OtherId { get; set; }
}

public class QueryObj 
{
    public string? OtherId { get; set; }
}

Stack traces

fail: 2025-11-19 09:03:33.217 CoreEventId.QueryIterationFailed[10100] (Microsoft.EntityFrameworkCore.Query) 
      An exception occurred while iterating over the results of a query for context type 'BloggingContext'.
      System.Diagnostics.UnreachableException: Parameter 'Select' is not an IList.
         at Microsoft.EntityFrameworkCore.Query.SqlNullabilityProcessor.TryMakeNonNullable(SelectExpression selectExpression, SelectExpression& rewrittenSelectExpression, Nullable`1& foundNull)
         at Microsoft.EntityFrameworkCore.Query.SqlNullabilityProcessor.VisitIn(InExpression inExpression, Boolean allowOptimizedExpansion, Boolean& nullable)
         at Microsoft.EntityFrameworkCore.SqlServer.Query.Internal.SqlServerSqlNullabilityProcessor.VisitIn(InExpression inExpression, Boolean allowOptimizedExpansion, Boolean& nullable)
         at Microsoft.EntityFrameworkCore.Query.SqlNullabilityProcessor.Visit(SqlExpression sqlExpression, Boolean allowOptimizedExpansion, Boolean preserveColumnNullabilityInformation, Boolean& nullable)
         at Microsoft.EntityFrameworkCore.Query.SqlNullabilityProcessor.Visit(SqlExpression sqlExpression, Boolean allowOptimizedExpansion, Boolean& nullable)
         at Microsoft.EntityFrameworkCore.Query.SqlNullabilityProcessor.Visit(SelectExpression selectExpression, Boolean visitProjection)
         at Microsoft.EntityFrameworkCore.Query.SqlNullabilityProcessor.VisitExtension(Expression node)
         at Microsoft.EntityFrameworkCore.SqlServer.Query.Internal.SqlServerSqlNullabilityProcessor.VisitExtension(Expression node)
         at Microsoft.EntityFrameworkCore.Query.SqlNullabilityProcessor.Process(Expression queryExpression, ParametersCacheDecorator parametersDecorator)
         at Microsoft.EntityFrameworkCore.SqlServer.Query.Internal.SqlServerSqlNullabilityProcessor.Process(Expression queryExpression, ParametersCacheDecorator parametersDecorator) 
         at Microsoft.EntityFrameworkCore.SqlServer.Query.Internal.SqlServerParameterBasedSqlProcessor.ProcessSqlNullability(Expression selectExpression, ParametersCacheDecorator Decorator)
         at Microsoft.EntityFrameworkCore.Query.RelationalParameterBasedSqlProcessor.Process(Expression queryExpression, ParametersCacheDecorator parametersDecorator)
         at Microsoft.EntityFrameworkCore.SqlServer.Query.Internal.SqlServerParameterBasedSqlProcessor.Process(Expression queryExpression, ParametersCacheDecorator parametersDecorator)
         at Microsoft.EntityFrameworkCore.Query.RelationalParameterBasedSqlProcessor.Process(Expression queryExpression, Dictionary`2 parameters, Boolean& canCache)
         at Microsoft.EntityFrameworkCore.Query.Internal.RelationalCommandCache.GetRelationalCommandTemplate(Dictionary`2 parameters)
         at Microsoft.EntityFrameworkCore.Internal.RelationalCommandResolverExtensions.RentAndPopulateRelationalCommand(RelationalCommandResolver relationalCommandResolver, RelationalQueryContext queryContext)
         at Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable`1.AsyncEnumerator.InitializeReaderAsync(AsyncEnumerator enumerator, CancellationToken cancellationToken)
         at Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal.SqlServerExecutionStrategy.ExecuteAsync[TState,TResult](TState state, Func`4 operation, Func`4 verifySucceeded, CancellationToken cancellationToken)
         at Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable`1.AsyncEnumerator.MoveNextAsync()

Verbose output

dbug: 2025-11-19 09:03:33.091 CoreEventId.QueryCompilationStarting[10111] (Microsoft.EntityFrameworkCore.Query) 
      Compiling query expression:
      'DbSet<Blog>()
          .Where(b => EF.Parameter<IEnumerable<string>>(@Select)
              .Contains(b.OtherId))'
dbug: 2025-11-19 09:03:33.201 CoreEventId.QueryExecutionPlanned[10107] (Microsoft.EntityFrameworkCore.Query) 
      Generated query execution expression:
      'queryContext => SingleQueryingEnumerable.Create<Blog>(
          relationalQueryContext: (RelationalQueryContext)queryContext, 
          relationalCommandResolver: parameters => [LIFTABLE Constant: RelationalCommandCache.QueryExpression(
              Projection Mapping:
                  EmptyProjectionMember -> Dictionary<IPropertyBase, int> { [Property: Blog.BlogId (int) Required PK AfterSave:Throw ValueGenerated.OnAdd, 0], [Property: Blog.OtherId (string), 1] }
              SELECT b.BlogId, b.OtherId
              FROM Blogs AS b
              WHERE b.OtherId IN (
                  SELECT s.value
                  FROM OPENJSON(@Select) WITH (value nvarchar(max) '') AS s)) | Resolver: c => new RelationalCommandCache(
              c.Dependencies.MemoryCache,
              c.RelationalDependencies.QuerySqlGeneratorFactory,
              c.RelationalDependencies.RelationalParameterBasedSqlProcessorFactory,
              Projection Mapping:
                  EmptyProjectionMember -> Dictionary<IPropertyBase, int> { [Property: Blog.BlogId (int) Required PK AfterSave:Throw ValueGenerated.OnAdd, 0], [Property: Blog.OtherId (string), 1] }
              SELECT b.BlogId, b.OtherId
              FROM Blogs AS b
              WHERE b.OtherId IN (
                  SELECT s.value
                  FROM OPENJSON(@Select) WITH (value nvarchar(max) '') AS s),
              False,
              MultipleParameters
          )].GetRelationalCommandTemplate(parameters),
          readerColumns: null,
          shaper: (queryContext, dataReader, resultContext, resultCoordinator) =>
          {
              Blog entity;
              entity =
              {
                  MaterializationContext materializationContext1;
                  IEntityType entityType1;
                  Blog instance1;
                  InternalEntityEntry entry1;
                  bool hasNullKey1;
                  materializationContext1 = new MaterializationContext(
                      [LIFTABLE Constant: ValueBuffer | Resolver: _ => (object)ValueBuffer.Empty],
                      queryContext.Context
                  );
                  instance1 = default(Blog);
                  entry1 = queryContext.TryGetEntry(
                      key: [LIFTABLE Constant: Key: Blog.BlogId PK | Resolver: c => c.Dependencies.Model.FindEntityType("Blog").FindPrimaryKey()],
                      keyValues: new object[]{ (object)dataReader.GetInt32(0) },
                      throwOnNullKey: True,
                      hasNullKey: hasNullKey1);
                  !(hasNullKey1) ? entry1 != default(InternalEntityEntry) ?
                  {
                      entityType1 = entry1.EntityType;
                      return instance1 = (Blog)entry1.Entity;
                  } :
                  {
                      ISnapshot shadowSnapshot1;
                      shadowSnapshot1 = [LIFTABLE Constant: Snapshot | Resolver: _ => Snapshot.Empty];
                      entityType1 = [LIFTABLE Constant: EntityType: Blog | Resolver: namelessParameter{0} => namelessParameter{0}.Dependencies.Model.FindEntityType("Blog")];
                      instance1 = switch (entityType1)
                      {
                          case [LIFTABLE Constant: EntityType: Blog | Resolver: namelessParameter{1} => namelessParameter{1}.Dependencies.Model.FindEntityType("Blog")]:
                              {
                                  return
                                  {
                                      Blog instance;
                                      instance = new Blog();
                                      instance.<BlogId>k__BackingField = dataReader.GetInt32(0);
                                      instance.<OtherId>k__BackingField = dataReader.IsDBNull(1) ? default(string) : dataReader.GetString(1);
                                      (instance is IInjectableService) ? ((IInjectableService)instance).Injected(
                                          context: materializationContext1.Context,
                                          entity: instance,
                                          queryTrackingBehavior: TrackAll,
                                          structuralType: [LIFTABLE Constant: EntityType: Blog | Resolver: namelessParameter{2} => namelessParameter{2}.Dependencies.Model.FindEntityType("Blog")]) : default(void);
                                      return instance;
                                  }}
                          default:
                              default(Blog)
                      }
                      ;
                      entry1 = entityType1 == default(IEntityType) ? default(InternalEntityEntry) : queryContext.StartTracking(
                          entityType: entityType1,
                          entity: instance1,
                          snapshot: shadowSnapshot1);
                      return instance1;
                  } : default(void);
                  return instance1;
              };
              return entity;
          },
          contextType: BloggingContext,
          standAloneStateManager: False,
          detailedErrorsEnabled: False,
          threadSafetyChecksEnabled: True)'

EF Core version

10.0.0

Database provider

Microsoft.EntityFrameworkCore.SqlServer@

Target framework

.NET 10

Operating system

No response

IDE

No response

Metadata

Metadata

Assignees

Type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions