I have a DDD setup in C#, linking entities to DB tables. An entity has a configured Id (primary key) that encapsulates a non-negative, non-zero integer (a value object). I have the primary key configured in EF Core fluent API, and have configured a converter for the primary key property to convert to/from int. On the DB side (MySQL) the primary key is an auto-incrementing int.
When I create a new entity instance, a new row is added to the table with an Id value I would expect. However, on the EF Core side, the added entity does not have an Id matching the Id in the DB.
I am using CSharpFunctionalExtensions (https://github.com/vkhorikov/CSharpFunctionalExtensions) for the value objects.
My entities look like this:
public class Entity
{
Entity() {}
public static Entity CreateNew()
{
Id = EntityId.CreateNew();
}
public EntityId Id { get; private init; } = null;
...
}
My entity ids look like this:
public class EntityId : SimpleValueObject<int>
{
EntityId(int value) : base(value) {}
public static EntityId CreateNew()
{
return new EntityId(0);
}
}
SimpleValueObject wraps a Type T and just provides some comparison and equality overloads
public T Value { get; private set; }
My MySQL table is as follows:
CREATE TABLE entity
(
EntityId INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
Field1 NVARCHAR(3072) NOT NULL,
Field2 INT NOT NULL,
...
);
The entity is configured in EF Core as follows:
public class EntityConfiguration : IEntityTypeConfiguration<Entity>
{
public void Configure(EntityTypeBuilder<Entity> builder)
{
builder.ToTable("entity");
builder.HasKey(j => j.Id).HasName("PRIMARY");
builder
.Property(e => e.Id)
.HasConversion(v => v.Value, v => EntityId.Create(v).Value)
.HasColumnName("EntityId")
.HasColumnType("int(11)");
}
}
When I run the following code, the entity’s Id does not update to match what is saved, but remains 0:
var entity = Entity.CreateNew();
_db.Entities.Add(entity);
await _db.SaveChangesAsync(cancellationToken);
// Expect Id to be the auto-generated value
var id = entity.Id; // But, this returns an EntityId with Value = 0
var allEntities = _db.Entities; // However, the entity has the correct Id value in the dbcontext
I have been agonizing over this for a day now. Any advice or suggestions are appreciated.
I have tried configuring the EF Core setup multiple different ways. I’ve tried backing fields and ValueGeneratedOnAdd. The only thing that has worked is removing the EntityId object and replacing it with an int. Then the Id value is updated when the entity is saved. But I don’t know why the same doesn’t work for a field represented by a Value Object.
Edit: I have tried
ValueGeneratedOnAdd()
using EF Core’s fluent API, but that didn’t work either.
EDIT: Runtimes
- EF Core v8.0.8
- .NET v8.0.303
2
Answers
I worked out eventually that the reason it isn't working is because EF Core only updates the entity with the auto-incremented ID if the ID in the model is the default value for the type. The default value for
EntityId
is null, so the value of 0 I had given it told EF Core that it didn't need to be updated.The best solution I have found to this so far is to create a backing field for the EntityId and assign to to a primitive type (int in this instance). Since EF Core does not allow null primary keys, this was all I could come up with at this stage.
Although a property is configured for value generation, in many cases you may still explicitly specify a value for it. Whether this will actually work depends on the specific value generation mechanism that has been configured; while you may specify an explicit value instead of using a column’s default value, the same cannot be done with computed columns.
To override value generation with an explicit value, simply set the property to any value that is not the CLR default value for that property’s type (null for string, 0 for int, Guid.Empty for Guid, etc.).
https://learn.microsoft.com/en-us/ef/core/modeling/generated-properties?tabs=data-annotations
try this: