【C#|.NET】从细节出发(三) 逻辑层事务和page object模式
2014-07-17 12:29 熬夜的虫子 阅读(1958) 评论(0) 编辑 收藏 举报一. 业务逻辑层的事务问题
如果你的程序分层清晰并且系统禁用复杂存储过程,那么在DA中的职责比较单一。程序的逻辑通过BLL调用各种不同模块的DA来实现数据操作。如果当需要不同模块在一个事务的时候,问题就产生了。
如果你在bll引用System.Data...或者你在DA中穿插各种复杂逻辑的时候基本上你的工程已经不能算是好的程序了,
1. 使用TransactionScope
TransactionScope可以使代码块成为事务性代码。但是需要开通MSDTC权限,并且TransactionScope的性能问题也一直存在争议。
2. 阶段性提交
这个涉及到框架层次的修改,会单独开篇幅来说
3. 使用委托 将多模块提交汇聚在一起 和框架层次的阶段性提交殊途同归
这个就是设计上的问题,将其他模块的方法以委托方式传入DA。举个例子假如主线为消费记录ConsumeRecord,分支线为消费详细ConsumeRecordDetail和票据信息TravelTicket。其中TravelTicket完全为另一个服务模块对自己为黑盒。ConsumeRecordDetail对自己为白盒。
DA层
1 | public delegate BizResult< string > ArchiveTravelTicketDelegate( DbTransaction transaction, List< string > travelTicketIDList); |
主线服务BLL中,其中StartArchives为其他模块但是需要包含近事务的方法
1 2 3 4 | private BizResult< string > InsertConsumeRecord(ConsumeRecordEntity consumeRecordEntity, List<ConsumeRecordDetailEntity> consumeRecordDetails) { return consumeRecordArchiveTargetDA.DoArchive(consumeRecordEntity, consumeRecordDetails, new TravelTicketArchiveService().StartArchives); } |
主线服务DA中
1 2 3 4 | public BizResult< string > DoArchive(ConsumeRecordEntity consumeRecordEntity, List<ConsumeRecordDetailEntity> consumeRecordDetails, <br>ArchiveHelper.ArchiveTravelTicketDelegate archiveTravelTicket) { return ArchiveHandle(consumeRecordEntity, consumeRecordDetails, true , ConsumeRecordHandle, archiveTravelTicket); } |
1 2 3 4 5 6 | public BizResult< string > ConsumeRecordHandle(DbTransaction currentTransaction, ConsumeRecordEntity consumeRecordEntity, List<ConsumeRecordDetailEntity> consumeRecordDetails) { return ConsumeRecordHandle(currentTransaction, consumeRecordEntity, consumeRecordDetails, (x, y) => InsertConsumeRecordLog(currentTransaction, consumeRecordEntity, true , false ) , InsertConsumeRecord, consumeRecordDetailArchiveDA.InsertConsumeRecordDetail); } |
其中InsertConsumeRecordLog,InsertConsumeRecord为主线自己的方法,ConsumeRecordDetail因为白盒可以直接使用consumeRecordDetailArchiveDA.InsertConsumeRecordDetail,archiveTravelTicket为黑盒。
主线BaseDA中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | public BizResult< string > ArchiveHandle(ConsumeRecordEntity consumeRecordEntity, List<ConsumeRecordDetailEntity> consumeRecordDetails, bool isArchive, ConsumeRecordHandlerDelegate consumeRecordHandlerDelegate, ArchiveHelper.ArchiveTravelTicketDelegate archiveTravelTicket) { using (DbConnection dbConnection = DbObject.CreateConnection()) { dbConnection.Open(); BizResult< string > bizResult; using (DbTransaction currentTransaction = dbConnection.BeginTransaction()) { try { bizResult = consumeRecordHandlerDelegate(currentTransaction, consumeRecordEntity, consumeRecordDetails); if (bizResult.IsSuccessful) { if (archiveTravelTicket != null ) { bizResult = archiveTravelTicket(currentTransaction, consumeRecordDetails.Select(x => x.TravelMoneyID). Distinct().ToList()); } if (bizResult.IsSuccessful) { bizResult.Message = "xxx success" ; currentTransaction.Commit(); return bizResult; } bizResult.Message = "xxx fail" ; } bizResult.Message = string .Format( "{0}:{1}" , isArchive, bizResult.Message); currentTransaction.Rollback(); } catch (Exception ex) { logger.Error(ex); currentTransaction.Rollback(); throw ; } } return bizResult; } } |
二. 测试环节的page object模式
拿自动化测试Selenium为例,一般而言我们对于程序都是应付测试用例,针对测试用例写出一个一个test-method。这样无可厚非,使用page object模式可以让代码开起来更专业更精彩。
在自动化测试中,主要关注UI的测试互动区域。 Page对象只是这些模型作为测试代码中的对象可以减少了重复的代码量,并且意味着,如果用户界面的变化,也只需要修改一个地方。和软件设计模式中dry(don't repeat yourself)类似。并且在page object模式中,PageObjects也可以实现页于页之间的逻辑。总得来说减少了代码的重复,让测试更具可读性和强大的,提高了测试的可维护性,特别是当有频繁变化的ui变更。
举个列子(java版本)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | public class LoginPageTest { private LoginPage loginPage; private final String username = "xx@163.com" ; private final String pwd= "Welcome1" ; @Before public void setUp() throws Exception { String applicationRoot = new File( getClass().getProtectionDomain().getCodeSource().getLocation().getPath()) .getParent(); System.setProperty( "webdriver.chrome.driver" , applicationRoot + "/chromedriver" ); WebDriver webDriver = new ChromeDriver(); loginPage = new LoginPage(webDriver); loginPage.init(); } @Test public void testLoginWithoutFakeCheckbox() { loginPage.setUserName(username); loginPage.setPassword(pwd); loginPage.pressLoginButton(); assertThat(loginPage.getClassByLoginWithoutFakeCheckbox(), equalTo( "checkbox-wrapper not-checked" )); } @Test public void testLoginWithWrongUsername() { loginPage.setUserName( "wrongusername" ); loginPage.setPassword(pwd); loginPage.pressCheckBox(); loginPage.pressLoginButton(); assertThat(loginPage.getClassByLoginWithWrongUserName(), equalTo( "wrapper-input-text wrong" )); } @Test public void testLoginWithNullPwd() { loginPage.clearInput(); loginPage.setUserName(username); loginPage.pressCheckBox(); loginPage.pressLoginButton(); assertThat(loginPage.getClassByLoginWithNullPwd(), equalTo( "wrapper-input-text wrong" )); } @Test public void testLoginWithRightWay() { loginPage.clearInput(); loginPage.setUserName(username); loginPage.setPassword(pwd); loginPage.pressCheckBox(); loginPage.pressLoginButton(); assertThat(loginPage.getTextByRightWay(), equalTo( "选择性别" )); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 | public class LoginPage { private final WebDriver webDriver; private final WebDriverWait wait; private JavascriptExecutor js; private final String username = "xx@163.com" ; private final String pwd = "Welcome1" ; private WebElement webElementLogin; private WebElement webElementPwd; private WebElement webElementInput; public LoginPage(WebDriver webDriver) { this .webDriver = webDriver; this .wait = new WebDriverWait(webDriver, 10 ); this .js = (JavascriptExecutor) webDriver; } public void init() { webDriver.get( "http://root:root@localhost:8080/apitool/xxx/owF3PjkBFY5_56Q_KYs5fxmLZTKI" ); WebElement webElementName = wait.until(ExpectedConditions.visibilityOfElementLocated(By.cssSelector( "p.text.bold.name" ))); if (webElementName != null ) { System.out.print(webElementName.getText()); } webElementLogin = webDriver.findElement(By.cssSelector( "input.input-text.login-input" )); webElementLogin.sendKeys(username); webElementPwd = webDriver.findElement(By.cssSelector( "input.input-text.password-input" )); webElementPwd.sendKeys(pwd); } public String getClassByLoginWithoutFakeCheckbox() { WebElement webElementCss = wait.until( ExpectedConditions.visibilityOfElementLocated( By.xpath( "//*[contains(@class,'checkbox-wrapper not-checked')]" ))); WebElement webElementHref = webDriver.findElement(By.xpath( "//*[@id=\"login-page\"]/div/section/div/div[5]/div/div" )); return webElementHref.getAttribute( "class" ); } public String getClassByLoginWithWrongUserName() { webElementInput = webDriver.findElement(By.xpath( "//*[@id=\"login-page\"]/div/section/div/div[3]" )); return webElementInput.getAttribute( "class" ); } public String getClassByLoginWithNullPwd() { webElementInput = webDriver.findElement(By.xpath( "//*[@id=\"login-page\"]/div/section/div/div[3]" )); return webElementInput.getAttribute( "class" ); } public String getTextByRightWay() { GenderViewPage genderViewPage = new GenderViewPage(webDriver); return genderViewPage.getGenderText(); } public void pressCheckBox() { js.executeScript( "$('.fake-checkbox').attr('class','fake-checkbox active')" ); } public void clearInput() { webElementLogin.clear(); webElementPwd.clear(); } public void setUserName(String username) { webElementLogin.clear(); webElementLogin.sendKeys(username); } public void setPassword(String password) { webElementPwd.clear(); webElementPwd.sendKeys(pwd); } public void pressLoginButton() { js.executeScript( "$('.login-button').trigger('touchstart')" ); } public void closeBrowser() { webDriver.quit(); } } |
三. sql update 慎用位运算
update t_User set valye = Flag | 4 where UserID = 1
如果Flag字段中存在负数,在做位运算的时候,更新出非常大的数值,超过2的62次方
希望对大家有帮助。
![]() |
原创作品允许转载,转载时请务必以超链接形式标明文章原始出处以及作者信息。 作者:熬夜的虫子 点击查看:博文索引 |
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· PostgreSQL 和 SQL Server 在统计信息维护中的关键差异
· C++代码改造为UTF-8编码问题的总结
· DeepSeek 解答了困扰我五年的技术问题
· 为什么说在企业级应用开发中,后端往往是效率杀手?
· 用 C# 插值字符串处理器写一个 sscanf
· 为DeepSeek添加本地知识库
· 精选4款基于.NET开源、功能强大的通讯调试工具
· DeepSeek智能编程
· 大模型工具KTransformer的安装
· [计算机/硬件/GPU] 显卡