.NET仓储模式高级用例

主要结论

  • 如果需要执行基本CURD之外的其他操作,此时就有必要使用仓储(Repository)。

  • 为了促进测试工作并改善可靠性,应将仓储视作可重复使用的库(Library)。

  • 将安全和审计功能放入仓储中可减少Bug并简化应用程序。

  • 对ORM的选择不会限制仓储的用途,只会影响仓储承担的工作量。

在之前发布的文章使用实体框架、Dapper和Chain的仓储模式实现策略中,我们介绍了实现仓储所需的基本模式。很多情况下,这些模式只是围绕底层数据访问技术,本质上并非完全必要的薄层。然而通过构建这样的仓储将获得很多新的机会。

在设计仓储时,需要从“必须发生的事”这个角度来思考。例如,假设制订了一条规则,每当一条记录被更新后,其“LastModifiedBy”列必须设置为当前用户。但我们并不需要在每次保存前更新应用程序代码中的LastModifiedBy,可以直接将相关函数放在仓储中。

通过将数据访问层视作管理所有“必须发生的事情”细节的独立库,即可大幅减少实现过程中的错误数量。与此同时可以简化基于仓储构建的代码,因为已经不再需要考虑“记账”之类的任务。

注意:本文会尽量提供适用于实体框架(Entity Framework)Dapper和/或Tortuga Chain的代码范例,然而大部分仓储功能均可通过不依赖具体ORM的方式实现。

审计列

大部分应用程序最终需要追踪谁在什么时间更改了数据库。对于简单的数据库,这是通过审计列(Audit column)的形式实现的。虽然名称可能各不相同,但审计列通常主要承担下列四个角色:

  • 创建者的User Key

  • 创建日期/时间

  • 最后修改者的User Key

  • 最后修改日期/时间

取决于应用程序的安全需求,可能还存在其他审计列,例如:

  • 删除者的User Key

  • 删除日期/时间

  • [创建 | 最后修改 | 删除] 者的Application Key

  • [创建 | 最后修改 | 删除] 者的IP地址

从技术角度来看日期列很容易处理,但User Key的处理就需要费些功夫了,这里需要的是“可感知上下文的仓储”。

常规的仓储是无法感知上下文的,这意味着除了连接数据库时绝对必要的信息,仓储无法获知其他任何信息。如果能正确地设计,仓储可以是彻底无状态(Stateless)的,这样即可在整个应用程序中共享一个实例。

可感知上下文的仓储略微复杂。除非了解上下文,否则无法创建这种仓储,而上下文至少要包含当前活跃用户的ID和Key。对于某些应用程序这就够了,但对于其他应用程序,我们可能还需要传递整个用户对象和/或代表运行中应用程序的对象。

Chain

Chain通过一种名为审计规则(Audit rule)的功能为此提供了内建的支持。审计规则可供我们根据列名指定要覆盖(Override)的值。该功能包含了拆箱即用的基于日期的规则,以及从用户对象将属性复制到列的规则。范例:

dataSource = dataSource.WithRules(
   new UserDataRule("CreatedByKey", "UserKey", OperationType.Insert),
   new UserDataRule("UpdatedByKey", "UserKey", OperationType.InsertOrUpdate),
   new DateTimeRule("CreatedDate", DateTimeKind.Local, OperationType.Insert),
new DateTimeRule("UpdatedDate", DateTimeKind.Local, OperationType.InsertOrUpdate) );

如上所述为了实现这一点我们需要一种可感知上下文的仓储。从下列构造函数中可以看到如何将上下文传递给不可变数据源,并使用必要信息新建数据源。

public EmployeeRepository(DataSource dataSource, User user)
{
    m_DataSource = dataSource.WithUser(user);
}

借此即可使用自行选择的DI框架针对每个请求自动创建并填写仓储。

实体框架

为了在实体框架中实现审计列的全局应用,我们需要利用ObjectStateManager并创建一个专用接口。该接口(如果愿意也可以称之为“基类(Base class)”)看起来类似这样:

public interface IAuditableEntity 
{
    DateTime CreatedDate {get; set;}
    DateTime UpdatedDate {get; set;}
    DateTime CreatedDate {get; set;}
    DateTime CreatedDate {get; set;}
}

随后该接口(或基类)会应用给数据库中与审计列匹配的每个实体。

随后需要通过下列方式对DataContext类的Save方法进行覆盖(Override)。

public override int SaveChanges()
{
    // Get added entries
    IEnumerable<ObjectStateEntry> addedEntryCollection = Context
        .ObjectContext
        .ObjectStateManager
        .GetObjectStateEntries(EntityState.Added)
        .Where(m => m != null && m.Entity != null);

    // Get modified entries
    IEnumerable<ObjectStateEn
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值