# 七、事件处理

## 保存和加载事件

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

实体框架的[对象上下文](http://msdn.microsoft.com/en-us/library/system.data.objects.objectcontext.aspx)类公开了两个事件。

表 6:对象上下文事件

| 事件 | 目的 |
| [对象物化](http://msdn.microsoft.com/en-us/library/system.data.objects.objectcontext.objectmaterialized.aspx) | 作为查询执行的结果，当实体被具体化时引发。 |
| [保存更改](http://msdn.microsoft.com/en-us/library/system.data.objects.objectcontext.savingchanges.aspx) | 当上下文将要保存其附加的实体时引发。 |

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

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

```cs
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);
    }
  }
}

```

因此，我们有两个选项来处理[对象具体化](http://msdn.microsoft.com/en-us/library/system.data.objects.objectcontext.objectmaterialized.aspx)和[保存更改](http://msdn.microsoft.com/en-us/library/system.data.objects.objectcontext.savingchanges.aspx)事件:

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

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

```cs
//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 的适当实现。

```cs
//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](http://technet.microsoft.com/en-us/library/ms186775.aspx) 时为主键生成一个值。在这种情况下，我们需要从某个地方获取下一个值，例如数据库序列、函数或表，并将其分配给标识符属性。

```cs
//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()");
    }
  }
}

```

| ![](img/tip.png) | 提示:如何实现 GetNextId 函数取决于您。 |