Due to the way UpdateOne and DeleteOne only allow for a single filter and not per-document subfilters we can not support concurrency tokens on owned entities. The only way to do it otherwise would be to perform multiple update/delete passes over the documents (with some re-ordering of operations too).
Unfortunately today we do not prevent them from being defined so the model can declare a concurrency token on an owned entity and then it is silently ignored.
We should flag such kinds of invalid concurrency tokens at model validation time to prevent unexpected behavior.
class Order { public ObjectId _id { get; set; } public string Name { get; set; } public Audit Audit { get; set; } // owned (nested) entity } class Audit { [ConcurrencyCheck] // token lives on the owned entity, not the root public int Version { get; set; } public string Text { get; set; } } // 1. Seed using (var db1 = /* context */) { db1.Add(new Order { Name = "Root", Audit = new Audit { Version = 1, Text = "Initial" } }); db1.SaveChanges(); } // 2. A concurrent context advances the owned token using (var db2 = /* fresh context */) { var o = db2.Set<Order>().First(); o.Audit.Version = 2; o.Audit.Text = "Changed by db2"; db2.SaveChanges(); } // 3. A stale context (still sees Audit.Version == 1) edits the owned entity and saves order1.Audit.Text = "Changed by db1"; db1.SaveChanges(); // EXPECTED: DbUpdateConcurrencyException // ACTUAL: succeeds and overwrites db2's changeç
Expected: step 3 throws DbUpdateConcurrencyException (the update filter should include { "Audit.Version": 1 }, match nothing).
Actual: the save succeeds; a re-read shows Audit.Text == "Changed by db1", proving the owned token was never checked.