EntityFramework.Docs icon indicating copy to clipboard operation
EntityFramework.Docs copied to clipboard

EF events example fails

Open samuelpsfung opened this issue 3 years ago • 2 comments

I run the example with MySQL, there are logs from the event listener, but the modified timestamp is not persisted to DB.

ef-events


Document Details

Do not edit this section. It is required for docs.microsoft.com ➟ GitHub issue linking.

samuelpsfung avatar May 27 '21 11:05 samuelpsfung

@roji to take a look.

ajcvickers avatar May 27 '21 17:05 ajcvickers

@ajcvickers this indeed doesn't work (not related to MySQL - see minimal repro below). By the time that the event fires - settings the Modified timestamp - that property has already been checked by the change tracker, and so the property remains Unchanged and the new value doesn't get sent to the database.

Explicitly setting the property to IsModified=true in the event handler fixes the issue. The somewhat tricky thing here is that depending on the traversal order of the properties in the change tracker, this may or may not happen. We should update the guidance here.

(BTW not sure about showing the "deleted" timestamp here, given that the entity is about to get deleted...)

Repro
using var context = new BlogsContext();
context.Database.EnsureDeleted();
context.Database.EnsureCreated();

var blog = context.Blogs.Single();
blog.Name = "EF Core Blog";
context.SaveChanges();

public class BlogsContext : DbContext
{
    public BlogsContext()
    {
        ChangeTracker.StateChanged += UpdateTimestamps;
        ChangeTracker.Tracked += UpdateTimestamps;
    }

    private static ILoggerFactory ContextLoggerFactory
        => LoggerFactory.Create(b => b.AddConsole().AddFilter("", LogLevel.Information));

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        => optionsBuilder
            .UseSqlServer(@"Server=localhost;Database=test;User=SA;Password=Abcd5678;Connect Timeout=60;ConnectRetryCount=0")
            .EnableSensitiveDataLogging()
            .UseLoggerFactory(ContextLoggerFactory);

    protected override void OnModelCreating(ModelBuilder modelBuilder)
        => modelBuilder.Entity<Blog>().HasData(new Blog { Id = 1, Name = "EF Blog" });

    public DbSet<Blog> Blogs { get; set; }

    private static void UpdateTimestamps(object sender, EntityEntryEventArgs e)
    {
        if (e.Entry.Entity is IHasTimestamps entityWithTimestamps)
        {
            switch (e.Entry.State)
            {
                case EntityState.Deleted:
                    entityWithTimestamps.Deleted = DateTime.UtcNow;
                    // e.Entry.Property("Modified").IsModified = true;
                    Console.WriteLine($"Stamped for delete: {e.Entry.Entity}");
                    break;
                case EntityState.Modified:
                    entityWithTimestamps.Modified = DateTime.UtcNow;
                    // e.Entry.Property("Modified").IsModified = true;
                    Console.WriteLine($"Stamped for update: {e.Entry.Entity}");
                    break;
                case EntityState.Added:
                    entityWithTimestamps.Added = DateTime.UtcNow;
                    Console.WriteLine($"Stamped for insert: {e.Entry.Entity}");
                    break;
            }
        }
    }
}

public static class HasTimestampsExtensions
{
    public static string ToStampString(this IHasTimestamps entity)
    {
        return $"{GetStamp("Added", entity.Added)}{GetStamp("Modified", entity.Modified)}{GetStamp("Deleted", entity.Deleted)}";

        string GetStamp(string state, DateTime? dateTime)
            => dateTime == null ? "" : $" {state} on: {dateTime}";
    }
}

public interface IHasTimestamps
{
    DateTime? Added { get; set; }
    DateTime? Deleted { get; set; }
    DateTime? Modified { get; set; }
}

public class Blog : IHasTimestamps
{
    [DatabaseGenerated(DatabaseGeneratedOption.None)]
    public int Id { get; set; }

    public string Name { get; set; }

    public DateTime? Added { get; set; }
    public DateTime? Deleted { get; set; }
    public DateTime? Modified { get; set; }

    public override string ToString()
        => $"Blog {Id}{this.ToStampString()}";
}
²²²

roji avatar May 28 '21 11:05 roji