Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

A delete/insert converted to a update allows changing property values that should not changed after insert #33653

Open
ajcvickers opened this issue May 1, 2024 · 0 comments

Comments

@ajcvickers
Copy link
Member

Originally reported here: PomeloFoundation/Pomelo.EntityFrameworkCore.MySql#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:

UPDATE [Item] SET [CreatedAt] = @p0, [UpdatedAt] = @p1

Before 7.0.3, the behavior was this:

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:

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<Item>()
    { 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<Item> Items { get; set; } = new();

    public string? CreatedAt { get; set; }
    public string? UpdatedAt { get; set; }
}

public class StoreContext : DbContext
{
    public DbSet<Store> Stores => Set<Store>();

    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<Item>(b =>
        {
            b.Property(x => x.CreatedAt).Metadata.SetAfterSaveBehavior(PropertySaveBehavior.Ignore);
            b.Property(x => x.UpdatedAt).Metadata.SetBeforeSaveBehavior(PropertySaveBehavior.Ignore);
        });

        modelBuilder.Entity<Store>(b =>
        {
            b.Property(x => x.CreatedAt).Metadata.SetAfterSaveBehavior(PropertySaveBehavior.Ignore);
            b.Property(x => x.UpdatedAt).Metadata.SetBeforeSaveBehavior(PropertySaveBehavior.Ignore);
        });
    }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants