A delete/insert converted to a update allows changing property values that should not changed after insert
- Dominant language
- C#
- Stars
- 14.8k
- Forks
- 3.4k
- PR merge metrics
- PR metrics pending
Description
Originally reported here: https://github.com/PomeloFoundation/Pomelo.EntityFrameworkCore.MySql/issues/1908
See #30705
The main issue here is that the application is marking an entity as Deleted, and then adding a new instance with the same key as Added. Because of #30705, this gets converted to an update containing this:
```sql
UPDATE [Item] SET [CreatedAt] = @p0, [UpdatedAt] = @p1
```
Before 7.0.3, the behavior was this:
```sql
UPDATE [Item] SET [UpdatedAt] = @p1
```
`UpdatedAt` is marked as `BeforeSaveBehavior` ignore.
`CreatedAt` is marked as `AfterSaveBehavior` ignore.
So, if we treat this as an update, then `UpdatedAt` should be sent, but `CreatedAt` should not.
On the other hand, if this remains a `delete` `insert`, then `CreatedAt` should be sent in the insert, and `UpdatedAt` should not.
Repro:
```C#
using var context = new StoreContext();
await context.Database.EnsureDeletedAsync();
await context.Database.EnsureCreatedAsync();
var item = new Item { Id = "abc", CreatedAt = DateTime.Now.ToString() };
var store = new Store { CreatedAt = DateTime.Now.ToString(), Items = { item }};
context.Add(store);
await context.SaveChangesAsync();
context.ChangeTracker.Clear();
store = await context.Stores.Include(e => e.Items).SingleAsync();
store.CreatedAt = "X";
store.UpdatedAt = "Y";
store.Items = new List()
{ new() { Id = item.Id, CreatedAt = "A", UpdatedAt = "B" } };
// Doing the store update like this instead does not result in the issue:
// store.Items.First().CreatedAt += "+";
// store.Items.First().UpdatedAt += "+";
context.SaveChanges();
public class Item
{
public string Id { get; set; } = null!;
public int StoreId { get; set; }
public string? CreatedAt { get; set; }
public string? UpdatedAt { get; set; }
}
public class Store
{
public int StoreId { get; set; }
public List Items { get; set; } = new();
public string? CreatedAt { get; set; }
public string? UpdatedAt { get; set; }
}
public class StoreContext : DbContext
{
public DbSet Stores => Set();
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) =>
optionsBuilder
.LogTo(Console.WriteLine, LogLevel.Information)
.UseSqlServer(
"Data Source=localhost;Database=BuildBlogs;Integrated Security=True;Trust Server Certificate=True;ConnectRetryCount=0");
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity(b =>
{
b.Property(x => x.CreatedAt).Metadata.SetAfterSaveBehavior(PropertySaveBehavior.Ignore);
b.Property(x => x.UpdatedAt).Metadata.SetBeforeSaveBehavior(PropertySaveBehavior.Ignore);
});
modelBuilder.Entity(b =>
{
b.Property(x => x.CreatedAt).Metadata.SetAfterSaveBehavior(PropertySaveBehavior.Ignore);
b.Property(x => x.UpdatedAt).Metadata.SetBeforeSaveBehavior(PropertySaveBehavior.Ignore);
});
}
}
```
Contributor guide
Assessment
This issue has not been assessed yet.