Skip to content

Latest commit

 

History

History
214 lines (164 loc) · 6.71 KB

07.md

File metadata and controls

214 lines (164 loc) · 6.71 KB

七、事件处理

保存和加载事件

事件是。NET 的观察者设计模式的实现,用于将一个类从其他对可能发生的变化感兴趣的类中分离出来。虽然实体框架代码优先没有直接公开任何事件,因为它位于“经典”实体框架之上,但是利用它公开的事件是非常容易的。

实体框架的对象上下文类公开了两个事件。

表 6:对象上下文事件

| 事件 | 目的 | | 对象物化 | 作为查询执行的结果,当实体被具体化时引发。 | | 保存更改 | 当上下文将要保存其附加的实体时引发。 |

我们为什么需要这个?嗯,我们可能希望在从数据库加载实体之后,或者在将要保存或删除实体之前,执行一些额外的任务。

将这些事件带到代码第一地的一种方法如下。

public class ProjectsContext : DbContext
{
  public ProjectsContext()
  {
    this.AddEventHandlers();
  }

  //raised when the context is about to save dirty entities 
  public event EventHandler<EventArgs> SavingChanges;
  //raised when the context instantiates an entity as the result of a query
  public event EventHandler<ObjectMaterializedEventArgs> ObjectMaterialized;

  public void AddEventHandlers()
  {
    //access the underlying ObjectContext
    var octx = (this as IObjectContextAdapter).ObjectContext;
    //add local event handlers
    octx.SavingChanges += (s, e) => this.OnSavingChanges(e);
    octx.ObjectMaterialized += (s, e) => this.OnObjectMaterialized(e);
  }

  protected virtual void OnObjectMaterialized(ObjectMaterializedEventArgs e)
  {
    var handler = this.ObjectMaterialized;

    if (handler != null)
    {
      //raise the ObjectMaterialized event
      handler(this, e);
    }
  }

  protected virtual void OnSavingChanges(EventArgs e)
  {
    var handler = this.SavingChanges;

    if (handler != null)
    {
      //raise the SavingChanges event
      handler(this, e);
    }
  }
}

因此,我们有两个选项来处理对象具体化保存更改事件:

  • 继承类可以重写 OnObject 物化和 OnSavingChanges 方法。
  • 相关方可以订阅保存更改和对象具体化事件。

现在想象一下:您想要定义一个标记的接口,比如 IImmutable,当它被实体实现时,将防止它被实体框架跟踪。对于这种情况,有一个可能的解决方案。

//a simple marker interface 
public interface IImmutable { }

public class Project : IImmutable { /* … */ }

public class ProjectsContext : DbContext
{
  protected virtual void OnObjectMaterialized(ObjectMaterializedEventArgs e)
  {
    var handler = this.ObjectMaterialized;

    if (handler != null)
    {
      handler(this, e);
    }

    //check if the entity is meant to be immutable
    if (e.Entity is IImmutable)
    {
      //if so, detach it from the context
      this.Entry(e.Entity).State = EntityState.Detached;
    }
  }

  protected virtual void OnSavingChanges(EventArgs e)
  {
    var handler = this.SavingChanges;

    if (handler != null)
    {
      handler(this, e);
    }

    //get all entities that are not unchanged (added, deleted or modified)
    foreach (var immutable in this.ChangeTracker.Entries()
    .Where(x => x.State != EntityState.Unchanged && x.Entity is  IImmutable).Select(x => x.Entity).ToList())
    {
      //set the entity as detached
      this.Entry(e.Entity).State = EntityState.Detached;
    }
  }
}

很快,它的作用是:

  1. 从查询(对象物化)加载的所有可免疫实体都会立即从上下文中分离出来。
  2. 即将被保存(保存更改)的脏可免疫实体被设置为分离,因此它们不会被保存。

在另一种情况下,对实体进行审计更改。为此,我们想记录下:

  • 首次创建实体的用户。
  • 当它被创造的时候。
  • 上次修改实体的用户。
  • 上次更新的时间。

我们将从定义一个公共的审计接口 IAuditable 开始,在那里定义这些审计属性,然后我们将提供 OnSavingChanges 的适当实现。

//an interface for the auditing properties
public interface IAuditable
{
  String CreatedBy { get; set; }

  DateTime CreatedAt { get; set; }

  String UpdatedBy { get; set; }

  DateTime UpdatedAt { get; set; }
}

public class Project : IAuditable { /* … */ }

public class ProjectsContext : DbContext
{
  protected virtual void OnSavingChanges(EventArgs e)
  {
    var handler = this.SavingChanges;

    if (handler != null)
    {
      handler(this, e);
    }
    foreach (var auditable in this.ChangeTracker.Entries()
.Where(x => x.State == EntityState.Added).Select(x => x.Entity).OfType<IAuditable>())
    {
      auditable.CreatedAt = DateTime.Now;
      auditable.CreatedBy = Thread.CurrentPrincipal.Identity.Name;
    }

    foreach (var auditable in this.ChangeTracker.Entries()
.Where(x => x.State == EntityState.Modified).Select(x => x.Entity)
.OfType<IAuditable>())
    {
      auditable.UpdatedAt = DateTime.Now;
      auditable.UpdatedBy = Thread.CurrentPrincipal.Identity.Name;
    }
  }
}

在 OnSavingChanges 方法中,我们:

  1. 列出所有新的可编辑实体,并设置它们的创建日期和创建日期属性。
  2. 同时,所有修改过的实体都设置了 UpdatedAt 和 UpdatedBy 属性。

SavingChanges 事件的另一个典型用途是在无法使用 IDENTITY 时为主键生成一个值。在这种情况下,我们需要从某个地方获取下一个值,例如数据库序列、函数或表,并将其分配给标识符属性。

//an interface for accessing the identifier property of entities that require explicit identifier assignments
public interface IHasGeneratedIdentifier
{
  Int32 Identifier { get; set; }
}

public class ProjectsContext : DbContext
{
  protected virtual void OnSavingChanges(EventArgs e)
  {
    var handler = this.SavingChanges;

    if (handler != null)
    {
      handler(this, e);
    }

    foreach (var entity in this.ChangeTracker.Entries()
.Where(x => x.State == EntityState.Added).Select(x => x.Entity)
.OfType<IHasGeneratedIdentifier>())
    {
      //call some function that returns a valid identifier
      entity.Identifier = this.Database.SqlQuery<Int32>("EXEC GetNextId()");
    }
  }
}

| | 提示:如何实现 GetNextId 函数取决于您。 |