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

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions