标签:数据 var 使用 bool exe serialize 实战 插入 批量
一、简介
耦合是软件不能抵御变变化的根本性原因,不仅实体对象与实体对象之间有耦合关系(如创建性设计模式存在的原因),对象和行为之间也存在耦合关系.
二、实战
1、常规开发中,我们经常会在控制器中或者Main方法中调用多个对象,进行批量的操作(完成一次事务性的操作),像下面这样:
/// <summary> /// 设计模式之Command命令模式 /// </summary> public class Program { public static void Main(string[] args) { //模拟持久化内容到文档中 var doc = new Document(); var result=doc.WriteText("小超"); if (result) { //持久化成功,记录日志 var log = new Log(); var logRes = log.WriteLog("小超写入到文档中成功"); if (logRes) { Console.WriteLine("事务性操作成功!"); } else { Console.WriteLine("事务性操作失败!"); } } Console.ReadKey(); } } /// <summary> /// 模拟文档对象 /// </summary> public class Document { public bool WriteText(string content) { //持久化到对应的数据容器 return true; } } /// <summary> /// 模拟日志对象 /// </summary> public class Log { public bool WriteLog(string logContent) { //持久化到对应的数据容器 return true; } }
ok,上面的硬编码可以很好的完成需求,但是如果中间发生异常,上的代码将无法支持撤销和回滚.注:这里假设持久化到文档和持久化到日志是一个事务操作(即他们两个必须同时成功,这个操作才算完成,否则就需要回滚).关于事务,和数据库操作一样,使用过SqlTransaction对象的都知道下面这几个方法:
如果我们传入的批量操作Sql(一般只用于增删改,查可以忽略)中有一个发生异常,那么我们就可以调用Dispose方法(释放资源)和Rollback方法,来对事务进行回滚.但是我们上面中的示例明显不支持,所以这个时候我们就需要引入Command命令模式,将两个操作合并为一个操作.在进行最终的提交,失败则回滚,如果涉及非托管资源,不论成功如否都需要释放资源.所以升级代码如下:
/// <summary> /// 设计模式之Command命令模式 /// </summary> public class Program { public static void Main(string[] args) { var document = new Document("小超1"); var command = new DocumentCommand(document); var document_3 = new Document("小超3"); var command_3 = new DocumentCommand(document_3); var document_1 = new Document("小超"); var command_1 = new DocumentCommand(document_1); var log = new Log("日志内容"); var command_2 = new LogCommand(log); var manager = new CommandManager(); manager.Commands.Enqueue(command_3); manager.Commands.Enqueue(command); manager.Commands.Enqueue(command_1); manager.Commands.Enqueue(command_2); manager.Execute(); Console.ReadKey(); } } /// <summary> /// 模拟文档对象 /// </summary> public class Document { private Document() { } public string Content { get; } public Document(string content) { Content = content; } public bool WriteText(string content) { //持久化到对应的数据容器 if (content == "小超") throw new Exception("写入文档异常"); else return true; } } /// <summary> /// 模拟日志对象 /// </summary> public class Log { private Log() { } public string Content { get; set; } public Log(string logContent) { Content = logContent; } public bool WriteLog() { //持久化到对应的数据容器 return true; } } /// <summary> /// 命令约束 /// </summary> public interface ICommand { void Execute(); void Undo(); void Redo(); } /// <summary> /// 命令基类 /// </summary> /// <typeparam name="T"></typeparam> public class Command<T> { /// <summary> /// 命令Id,方便回回滚数据 /// </summary> protected Guid CommandId { get; set; } = Guid.NewGuid(); } /// <summary> /// 文档操作命令对象 /// </summary> public class DocumentCommand : Command<Guid>,ICommand { /// <summary> /// 模拟文档内容数据容器 /// </summary> public Dictionary<Guid, Document> DocumentContents { get; set; } = new Dictionary<Guid, Document>(); private DocumentCommand() {} private Document _document; public DocumentCommand(Document document) { _document = document; } public void Execute() { //模拟持久化到数据容器中 try { Console.WriteLine("当前命令Id:{0},参数内容:{1}", CommandId, JsonConvert.SerializeObject(_document)); _document.WriteText(_document.Content); DocumentContents.Add(CommandId, _document); Console.WriteLine("当前命令执行成功,命令Id:{0},参数内容:{1}", CommandId, JsonConvert.SerializeObject(_document)); } catch (Exception ex) { Console.WriteLine("当前命令执行失败,命令Id:{0},参数内容:{1},异常信息:{2}", CommandId, JsonConvert.SerializeObject(_document),ex.Message); throw ex; } } public void Redo() { //重新执行Execute方法 Execute(); } /// <summary> /// 事物操作,如果后面的操作发生异常,这里也需要回滚 /// </summary> public void Undo() { var value = default(Document); if (DocumentContents.ContainsKey(CommandId)) { value = DocumentContents[CommandId]; } else { Console.WriteLine("文档命令执行发生异常,当前命令Id:{0},当前文档信息:{1}", CommandId, JsonConvert.SerializeObject(_document));//记录日志 } if (!DocumentContents.Remove(CommandId)) Console.WriteLine("文档命令执行发生异常,当前命令Id:{0},当前文档信息:{1}", CommandId, JsonConvert.SerializeObject(_document));//记录日志 else Console.WriteLine("事物回滚,将插入到文档中的内容删除,被删除的对象是:{0}", JsonConvert.SerializeObject(_document));//记录日志 } } /// <summary> /// 日志操作命令 /// </summary> public class LogCommand: Command<Guid>, ICommand { /// <summary> /// 模拟文档内容数据容器 /// </summary> public Dictionary<Guid, string> LogContents { get; set; } = new Dictionary<Guid, string>(); private LogCommand() { } private Log _log; public LogCommand(Log log) { _log = log; } public void Execute() { //模拟持久化到数据容器中 try { _log.WriteLog(); LogContents.Add(CommandId, _log.Content); } catch (Exception ex) { throw ex; } } public void Redo() { //重新执行Execute方法 Execute(); } /// <summary> /// 事物操作,如果后面的操作发生异常,这里也需要回滚 /// </summary> public void Undo() { var value = ""; if (LogContents.ContainsKey(CommandId)) { value = LogContents[CommandId]; } else { Console.WriteLine("日志命令执行发生异常,当前命令Id:{0},当前日志信息:{1}", CommandId, JsonConvert.SerializeObject(_log));//记录日志 } if (!LogContents.Remove(CommandId)) Console.WriteLine("日志命令执行发生异常,当前命令Id:{0},当前日志信息:{1}", CommandId, JsonConvert.SerializeObject(_log));//记录日志 else Console.WriteLine("事物回滚,将插入到日志中的内容删除,被删除的内容是:{0}", value);//记录日志 } } /// <summary> /// 命令管理器 /// </summary> public class CommandManager { public Queue<ICommand> Commands = new Queue<ICommand>(); public Queue<ICommand> UndoCommands = new Queue<ICommand>(); public Queue<ICommand> SuccessCommands = new Queue<ICommand>(); /// <summary> /// 命令执行 /// </summary> public void Execute() { foreach (var command in Commands) { try { Console.WriteLine("命令开始执行,当前命令名称:{0}", command.GetType().Name);//记录日志 command.Execute(); Console.WriteLine("命令执行结束,当前命令名称:{0}", command.GetType().Name);//记录日志 Console.WriteLine(); SuccessCommands.Enqueue(command); } catch { Console.WriteLine("命令执行结束,当前命令名称:{0}", command.GetType().Name);//记录日志 Undo(command); Redo(); RollBack(); break; } } } public void Undo(ICommand command) { if (CanUndo) { UndoCommands.Enqueue(command); } else { Console.WriteLine("当前命令队列没有排队的命令!");//记录日志 } } /// <summary> /// 命令重试 /// </summary> public void Redo() { //这个最大重试次数,建议读取配置文件 var tryCount = 3; var time = 0; if (CanRedo) { var command = UndoCommands.Dequeue(); //开启一个新线程进行重试操作,重试3次,失败则发送邮件通知,或者记录日志 Task.Run(() => { var index = 1; while (true) { Interlocked.Add(ref time, index); try { command.Redo(); } catch (Exception ex) { if (time == tryCount) { Console.WriteLine("当前命令:{0},重试{1}次后执行失败,请检查原因!异常信息如下:{2}", typeof(DocumentCommand).Name, tryCount, ex.Message); break; } } } }); } } /// <summary> /// 事务回滚 /// </summary> public void RollBack() { Console.WriteLine(); if (SuccessCommands.Count > 0) { Console.WriteLine("事物发生异常,记录开始回滚!"); foreach (var command in SuccessCommands) { command.Undo(); } Console.WriteLine("事物回滚结束"); } else { Console.WriteLine("当前没有需要回滚的操作!"); } Console.WriteLine(); } private bool CanUndo { get { return Commands.Count > 0; } } private bool CanRedo { get { return UndoCommands.Count > 0; } } }
注:上面所有的Console.WriteLine都需要改成异步日志功能.异步重试中的Concosole.WriteLine因为Ms做了同步处理,所以输出可能会异常.所以异步写日志比较合理.
这里在提一点,如果需要实现多个命令组成一个复合命令,可以使用Composite组合模式将多个命令组成一个复合命令,来实现.后续的随笔中我会介绍.
文章中的代码有bug,或者不当之处,请在下面指正,感谢!
C# Command命令(行为型模式)+队列 实现事务,带异步命令重试机制和生命周期
标签:数据 var 使用 bool exe serialize 实战 插入 批量
原文地址:https://www.cnblogs.com/GreenLeaves/p/10203351.html