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:
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
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 PostgreSQLbyteaworks in EF Core 8:In EF Core 9/10, this fails during query translation.
It looks like EF Core rewrites:
to:
This rewrite works for real collection/array columns, such as PostgreSQL
integer[], but not forbyte[]mapped to PostgreSQLbytea.Expected behavior
.Length > 0on abyte[]property mapped to PostgreSQLbyteatranslates 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:
Notes
.Length > 0pattern works forint[], which is mapped as a PostgreSQL arrayPossible workaround
Compare against an empty byte array:
which translates to
Your code
Stack traces / stdout
EF Core 8 - works
EF Core 10 - fails
Related