Skip to content

Source Generator produces empty array for byte[] properties — WriteBinary is never called #27

@LeoYang06

Description

@LeoYang06

Package version

4.0.2

Affected package

BLite (client SDK)

.NET version

10.0

Description

When an entity contains a byte[]? property, the Source Generator produces serialization code that writes an empty BSON array ([]) regardless of the actual data. The property is silently discarded on every write and read, resulting in permanent data loss with no compiler warning.

The root cause is that EntityAnalyzer classifies byte[] as a collection (IsCollection = true, CollectionItemType = "byte"). The collection serialization branch in CodeGenerator.GenerateWriteProperty then calls GetPrimitiveWriteMethod with type name "byte", which returns null because "byte" is not in the primitive type mapping table. When writeMethod == null, the foreach loop body contains no write call — nothing is written per element. Only BeginArray / EndArray wrappers are emitted, producing an empty BSON array.

Meanwhile, BsonSpanWriter.WriteBinary does exist and correctly writes BSON Binary data (type 0x05). The Source Generator simply never generates a call to it for byte[] properties.

Minimal reproduction

Entity definition

public class Photo
{
    public Guid Id { get; set; }
    public byte[]? ThumbHashData { get; set; }
}

DbContext

public class AppDbContext : BLiteDbContext
{
    public DocumentCollection<Photo> Photos { get; set; } = null!;
}

Write and read back

using var db = new AppDbContext("PhotoIndex");
var photo = new Photo
{
    Id = Guid.NewGuid(),
    ThumbHashData = new byte[] { 0x01, 0x02, 0x03, 0x04 }
};
db.Photos.Upsert(photo);

var retrieved = db.Photos.FindById(photo.Id);
// retrieved.ThumbHashData is Array.Empty<byte>() — data is silently lost

Generated source code (actual)

This is what the Source Generator produces for the byte[]? ThumbHashData property.

Serialization — writes empty array, data is lost

if (entity.ThumbHashData != null)
{
    var thumbhashdataArrayPos = writer.BeginArray("thumbhashdata");
    var thumbhashdataIndex = 0;
    foreach (var item in entity.ThumbHashData)
    {
        // BUG: GetPrimitiveWriteMethod("byte") returns null,
        // so no write call is generated here — data is silently dropped
        thumbhashdataIndex++;
    }
    writer.EndArray(thumbhashdataArrayPos);  // writes BSON []
}
else
{
    writer.WriteNull("thumbhashdata");
}

Deserialization — never reads any elements

var thumbhashdata = new global::System.Collections.Generic.List<byte>();
// ...
case "thumbhashdata":
    if (bsonType == global::BLite.Bson.BsonType.Null) break;
    var thumbhashdataArrSize = reader.ReadDocumentSize();
    var thumbhashdataArrEndPos = reader.Position + thumbhashdataArrSize - 4;
    while (reader.Position < thumbhashdataArrEndPos)
    {
        var itemType = reader.ReadBsonType();
        if (itemType == global::BLite.Bson.BsonType.EndOfDocument) break;
        reader.SkipArrayKey();
        // BUG: GetPrimitiveReadMethod("byte") returns null,
        // falls through to SkipValue — every element is skipped
        reader.SkipValue(itemType);
    }
    break;
// ...
entity.ThumbHashData = thumbhashdata.ToArray();
// Result: always Array.Empty<byte>()

In practice, since serialization writes an empty array [], the while loop never executes (immediately hits EndOfDocument), and thumbhashdata is always an empty List<byte>. The final .ToArray() always produces Array.Empty<byte>().

Expected behavior

For byte[] properties, the Source Generator should generate a call to BsonSpanWriter.WriteBinary / BsonSpanReader.ReadBinary instead of treating it as a collection of individual byte elements.

Note: BsonSpanWriter.WriteBinary and BsonSpanReader.ReadBinary already exist:

// BsonSpanWriter.cs
public void WriteBinary(string name, ReadOnlySpan<byte> data, byte subtype = 0)

// BsonSpanReader.cs
public ReadOnlySpan<byte> ReadBinary(out byte subtype)

Expected serialization:

if (entity.ThumbHashData != null)
{
    writer.WriteBinary("thumbhashdata", entity.ThumbHashData);
}
else
{
    writer.WriteNull("thumbhashdata");
}

Expected deserialization:

case "thumbhashdata":
    if (bsonType == global::BLite.Bson.BsonType.Null)
    {
        thumbhashdata = null;
    }
    else
    {
        thumbhashdata = reader.ReadBinary(out _).ToArray();
    }
    break;

Actual behavior

  • Serialization: An empty BSON array [] is written. The byte data is completely lost.
  • Deserialization: The property is always read back as Array.Empty<byte>().
  • No compiler warning: Unlike unsupported complex types that generate #warning, the bug is completely silent — data loss occurs without any build-time indication.

Additional context

  • WriteBinary / ReadBinary already exist in BsonSpanWriter / BsonSpanReader, so this is purely a Source Generator oversight.
  • GetWriteMethodForUnderlyingType maps "byte""WriteInt32", but this code path is only used for enum underlying types, not for collection item serialization.
  • The fix should be in either EntityAnalyzer (special-case byte[] as a binary property rather than a collection) or CodeGenerator (detect CollectionItemType == "byte" and emit a single WriteBinary call instead of iterating).

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingpackage/clientAffects BLite client SDKtriage

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions