Tracking changes in json column in EF Core 8 and postgres
i have a problem with tracking changes in json column in EF Core 8 and postgres 8. It seems like explicity setting entries as modified does not work
Models i am using:
public class DocumentReadModelEntity<T> where T : IDocumentReadModel
{
public Guid Id { get; set; }
public T Data { get; set; }
public DocumentReadModelEntity()
{
}
public DocumentReadModelEntity(T data)
{
Data = data;
Id = data.Id;
}
}
public sealed class SupplierReadModel : IDocumentReadModel
{
public string? Description { get; set; }
public string? Website { get; set; }
public Guid? LogoFileId { get; set; }
public bool IsActive { get; set; }
public bool ShouldSendRemittanceEmail { get; set; }
public List<SupplierBlockModel> Blocks { get; set; }
public List<EstateBlockModel> Estates { get; set; }
public Guid CompanyId { get; set; }
public CompanyShortData? CompanyData { get; set; }
public BankAccountModel? BankAccount { get; set; }
}
every complex property in SupplierReadModel (in this example they are: SupplierBlockModel, EstateBlockModel, CompanyShortData, BankAccountModel) inherits from
[Owned]
public abstract class ReadModelDataModel
{
}
For example (SupplierBlockModel):
public class SupplierBlockModel : ReadModelDataModel
{
public Guid BlockId { get; set; }
public BlockShortData? BlockData { get; set; }
}
Nested properties inherit as well
public class BlockShortData : ReadModelDataModel
{
public string CustomId { get; set; }
public string BlockName { get; set; }
[JsonIgnore]
public string FullName => $"[{CustomId}] {BlockName}";
}
Entity configuration looks like:
public static void ApplyDefaultConfiguration<T>(this EntityTypeBuilder<DocumentReadModelEntity<T>> builder) where T : class, IDocumentReadModel
{
builder.ToTable(typeof(T).Name);
builder.HasKey(x => x.Id);
builder.OwnsOne(x => x.Data, x =>
{
x.ToJson();
});
}
I am quering db context as:
return await _entities.AsNoTracking()
.Where(x => ids.Contains(x.Id))
.Select(x => x.Data)
.ToListAsync();
where _entities is _entities = _dbContext.Set<DocumentReadModelEntity<T>>(); in generic repository public class DocumentStore<T> : IDocumentStore<T> where T : class, IDocumentReadModel, new()
when i am updating SupplierReadModel and i try to update this entry with:
public void Update(T updated)
{
var entity = new DocumentReadModelEntity<T>(updated);
_entities.Entry(entity).State = EntityState.Modified;
}
and then
public async Task Save()
{
await _dbContext!.SaveChangesAsync();
}
nothing is saved. additionaly when i look at the debug view of db context change tracker i can see
DocumentReadModelEntity<SupplierReadModel> {Id: 018d834f-75d9-48eb-8af1-543579976bc5} Modified
Id: '018d834f-75d9-48eb-8af1-543579976bc5' PK
Data: <not found>
what do i wrong?
This issue is lacking enough information for us to be able to fully understand what is happening. Please attach a small, runnable project or post a small, runnable code listing that reproduces what you are seeing so that we can investigate.
@bartoszgrzeda a simple console program rather than a collection of snippets is what we need in order to investigate.
i created a console program. https://github.com/bartoszgrzeda/TrackingChanges in readme you have a scripts to run to seed database. connection string is hardcoded in DocumentStore class
expected behaviour is to have updated TestProperty value
Note for EF team: the problem here is that the JSON document is used Id as its primary key, but EF is not picking up on this, so the resulting model is:
Model:
EntityType: DocumentReadModelEntity<TestReadModel>
Properties:
Id (Guid) Required PK AfterSave:Throw ValueGenerated.OnAdd
Navigations:
Data (TestReadModel) ToDependent TestReadModel
Keys:
Id PK
EntityType: TestReadModel Owned
Properties:
DocumentReadModelEntityId (no field, Guid) Shadow Required PK FK AfterSave:Throw
Id (Guid) Required
TestProperty (string) Required
Keys:
DocumentReadModelEntityId PK
Foreign keys:
TestReadModel {'DocumentReadModelEntityId'} -> DocumentReadModelEntity<TestReadModel> {'Id'} Unique Required Ownership Cascade ToDependent: Data
Notice the shadow PK/FK. This can be fixed with:
builder.OwnsOne(x => x.Data, x =>
{
x.HasKey(e => e.Id);
x.ToJson();
});
Which gives the expected model:
Model:
EntityType: DocumentReadModelEntity<TestReadModel>
Properties:
Id (Guid) Required PK AfterSave:Throw ValueGenerated.OnAdd
Navigations:
Data (TestReadModel) ToDependent TestReadModel
Keys:
Id PK
EntityType: TestReadModel Owned
Properties:
Id (Guid) Required PK FK AfterSave:Throw
TestProperty (string) Required
Keys:
Id PK
Foreign keys:
TestReadModel {'Id'} -> DocumentReadModelEntity<TestReadModel> {'Id'} Unique Required Ownership Cascade ToDependent: Data
But then hits #29380.
Closing this since #29380 is tracking the root cause.