Skip to content

Renamed properties ([Column]/[BsonProperty]/[JsonPropertyName]) fail on insert: field name not registered #184

Closed

Description

@CaffeinatedCoder

Version: BLite 5.2.3 (main at cb8a521), .NET 10.0.12, macOS 27 arm64 (the code path is platform-independent)

What happens

A typed entity with a property renamed through [Column("...")], [BsonProperty("...")] or [JsonPropertyName("...")] can't be inserted:

System.InvalidOperationException : BSON Key 'full_name' not found in dictionary cache. Ensure all keys are registered before serialization.
   at BLite.Bson.BsonSpanWriter.WriteElementHeader(BsonType type, String name)
   at BLite.Bson.BsonSpanWriter.WriteString(String name, String value)
   at ..._CustomerMapper.Serialize(Customer entity, BsonSpanWriter writer)
   at BLite.Core.Collections.DocumentCollection`2.SerializeWithRetry(T entity, Byte[]& rentedBuffer)
   at BLite.Core.Collections.DocumentCollection`2.InsertCore(T entity, ITransaction transaction)

The insert only works if the new name happens to be another property's conventional name, which is already registered.

Cause

The two sides disagree on the field names:

  • Writing: the mapper generator honours the attributes. EntityAnalyzer sets BsonFieldName from [BsonProperty] / [JsonPropertyName] / [Column] and falls back to prop.Name.ToLowerInvariant() (EntityAnalyzer.cs:162-175). The generated Serialize writes that name (CodeGenerator.cs:291), e.g. writer.WriteString("full_name", entity.Name).
  • Registering: the key dictionary is filled from _mapper.UsedKeys (DocumentCollection.cs:852). UsedKeys comes from GetSchema(), i.e. BsonSchemaGenerator.FromType<T>() (BaseMappers.cs:26-29), which names every field name.ToLowerInvariant() and ignores the attributes (BsonSchemaGenerator.cs:74-79). So "name" is registered, "full_name" is not, and BsonSpanWriter.WriteElementHeader throws (BsonSpanWriter.cs:106).

The query side makes the same lowercase assumption: BsonExpressionEvaluator (:129 and the similar branches after it) and ProjectionAnalyzer.ToBsonName (:77-79). Once inserts work, BSON-level filters and projections on a renamed property would look in the wrong field. They would find nothing, or another property's value when its conventional name is the renamed one. I haven't verified this end to end, since the insert fails first.

Repro

An entity and context in tests/BLite.Shared (so the mapper is generated), and a test in tests/BLite.Tests:

// tests/BLite.Shared
using System.ComponentModel.DataAnnotations.Schema;
using System.Text.Json.Serialization;

public class Customer
{
    public ObjectId Id { get; set; }
    [Column("full_name")] public string Name { get; set; } = "";
    [JsonPropertyName("mail")] public string Email { get; set; } = "";
}

public partial class RenameReproContext : DocumentDbContext
{
    public DocumentCollection<ObjectId, Customer> Customers { get; set; } = null!;
    public RenameReproContext(string path) : base(path) => InitializeCollections();
    protected override void OnModelCreating(ModelBuilder modelBuilder)
        => modelBuilder.Entity<Customer>().ToCollection("customers");
}

// tests/BLite.Tests
[Fact]
public async Task Renamed_Properties_Can_Be_Inserted()
{
    var path = Path.Combine(Path.GetTempPath(), $"rename_{Guid.NewGuid():N}.db");
    using var db = new RenameReproContext(path);
    await db.Customers.InsertAsync(new Customer { Name = "Ada", Email = "ada@example.com" }); // throws
}

The generated mapper contains writer.WriteString("full_name", entity.Name) and writer.WriteString("mail", entity.Email).

Possible fix

Give the runtime one source of truth for the field names, and use it for key registration, BSON predicates and projections:

  • Option 1: the generator emits a UsedKeys (or GetSchema) override with the BsonFieldNames, plus a property-name → field-name map.
  • Option 2: BsonSchemaGenerator reads the same three attributes the generator does. That fixes registration but not the query side, which works from member names.

Option 1 seems more robust, since it can't drift from what Serialize writes. I'm happy to send a PR for whichever you prefer.

(Found while working on #183, which skips its raw-BSON aggregate path for renamed properties for this reason.)

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingpackage/coreAffects BLite.Corepackage/generatorpriority/P1Must be in the next minor releaseseverity/highWrong results, crash or failed writes in a realistic scenario

    Type

    No type

    Projects

    • Status
      Done

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions