背景
企业应用开发过程中经常面对一些非功能型需求,如:自动收集和设置审计信息、索引和关系约束,有些非功能需求当然可以用数据库自带的功能,如索引约束,但是应用层视乎也有必要重复一次,因为当违背这种约束的时候我们希望提示给用户友好的信息,如:‘xxx已经存在,xxx必须唯一’,这篇文章我就介绍一个简单的方案应对这种需求。
思路
我觉得数据库的触发器是个好东西,应用层完全可以借用一下,我还认为如果我在应用层实现了触发器,像一些前置条件和后置条件验证也可以用触发器实现(这块我不是很清楚设计的是否合理,还是要引入另外一个继承体系)。
实现
核心类
核心代码
DefaultTriggerService.cs
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 7 using Microsoft.Practices.ServiceLocation; 8 9 using Happy.Domain;10 11 namespace Happy.Application.Trigger.Internal12 {13 internal sealed class DefaultTriggerService : ITriggerService14 {15 public void ExecuteBeforeInsert(TAggregateRoot aggregate)16 where TAggregateRoot : AggregateRoot17 {18 var triggers = ServiceLocator19 .Current20 .GetAllInstances >();21 22 foreach (var trigger in triggers)23 {24 trigger.BeforeInsert(aggregate);25 }26 }27 28 public void ExecuteAfterInsert (TAggregateRoot aggregate)29 where TAggregateRoot : AggregateRoot30 {31 var triggers = ServiceLocator32 .Current33 .GetAllInstances >();34 35 foreach (var trigger in triggers)36 {37 trigger.AfterInsert(aggregate);38 }39 }40 41 public void ExecuteBeforeUpdate (TAggregateRoot aggregate)42 where TAggregateRoot : AggregateRoot43 {44 var triggers = ServiceLocator45 .Current46 .GetAllInstances >();47 48 foreach (var trigger in triggers)49 {50 trigger.BeforeUpdate(aggregate);51 }52 }53 54 public void ExecuteAfterUpdate (TAggregateRoot aggregate)55 where TAggregateRoot : AggregateRoot56 {57 var triggers = ServiceLocator58 .Current59 .GetAllInstances >();60 61 foreach (var trigger in triggers)62 {63 trigger.AfterUpdate(aggregate);64 }65 }66 67 public void ExecuteBeforeDelete (TAggregateRoot aggregate)68 where TAggregateRoot : AggregateRoot69 {70 var triggers = ServiceLocator71 .Current72 .GetAllInstances >();73 74 foreach (var trigger in triggers)75 {76 trigger.BeforeDelete(aggregate);77 }78 }79 80 public void ExecuteAfterDelete (TAggregateRoot aggregate)81 where TAggregateRoot : AggregateRoot82 {83 var triggers = ServiceLocator84 .Current85 .GetAllInstances >();86 87 foreach (var trigger in triggers)88 {89 trigger.AfterDelete(aggregate);90 }91 }92 }93 }
自动管理树形节点的路径信息
代码
ITreeNode.cs
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 7 namespace Happy.Domain.Feature 8 { 9 ///10 /// 树的节点。11 /// 12 public interface ITreeNode13 {14 ///15 /// 节点ID。16 /// 17 Guid Id { get; set; }18 19 ///20 /// 父节点ID。21 /// 22 Guid ParentId { get; set; }23 24 ///25 /// 节点在树中的路径,如:/A/B/C/D,包含自己。26 /// 27 string NodePath { get; set; }28 }29 }
TreeNodeTrigger.cs
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 7 using Happy.ExtensionMethod; 8 using Happy.Domain; 9 using Happy.Domain.Feature;10 using Happy.Application.Trigger;11 12 namespace Happy.EntityFramework.Trigger13 {14 public class TreeNodeTrigger: TriggerBase 15 where TUnitOfWork : UnitOfWork16 where TAgggregateRoot : AggregateRoot, ITreeNode17 {18 public override void BeforeInsert(TAgggregateRoot aggregate)19 {20 var parentNodePath = this.GetParentNodePath(aggregate);21 22 aggregate.NodePath = parentNodePath + "/" + aggregate.Id;23 }24 25 public override void BeforeUpdate(TAgggregateRoot aggregate)26 {27 var newParentNodePath = this.GetParentNodePath(aggregate);28 var newNodePath = newParentNodePath + "/" + aggregate.Id;29 var oldNodePath = aggregate.NodePath;30 31 if (oldNodePath == newNodePath)32 {33 return;34 }35 36 aggregate.NodePath = newNodePath;37 38 var table = typeof(TAgggregateRoot).Name.ToPluralize();39 var sql = string.Format("UPDATE {0} SET NodePath = REPLACE(NodePath, { {0}}, { {1}}) WHERE NodePath LIKE { {2}}", table);40 this.UnitOfWork.Database.ExecuteSqlCommand(sql, oldNodePath, newNodePath, oldNodePath + "/%");41 }42 43 public override void BeforeDelete(TAgggregateRoot aggregate)44 {45 var table = typeof(TAgggregateRoot).Name.ToPluralize();46 var sql = string.Format("DELETE FROM {0} WHERE NodePath LIKE { {0}}", table);47 this.UnitOfWork.Database.ExecuteSqlCommand(sql, aggregate.NodePath + "/%");48 }49 50 private string GetParentNodePath(TAgggregateRoot aggregate)51 {52 var table = typeof(TAgggregateRoot).Name.ToPluralize();53 var sql = string.Format("SELECT NodePath FROM {0} WHERE Id = { {0}}", table);54 return this.UnitOfWork.Database.SqlQuery (sql, aggregate.ParentId).FirstOrDefault();55 }56 }57 }
运行效果
备注
这种触发器我在项目中有用过,虽然有所不足,如批量操作性能不高,但是在很多场景下,也减少了不少的重复代码。