EF Core 中的 .NET 事件

提示

可以从 GitHub 中下载事件示例

当在 EF Core 代码中发生某些事情时,Entity Framework Core (EF Core) 公开 .NET 事件以用作回调。 事件比侦听器器简单,并且允许更灵活的注册。 但是,它们只是同步,因此不能执行非阻塞异步 I/O。

根据每个 DbContext 实例注册事件。 使用诊断侦听器获取相同的信息,但要获取进程中所有 DbContext 实例的信息。

EF Core 引发的事件

EF Core 引发了以下事件:

事件 引发时间
DbContext.SavingChanges SaveChangesSaveChangesAsync 开始时
DbContext.SavedChanges 在成功的 SaveChangesSaveChangesAsync 结束时
DbContext.SaveChangesFailed 在失败的 SaveChangesSaveChangesAsync 结束时
ChangeTracker.Tracked 当上下文跟踪实体时
ChangeTracker.StateChanged 当跟踪的实体更改其状态时

示例:时间戳状态更改

由 DbContext 跟踪的每个实体都有一个 EntityState。 例如,Added 状态表示将实体插入到数据库中。

此示例使用 TrackedStateChanged 事件来检测实体何时更改状态。 然后用当前时间标记实体,表示发生此更改的时间。 这将生成表示实体何时插入、删除和/或上次更新的时间戳。

本示例中的实体类型实现了一个定义时间戳属性的接口:

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

然后,应用程序的 DbContext 上的方法可以为实现此接口的任何实体设置时间戳:

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;
                Console.WriteLine($"Stamped for delete: {e.Entry.Entity}");
                break;
            case EntityState.Modified:
                entityWithTimestamps.Modified = DateTime.UtcNow;
                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;
        }
    }
}

此方法具有适当的签名,可用作 TrackedStateChanged 事件的事件处理程序。 在 DbContext 构造函数中为这两个事件注册了处理程序。 请注意,可以随时将事件附加到 DbContext;在上下文构造函数中则不需要这样做。

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

这两个事件都是必需的,因为新实体在第一次被跟踪时会触发 Tracked 事件。 StateChanged 事件仅针对在已被跟踪时更改状态的实体触发。

此示例的示例包含一个可对博客数据库进行更改的简单控制台应用程序:

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

    context.Add(
        new Blog
        {
            Id = 1,
            Name = "EF Blog",
            Posts = { new Post { Id = 1, Title = "EF Core 3.1!" }, new Post { Id = 2, Title = "EF Core 5.0!" } }
        });

    context.SaveChanges();
}

using (var context = new BlogsContext())
{
    var blog = context.Blogs.Include(e => e.Posts).Single();

    blog.Name = "EF Core Blog";
    context.Remove(blog.Posts.First());
    blog.Posts.Add(new Post { Id = 3, Title = "EF Core 6.0!" });

    context.SaveChanges();
}

此代码的输出显示正在发生的状态更改和正在应用的时间戳:

Stamped for insert: Blog 1 Added on: 10/15/2020 11:01:26 PM
Stamped for insert: Post 1 Added on: 10/15/2020 11:01:26 PM
Stamped for insert: Post 2 Added on: 10/15/2020 11:01:26 PM
Stamped for delete: Post 1 Added on: 10/15/2020 11:01:26 PM Deleted on: 10/15/2020 11:01:26 PM
Stamped for update: Blog 1 Added on: 10/15/2020 11:01:26 PM Modified on: 10/15/2020 11:01:26 PM
Stamped for insert: Post 3 Added on: 10/15/2020 11:01:26 PM