EF Core 中的 .NET 事件

小窍门

可以从 GitHub 下载事件示例

实体框架核心(EF Core)提供 .NET 事件,用于在 EF Core 代码中发生某些特定情况时作为回调函数。 事件比 侦听器 更简单,并且允许更灵活的注册。 但是,它们仅支持同步,因此无法执行非阻塞异步 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())
{
    await context.Database.EnsureDeletedAsync();
    await context.Database.EnsureCreatedAsync();

    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!" } }
        });

    await context.SaveChangesAsync();
}

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

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

    await context.SaveChangesAsync();
}

此代码的输出显示发生的状态变化,并且时间戳被应用到这些变化中。

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