Skip to content

Byte array in owned entity mapped to JSON returns different value when using projection #3566

Open

Description

@drapka

Hi, I encountered a problem while working with a byte array in an owned entity mapped to JSON - it looks like the materialization behaves differently when getting the whole entity vs getting only the specific property.
I would like to use this for encrypted columns, below is a simplified example.

Looks similar to dotnet/efcore#33435.

Sample Code

using Microsoft.EntityFrameworkCore;

await using var context = new BloggingContext();
await context.Database.EnsureCreatedAsync();
var blog = new Blog
{
    Posts =
    [
        new Post { Content = "This is the content of the first post."u8.ToArray() }
    ]
};


await context.Blogs.AddAsync(blog);
await context.SaveChangesAsync();

var blogs1 = await context.Blogs.ToListAsync(); 
var blogs2 = await context.Blogs
    .SelectMany(b => b.Posts.Select(p => p.Content))
    .ToListAsync();

var content1 = blogs1[0].Posts[0].Content; 
var content2 = blogs2[0];
var decodedViaSql = await context.Database
    .SqlQueryRaw<byte[]>( "SELECT decode((b.\"Posts\"->0->>'Content'), 'base64') AS \"Value\" FROM \"Blogs\" AS b LIMIT 1")
    .FirstAsync();

Console.WriteLine($"content1 len={content1.Length}, bytes={string.Join(",", content1)}"); 
Console.WriteLine($"content2 len={content2.Length}, bytes={string.Join(",", content2)}"); 
Console.WriteLine($"decodedViaSql len={decodedViaSql.Length}, bytes={string.Join(",", decodedViaSql)}"); 
Console.WriteLine("content1 equals content2: " + content1.SequenceEqual(content2)); 
Console.WriteLine("content1 equals decodedViaSql: " + content1.SequenceEqual(decodedViaSql));
Console.WriteLine("content2 equals decodedViaSql: " + content2.SequenceEqual(decodedViaSql));

public class BloggingContext : DbContext
{
    public DbSet<Blog> Blogs => Set<Blog>();

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        => optionsBuilder.UseNpgsql("Host=localhost;Port=5432;Username=postgres;Password=password;Database=demo");

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Blog>()
            .OwnsMany<Post>(b => b.Posts, b =>
            {
                b.ToJson();
                b.WithOwner();
            });
    }
}

public class Blog
{
    public int BlogId { get; set; }
    public List<Post> Posts { get; set; } = [];
}

public class Post
{
    public required byte[] Content { get; set; }
}

Thanks for looking into this.

Versions:
.Net: 9, 10
Npgsql.EntityFrameworkCore.PostgreSQL: v9.0.4, v10.0.0

Activity

  1. self-assigned this
    on Jul 4, 2025
  2. added theissue type on Jul 4, 2025
  3. added this to the 9.0.5 milestone on Jul 4, 2025
  4. roji commented on Jul 4, 2025

    @roji
    Member

    Thanks for the minimal repro - I can indeed see the bug happening. It also indeed seems PG-specific (does not repro with SQL Server). I'll try to take a look at this soon.

  5. drapka commented on Dec 10, 2025

    @drapka
    Author

    Hi @roji, just a heads up that this still reproduces in EfCore.Pg v10.

    Looking into this closer, I adapted the repro code a bit to see what might be happening under the hood and this is my conclusion:

    • Full entity materialization correctly yields the base64-decoded payload.
    • Projection returns the UTF-8 bytes of the base64 string itself, i.e., no decode is happening in the projection path.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions