事件是。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;
}
}
}
很快,它的作用是:
- 从查询(对象物化)加载的所有可免疫实体都会立即从上下文中分离出来。
- 即将被保存(保存更改)的脏可免疫实体被设置为分离,因此它们不会被保存。
在另一种情况下,对实体进行审计更改。为此,我们想记录下:
- 首次创建实体的用户。
- 当它被创造的时候。
- 上次修改实体的用户。
- 上次更新的时间。
我们将从定义一个公共的审计接口 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 方法中,我们:
- 列出所有新的可编辑实体,并设置它们的创建日期和创建日期属性。
- 同时,所有修改过的实体都设置了 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()");
}
}
}