Repository navigation
Lack of Auto-Increment Support in Cosmos DB with EF Core #1105
Description
// might want to mark this issue as bug, to be fixed in the long run
Issue
Cosmos DB does not support auto-incrementing fields like SQL's IDENTITY. When using Entity Framework Core with Cosmos DB, there is no built-in way to generate a unique, sequential global number for each entity. This makes it difficult to assign a reliable GlobalSequenceNumber across all documents.
For ex for PostgreSQL schema looks like GlobalSequenceNumber bigint GENERATED BY DEFAULT AS IDENTITY NOT NULL,
Workaround
A custom EF Core interceptor is used to set the GlobalSequenceNumber for new entities. The current workaround uses UTC ticks as a unique value, but this is not a true auto-increment and can lead to collisions under high load. The recommended robust solution is to implement a counter document with optimistic concurrency, similar to other event stores to guarantee sequential numbers in Cosmos DB.
Also not sure why for efcore GlobalSequenceNumber property is init-Only
Ugly but working fix (with reflection for init prop)
services.AddDbContext<MyOwnDbContext>((provider, options) =>
options.UseCosmos(
"...",
databaseName: "mydb-dev",
optionsx =>
{
optionsx.ConnectionMode(ConnectionMode.Gateway);
})
.AddInterceptors(provider.GetRequiredService<EntityModificationInterceptor>())
);public class EntityModificationInterceptor : SaveChangesInterceptor
{
public override InterceptionResult<int> SavingChanges(
DbContextEventData eventData,
InterceptionResult<int> result)
{
var context = eventData.Context;
if (context == null) return result;
var data = context.ChangeTracker.Entries().ToList();
foreach (var entry in data)
{
if (entry.CurrentValues.Properties.Any(p => p.Name == nameof(EventEntity.GlobalSequenceNumber)))
{
var currentValue = entry.CurrentValues[nameof(EventEntity.GlobalSequenceNumber)];
var tsValue = DateTime.UtcNow.Ticks;
long globalSequence = currentValue is long l ? l : 0;
if (globalSequence == 0)
{
if (entry.Entity is EventEntity entity)
{
var prop = entry.Entity.GetType().GetProperty(nameof(EventEntity.GlobalSequenceNumber));
if (prop != null)
{
prop.SetValue(entry.Entity, tsValue);
}
}
entry.Property(nameof(EventEntity.GlobalSequenceNumber)).CurrentValue = tsValue;
var typeName = entry.Entity.GetType().Name;
entry.Property("__id").CurrentValue = $"{typeName}|{tsValue}";
}
}
}
return result;
}
public override async ValueTask<InterceptionResult<int>> SavingChangesAsync(
DbContextEventData eventData,
InterceptionResult<int> result,
CancellationToken cancellationToken = default)
{
SavingChanges(eventData, result);
return await base.SavingChangesAsync(eventData, result, cancellationToken);
}
}

Activity
github-actions commentedon Sep 3, 2025
Hello there!
We hope you are doing well. We noticed that this issue has not seen any activity in the past 90 days.
We consider this issue to be stale and will be closing it within the next seven days.
If you still require assistance with this issue, please feel free to reopen it or create a new issue.
Thank you for your understanding and cooperation.
Best regards,
EventFlow
github-actions commentedon Sep 10, 2025
Hello there!
This issue has been closed due to inactivity for seven days. If you believe this issue still
needs attention, please feel free to open a new issue or comment on this one to request its
reopening.
Thank you for your contribution to this repository.
Best regards,
EventFlow
rasmus commentedon Oct 26, 2025
.analyze
github-actions commentedon Oct 26, 2025
Overview
GlobalSequenceNumberwhen committing events, but Cosmos DB lacks any identity/auto-increment primitive. As a result, every inserted event keeps the default0key, so the second write fails andLoadAllCommittedEventscannot advance.Reproduction
options.UseCosmos(...)and callmodelBuilder.AddEventFlowEvents()(Cosmos container is created withGlobalSequenceNumberas the primary key).SaveChanges()throws because the new event still carriesGlobalSequenceNumber = 0, producing a duplicate key for the document id.Root Cause
CommitEventsAsyncmaterializesEventEntityinstances without assigning any global position and relies on the database to generate it (Source/EventFlow.EntityFramework/EventStores/EntityFrameworkEventPersistence.cs:62-71,83).GlobalSequenceNumberas the key, with no generation strategy (Source/EventFlow.EntityFramework/Extensions/ModelBuilderExtensions.cs:33-41); relational providers implicitly turn this into an identity column, but Cosmos does not.init-only (Source/EventFlow.EntityFramework/EventStores/EventEntity.cs:28-36), downstream code cannot easily substitute its own generator without resorting to reflection, as shown in the issue.Assessment
LoadAllCommittedEvents(global ordering, resumableGlobalPositiontokens).Options for a Fix
IGlobalSequenceNumberGenerator), default it to identity columns, and supply a Cosmos implementation that stores the counter in a separate document and increments it using optimistic concurrency orTransactionalBatch.EventEntity.GlobalSequenceNumbertoget; set;and document an officially supported interception hook, so users can inject a safe generator without breaking immutability via reflection.Workarounds
init. It should be documented as “unsafe/temporary only.”Next Steps
Your friendly neighborhood AI