现在项目中事务处理是在DAO中手工控制的,必须首先获得连接,开启事务,成功提交事务,失败回滚事务。典型的代码结构如下:
using (var conn = ConnectionManager) { try { conn.BeginTransaction(); .... // excute SQL conn.Commit(); } catch (Exception e) { conn.Rollback(); throw e; } }
我对此感到很不舒服,代码中真正有用的只是执行SQL,至于打开连接、处理事务等操作在每个方法中都是一样的,在每个方法中重复这样的代码是典型的bad smell,于是我在BaseDAO中添加了ExecuteInTransaction方法,它接收一个Action委托,Action委托来执行SQL。ExecuteInTransaction实现如下:
protected void ExecuteInTransaction(Action dbOp) { using (var conn = ConnectionManager) { try { conn.BeginTransaction(); dbOp.Invoke(); conn.Commit(); } catch (Exception e) { conn.Rollback(); throw e; } } }
这样封装之后,要执行数据库事务就简单多了,对于最开始的代码现在就可以写成:
ExecuteInTransaction( () => { ... // excute SQL });
我觉得这样改动挺好,也没和技术经理商量,技术经理发现之后,却勒令我改回来。他给我的理由是这样性能太差,为此他专门找了一篇MSDN上的文章(Writing Faster Managed Code: Know What Things Cost )来证明这一点。这是他的惯用手段,每次他说服我改动代码时都会来拿性能来说事。我很不屑于他的这一点,我觉得开发效率和减少BUG才是优先考虑要素,大师Donald E. Knuth早就说过过早优化是万恶之源。但我还是改了,我的理由是“给代码带来了不一致性”,毕竟我没有理由要别人将他们的代码也改成这样。我坦承我一旦觉得原有代码写得不好,就很容易引发“重构”的冲动,但是我又不能改动别人的代码,这样就导致整个项目代码的不一致了。不晓得大家有没有这样的冲动?
到目前为止,我似乎说的是无关主题的废话,但别急,我马上就要进入正题了。既然要改代码,复制/粘贴是任何人都会用的方法,但我不想这样做。我改动的地方只有五六处,不多,可能复制/粘贴的速度会更快,但于我的技能却不会有任何提高,我喜欢有技术含量的偷懒。我早知道Visual Studio下有Code Snippet功能,可以自定义代码模板,但从来没有使用过,这次是个好机会。
第一步是选择模板目录,从菜单选择Tools -> Code Snippets Manager...,或者使用快捷键C+K,C+B。VS(Visual Studio)默认带有My Code Snippets目录,可以将自定义的代码模板放在其下,实际目录是Visual Studio 2008\Code Snippets\Visual C#\My Code Snippets。要添加一个新的模板目录,只需要点击"Add..."按钮,然后选择目录就可以了。我添加了一个叫做VS_CodeSnippet的模板目录。如图:
第二步就是创建代码模板。凡是在模板目录下文件名以.snippet结尾的文件都会认为是代码模板,代码模板是XML格式,需要遵守一定的格式规范。代码模板主要分为三部分,描述信息、变量声明和模板定义。描述信息置于Header部分,最重要的是Shortcut信息,当在VS中输入这个Shortcut,然后再按Tab键,就会用模板的内容来替换Shortcut。我这个模板的Shortcut定义为dao_execute_in_transaction。变量声明置于Snippet的Declarations下,我只定义了一个变量,取名为DBOperation。模板定义置于Snippet的Code下,模板中通过$TempVar$的形式来引用模板变量。以下是我这个模板的完整代码:
<?xml version="1.0" encoding="utf-8" ?> <CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet"> <CodeSnippet Format="1.0.0"> <Header> <Title>dao_execute_in_transaction</Title> <Shortcut>dao_execute_in_transaction</Shortcut> <Description>Code snippet for execute sql in transaction</Description> <Author>Marlon Yao</Author> <SnippetTypes> <SnippetType>Expansion</SnippetType> </SnippetTypes> </Header> <Snippet> <Declarations> <Literal> <ID>DBOperation</ID> <Default>sql</Default> <ToolTip>Transaction Code</ToolTip> </Literal> </Declarations> <Code Language="csharp"><![CDATA[using (var conn = ConnectionManager) { try { conn.BeginTransaction(); ExecuteNonQuery($DBOperation$); conn.Commit(); } catch (Exception e) { conn.Rollback(); throw e; } }]]> </Code> </Snippet> </CodeSnippet> </CodeSnippets>
我只用很少的功能,写时可以参考VS自带的模板。
最后就是使用代码模板。使用起来很简单,只需要输入代码模板中定义的Shortcut,对于我这个模板来说就是,dao_execute_in_transaction,输入时VS会自动给予提示(也可以手动键入C+K,C+W来补全)。
输入dao_execute_in_transaction,然后按Tab键,就会将dao_execute_in_transaction替换成模板内容,并且光标定位在第一个模板变量处。
如果定义了多个模板变量,按Tab键可以切换到下一个模板变量处,最后按Enter键进入正常编辑模式。
定义好代码模板之后,修改起来就简单多了,最重要的是它还可以在以后的编程中用到。我仍旧不喜欢这种代码重复的方式,但这却不是我要并且能关心的事。VS的Code Snippets功能很强大,我只使用很小一部分功能,但也却足以应付大部分情况了,对于它的高级功能等到碰到问题时再去了解。