Skip to content

bytea/byte[] .Length > 0 query fails after rewrite to Any() in EF Core 9/10 #3816

@georg-jung

Description

@georg-jung

Hey @roji 👋🏼 - me again, upgrading from EF Core 8 to 10 😅

Bug description

I noticed the following behavior when upgrading an app from EF Core 8 to EF Core 9/10.

A query against a byte[] property mapped to PostgreSQL bytea works in EF Core 8:

var postId = await db.BlogPosts.AsNoTracking()
    .Where(x => x.AttachmentData != null && x.AttachmentData.Length > 0)
    .Select(x => x.Id)
    .FirstAsync();

In EF Core 9/10, this fails during query translation.

It looks like EF Core rewrites:

x.AttachmentData.Length > 0

to:

x.AttachmentData.Any()

This rewrite works for real collection/array columns, such as PostgreSQL integer[], but not for byte[] mapped to PostgreSQL bytea.

Expected behavior

.Length > 0 on a byte[] property mapped to PostgreSQL bytea translates successfully, as it did in EF Core 8.

In EF Core 8 this was translated as length(b."AttachmentData") > 0.

Other options could be:

  • octet_length("AttachmentData") > 0
  • "AttachmentData" <> '\x'

Actual behavior

EF Core 9/10 rewrites the expression so that the query fails with an error similar to:

The LINQ expression 'DbSet<BlogPost>()
    .Where(b => b.AttachmentData != null && b.AttachmentData
        .Any())' could not be translated. Additional information: Translation of method 'System.Linq.Enumerable.Any' failed.

Notes

  • EF Core 8: works
  • EF Core 9/10: fails
  • The same .Length > 0 pattern works for int[], which is mapped as a PostgreSQL array

Possible workaround

Compare against an empty byte array:

.Where(x => x.AttachmentData != null && x.AttachmentData != Array.Empty<byte>())

which translates to

WHERE "AttachmentData" <> '\x'

Your code

#!/usr/bin/env dotnet
#:property TargetFramework=net10.0
#:property PublishAot=false
// #:package Microsoft.EntityFrameworkCore@8.*
// #:package Npgsql.EntityFrameworkCore.PostgreSQL@8.*
// #:package Microsoft.EntityFrameworkCore@9.*
// #:package Npgsql.EntityFrameworkCore.PostgreSQL@9.*
#:package Microsoft.EntityFrameworkCore@10.*
#:package Npgsql.EntityFrameworkCore.PostgreSQL@10.*

using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;

// ── Repro ───────────────────────────────────────────────────────────────────────
// Usage:  dotnet run -- "<connection-string>"
// Example connection string: "Host=localhost;Database=mwe_bytea;Username=postgres;Password=postgres"

Console.WriteLine($"Runtime:  {Environment.Version}");
Console.WriteLine($"EF Core:  {typeof(DbContext).Assembly.GetName().Version}");
Console.WriteLine($"Provider: {typeof(NpgsqlDbContextOptionsBuilderExtensions).Assembly.GetName().Version}");

var connectionString = args.Length > 0
    ? args[0]
    : "Host=localhost;Database=mwe_bytea;Username=postgres;Password=postgres";

var optionsBuilder = new DbContextOptionsBuilder<BlogDbContext>();
optionsBuilder
    .UseNpgsql(connectionString)
    .EnableDetailedErrors()
    .EnableSensitiveDataLogging()
    .LogTo(Console.WriteLine,
        [
            DbLoggerCategory.Query.Name,
            DbLoggerCategory.Database.Command.Name,
            DbLoggerCategory.Infrastructure.Name,
        ],
        LogLevel.Information);

await using var db = new BlogDbContext(optionsBuilder.Options);
await db.Database.EnsureDeletedAsync();
await db.Database.EnsureCreatedAsync();

// Seed a row with attachment data so the query has something to match.
db.BlogPosts.Add(new BlogPost
{
    Title = "Hello World",
    AttachmentData = [0x01, 0x02, 0x03],
    Ratings = [5, 4, 3],
});
await db.SaveChangesAsync();
Console.WriteLine("Seeded a BlogPost with attachment data.");

// ─── Query that FAILS in EF Core 9/10 ───────────────────────────────
// This is the natural way to find rows with non-empty byte[] data.
// EF Core 9/10 internally rewrites  `x.AttachmentData.Length > 0`  to  `x.AttachmentData.Any()`
// which the Npgsql provider cannot translate for `bytea`.
try
{
    Console.WriteLine("\n--- Test 1: .Length > 0 (FAILS in EF Core 9/10) ---");
    var postId1 = await db.BlogPosts.AsNoTracking()
        .Where(x => x.AttachmentData != null && x.AttachmentData.Length > 0)
        .Select(x => x.Id)
        .FirstAsync();
    Console.WriteLine($"Found BlogPost with id={postId1} (unexpected success — are you on EF Core 8?)");
}
catch (Exception ex)
{
    Console.ForegroundColor = ConsoleColor.Red;
    Console.WriteLine($"FAILED as expected:\n{ex}");
    Console.ResetColor();
}

// ─── Workaround: Compare against empty array ────────────────────────────────────
// bytea comparison with empty value:  WHERE "AttachmentData" <> '\x'
Console.WriteLine("\n--- Test 2: Compare != Array.Empty<byte>() (workaround) ---");
var postId2 = await db.BlogPosts.AsNoTracking()
    .Where(x => x.AttachmentData != null && x.AttachmentData != Array.Empty<byte>())
    .Select(x => x.Id)
    .FirstAsync();
Console.WriteLine($"Found BlogPost with id={postId2}  ✓");

Console.WriteLine("\n--- Test 3: .Length > 0 on int[] ---");
var postId3 = await db.BlogPosts.AsNoTracking()
    .Where(x => x.Ratings != null && x.Ratings.Length > 0)
    .Select(x => x.Id)
    .FirstAsync();
Console.WriteLine($"Found BlogPost with id={postId3}  ✓");

Console.WriteLine("\nDone.");

public sealed class BlogPost
{
    public int Id { get; set; }
    public string Title { get; set; } = "";

    /// <summary>byte[] → PostgreSQL `bytea`. NOT a PG array.</summary>
    public byte[]? AttachmentData { get; set; }

    public int[]? Ratings { get; set; }
}

public sealed class BlogDbContext(DbContextOptions<BlogDbContext> options)
    : DbContext(options)
{
    public DbSet<BlogPost> BlogPosts { get; set; }
}

Stack traces / stdout

EF Core 8 - works

Runtime:  10.0.6
EF Core:  8.0.26.0
Provider: 8.0.11.0
warn: 24.04.2026 17:23:30.291 CoreEventId.SensitiveDataLoggingEnabledWarning[10400] (Microsoft.EntityFrameworkCore.Infrastructure) 
      Sensitive data logging is enabled. Log entries and exception messages may include sensitive application data; this mode should only be enabled during development.
info: 24.04.2026 17:23:30.499 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command) 
      Executed DbCommand (28ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      DROP DATABASE mwe_bytea WITH (FORCE);
info: 24.04.2026 17:23:30.555 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command) 
      Executed DbCommand (33ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      CREATE DATABASE mwe_bytea;
info: 24.04.2026 17:23:30.611 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command) 
      Executed DbCommand (2ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      CREATE TABLE "BlogPosts" (
          "Id" integer GENERATED BY DEFAULT AS IDENTITY,
          "Title" text NOT NULL,
          "AttachmentData" bytea,
          "Ratings" integer[],
          CONSTRAINT "PK_BlogPosts" PRIMARY KEY ("Id")
      );
info: 24.04.2026 17:23:30.733 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command) 
      Executed DbCommand (44ms) [Parameters=[@p0='0x010203', @p1={ '5', '4', '3' } (DbType = Object), @p2='Hello World' (Nullable = false)], CommandType='Text', CommandTimeout='30']
      INSERT INTO "BlogPosts" ("AttachmentData", "Ratings", "Title")
      VALUES (@p0, @p1, @p2)
      RETURNING "Id";
Seeded a BlogPost with attachment data.

--- Test 1: .Length > 0 (FAILS in EF Core 9/10) ---
info: 24.04.2026 17:23:30.844 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command) 
      Executed DbCommand (1ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      SELECT b."Id"
      FROM "BlogPosts" AS b
      WHERE b."AttachmentData" IS NOT NULL AND length(b."AttachmentData") > 0
      LIMIT 1
Found BlogPost with id=1 (unexpected success — are you on EF Core 8?)

--- Test 2: Compare != Array.Empty<byte>() (workaround) ---
info: 24.04.2026 17:23:30.854 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command) 
      Executed DbCommand (1ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      SELECT b."Id"
      FROM "BlogPosts" AS b
      WHERE b."AttachmentData" IS NOT NULL AND b."AttachmentData" <> BYTEA E'\\x'
      LIMIT 1
Found BlogPost with id=1  ✓

--- Test 3: .Length > 0 on int[] ---
info: 24.04.2026 17:23:30.868 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command) 
      Executed DbCommand (1ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      SELECT b."Id"
      FROM "BlogPosts" AS b
      WHERE b."Ratings" IS NOT NULL AND cardinality(b."Ratings") > 0
      LIMIT 1
Found BlogPost with id=1  ✓

Done.

EF Core 10 - fails

Runtime:  10.0.6
EF Core:  10.0.7.0
Provider: 10.0.1.0
warn: 24.04.2026 17:25:41.778 CoreEventId.SensitiveDataLoggingEnabledWarning[10400] (Microsoft.EntityFrameworkCore.Infrastructure) 
      Sensitive data logging is enabled. Log entries and exception messages may include sensitive application data; this mode should only be enabled during development.
info: 24.04.2026 17:25:41.999 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command) 
      Executed DbCommand (13ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      SELECT 1
info: 24.04.2026 17:25:42.093 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command) 
      Executed DbCommand (21ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      DROP DATABASE mwe_bytea WITH (FORCE);
info: 24.04.2026 17:25:42.249 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command) 
      Executed DbCommand (36ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      CREATE DATABASE mwe_bytea;
info: 24.04.2026 17:25:42.308 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command) 
      Executed DbCommand (1ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      SELECT 1
info: 24.04.2026 17:25:42.365 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command) 
      Executed DbCommand (3ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      CREATE TABLE "BlogPosts" (
          "Id" integer GENERATED BY DEFAULT AS IDENTITY,
          "Title" text NOT NULL,
          "AttachmentData" bytea,
          "Ratings" integer[],
          CONSTRAINT "PK_BlogPosts" PRIMARY KEY ("Id")
      );
info: 24.04.2026 17:25:42.493 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command) 
      Executed DbCommand (45ms) [Parameters=[@p0='0x010203', @p1={ '5', '4', '3' } (DbType = Object), @p2='Hello World' (Nullable = false)], CommandType='Text', CommandTimeout='30']
      INSERT INTO "BlogPosts" ("AttachmentData", "Ratings", "Title")
      VALUES (@p0, @p1, @p2)
      RETURNING "Id";
Seeded a BlogPost with attachment data.

--- Test 1: .Length > 0 (FAILS in EF Core 9/10) ---
FAILED as expected:
System.InvalidOperationException: The LINQ expression 'DbSet<BlogPost>()
    .Where(b => b.AttachmentData != null && b.AttachmentData
        .Any())' could not be translated. Additional information: Translation of method 'System.Linq.Enumerable.Any' failed. If this method can be mapped to your custom function, see https://go.microsoft.com/fwlink/?linkid=2132413 for more information. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to 'AsEnumerable', 'AsAsyncEnumerable', 'ToList', or 'ToListAsync'. See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.
   at Microsoft.EntityFrameworkCore.Query.QueryableMethodTranslatingExpressionVisitor.Translate(Expression expression)
   at Microsoft.EntityFrameworkCore.Query.QueryCompilationContext.CreateQueryExecutorExpression[TResult](Expression query)
   at Microsoft.EntityFrameworkCore.Query.QueryCompilationContext.CreateQueryExecutor[TResult](Expression query)
   at Microsoft.EntityFrameworkCore.Storage.Database.CompileQuery[TResult](Expression query, Boolean async)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.CompileQueryCore[TResult](IDatabase database, Expression query, IModel model, Boolean async)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.<>c__DisplayClass11_0`1.<ExecuteCore>b__0()
   at Microsoft.EntityFrameworkCore.Query.Internal.CompiledQueryCache.GetOrAddQuery[TResult](Object cacheKey, Func`1 compiler)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.ExecuteCore[TResult](Expression query, Boolean async, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.ExecuteAsync[TResult](Expression query, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryProvider.ExecuteAsync[TResult](Expression expression, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ExecuteAsync[TSource,TResult](MethodInfo operatorMethodInfo, IQueryable`1 source, Expression expression, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ExecuteAsync[TSource,TResult](MethodInfo operatorMethodInfo, IQueryable`1 source, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.FirstAsync[TSource](IQueryable`1 source, CancellationToken cancellationToken)
   at Program.<Main>$(String[] args) in D:\git\mwe-efcore-bytea\Program.cs:line 60

--- Test 2: Compare != Array.Empty<byte>() (workaround) ---
info: 24.04.2026 17:25:42.675 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command) 
      Executed DbCommand (1ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      SELECT b."Id"
      FROM "BlogPosts" AS b
      WHERE b."AttachmentData" IS NOT NULL AND b."AttachmentData" <> BYTEA E'\\x'
      LIMIT 1
Found BlogPost with id=1  ✓

--- Test 3: .Length > 0 on int[] ---
info: 24.04.2026 17:25:42.687 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command) 
      Executed DbCommand (1ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      SELECT b."Id"
      FROM "BlogPosts" AS b
      WHERE b."Ratings" IS NOT NULL AND cardinality(b."Ratings") > 0
      LIMIT 1
Found BlogPost with id=1  ✓

Done.

Related

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions