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

No fields configured for Bug.

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions