当前位置: 首页 > article >正文

C#事务处理最佳实践:别再让“主表存了、明细丢了”的破事发生

大家好我是刚子。做业务开发的时候经常遇到一个操作要同时更新好几张表的情况。比如保存一张单据既要写主表又要写明细还得写关联条件。这种场景下要么全部成功要么全部失败绝对不能出现“主表存上了明细丢了”这种半截子事儿。怎么保证用事务Transaction。今天刚子就拿一个“凭证规则保存”的真实例子跟你聊聊TransactionScope怎么用、try-catch放哪儿最合适顺便总结一套能直接抄的最佳实践。1. 业务场景保存一个“凭证规则”我们要保存的东西长这样一条主表记录fin_voucher_rule_master多条明细记录fin_voucher_rule_detail每条明细下面还有多条条件记录fin_voucher_rule_condition保存有两种情况新增主表 ID 为 0插入主表、明细、条件。更新主表 ID 不为 0。更新主表字段然后删掉旧的明细和条件再重新插入新的这叫“全量替换”模式。要求很明确要么全成要么全败不允许半截数据。2. 最终代码可以直接用的版本下面就是优化后的 Service 层代码。你先看一遍后面我会拆开讲每一块是干啥的。public static OperationResult SaveVoucher_Rule_Master(fin_voucher_rule_master master){ using (var scope new TransactionScope()) { try { // ----- 1. 新增或更新主表 ----- if (master.Id 0) { // 新增主表 int id DAL.VoucherRuleMasterDAL.Add(master); if (id 0) return OperationResult.Fail(添加主表失败); master.Id id; // 把数据库生成的自增ID回填到实体 } else { // 更新模式先查出旧的完整数据含明细和条件 var existingMaster DAL.VoucherRuleMasterDAL.GetFin_Voucher_Rule_Master(master.Id); if (existingMaster null) return OperationResult.Fail(未找到要更新的记录); // 更新主表字段把前端传的值赋给查出来的实体 existingMaster.business_type master.business_type; existingMaster.rule_code master.rule_code; // ... 其他字段赋值省略 bool updateOk DAL.VoucherRuleMasterDAL.EditFin_Voucher_Rule_Master(existingMaster); if (!updateOk) return OperationResult.Fail(更新主表失败); // 删除旧明细下的所有条件 Listint conditionIds existingMaster.fin_voucher_rule_detail .SelectMany(d d.fin_voucher_rule_condition) .Select(c c.Id).ToList(); if (conditionIds.Any()) { bool delCondOk DAL.VoucherRuleMasterDAL.DeleteFin_Voucher_Rule_Conditions(conditionIds); if (!delCondOk) return OperationResult.Fail(删除明细条件失败); } // 删除旧明细 Listint detailIds existingMaster.fin_voucher_rule_detail.Select(d d.Id).ToList(); if (detailIds.Any()) { bool delDetailOk DAL.VoucherRuleMasterDAL.DeleteFin_Voucher_Rule_Details(detailIds); if (!delDetailOk) return OperationResult.Fail(删除明细失败); } } // ----- 2. 插入新的明细和条件 ----- foreach (var detail in master.fin_voucher_rule_detail) { detail.rule_id master.Id; // 关联主表ID int detailId DAL.VoucherRuleMasterDAL.AddFin_Voucher_Rule_Detail(detail); if (detailId 0) return OperationResult.Fail(添加明细失败); // 把明细ID回填到每个条件 foreach (var condition in detail.fin_voucher_rule_condition) { condition.detail_id detailId; } bool addCondOk DAL.VoucherRuleMasterDAL.AddFin_Voucher_Rule_Conditions(detail.fin_voucher_rule_condition.ToList()); if (!addCondOk) return OperationResult.Fail(添加明细条件失败); } // ----- 3. 所有操作成功提交事务 ----- scope.Complete(); return OperationResult.Success(保存成功); } catch (Exception ex) { // 事务会自动回滚因为没调用 Complete // 这里记录详细日志示例省略正式项目必须加上 return OperationResult.Fail(系统错误请稍后重试); } }}补充说明DAL 层方法返回int新增时返回自增ID或bool更新/删除是否成功并且不会在内部吞掉异常要么向上抛要么返回明确状态。3. 核心设计要点看完就懂为什么要这么写3.1TransactionScope怎么用才正确必须包在using块里这样不管代码正常结束还是抛异常事务资源都能被释放。只有所有操作成功后才调用scope.Complete()如果中途任何一步失败直接returnusing块结束时事务会自动回滚。不需要手动写Rollback没调用Complete就等于回滚代码更干净。3.2try-catch应该放在事务里面还是外面答案是放里面就像上面的代码一样。理由放里面可以捕获异常后做额外操作比如记日志记到非事务表或者写文件。放里面能明确区分“业务逻辑返回失败”和“系统异常”。前者用OperationResult.Fail正常返回后者用catch兜底。如果把try-catch包在整个using外面效果也差不多但你在catch里想访问事务相关的资源就不太方便。所以推荐把catch放在using块内部。3.3 每个 DAL 调用的返回值都要检查不能偷懒DAL 返回0或false表示操作失败。Service 层必须挨个检查一旦失败就立即停止并返回错误。这是保证事务原子性的关键。如果不检查失败了还继续往下走最后调用Complete()就会导致“部分成功”的灾难。你看代码里每一处 DAL 调用后都有if (!ok) return Fail(...)这就是守卫代码。3.4 主键回填小细节大作用新增主表后数据库生成的自增 ID 必须马上赋给master.Id。因为后面插入明细的时候需要detail.rule_id master.Id。如果你忘了回填master.Id还是 0明细就关联不上了。3.5 导航属性要提前加载否则会踩坑在更新模式里我们调用了GetFin_Voucher_Rule_Master(master.Id)。这个方法内部必须用Include把明细和条件一起查出来否则后面访问existingMaster.fin_voucher_rule_detail的时候可能会因为 DbContext 已经释放或者延迟加载失败而报错。正确的 DAL 写法类似public static fin_voucher_rule_master GetFin_Voucher_Rule_Master(int id){ using (var db new PcbEntities()) { return db.fin_voucher_rule_master .Include(x x.fin_voucher_rule_detail.Select(d d.fin_voucher_rule_condition)) .FirstOrDefault(x x.Id id); }}3.6 不要把异常消息直接扔给用户catch块里返回系统错误请稍后重试而不是ex.Message。这样做有两个好处不泄露敏感信息比如 SQL 语句、数据库连接串。用户体验好看到一堆英文报错会慌。详细的异常信息应该记到日志文件或日志表里方便你自己排查问题。4. 进阶建议让代码更专业4.1 指定事务隔离级别TransactionScope默认的隔离级别是Serializable性能差还容易死锁。建议显式指定为ReadCommittedvar options new TransactionOptions{ IsolationLevel IsolationLevel.ReadCommitted, Timeout TimeSpan.FromMinutes(1)};using (var scope new TransactionScope(TransactionScopeOption.Required, options)){ // ...}4.2 避免循环里多次SaveChanges当前代码中每个明细插入都单独调用一次AddFin_Voucher_Rule_Detail内部会SaveChanges。如果明细数量很多会产生大量数据库往返。优化方案写一个批量插入方法接收Listfin_voucher_rule_detail内部只调用一次SaveChanges。4.3 并发冲突怎么处理更新模式下如果两个人同时改同一条规则后提交的人会直接覆盖前面人的修改。解决办法加一个乐观锁字段比如RowVersion更新时检查版本号如果不一致就抛异常让用户刷新后重试。5. 总结事务与异常处理的最佳实践清单实践项说明使用using (TransactionScope)确保资源释放和自动回滚把try-catch放在事务块内部方便异常后做补偿操作所有数据库操作成功后调用scope.Complete()否则事务自动回滚检查每个 DAL 调用的返回值避免静默失败导致部分提交不要向外抛出内部异常消息用户只看到友好的通用提示记录详细异常日志方便你自己排查问题显式指定合理的隔离级别提升并发性能预加载需要的导航属性避免 DbContext 释放后延迟加载失败照着这个清单写代码你的数据持久层会既健壮又好维护。本文的凭证规则保存例子是一个典型的“主表明细子明细”事务场景。掌握了这些技巧类似的需求你都能轻松拿下。如果你觉得有用点个赞转发给还在被事务折磨的兄弟。我是刚子一个写了六年 .NET 代码的程序员。咱们下回见#csharp #事务处理 #transactionscope #trycatch- - 推荐关注「CSharp精选营」提升编程技能 精选推荐 点击标题可跳转使用 C# 实现23种常见的设计模式 C# WinForms 实现打印监听组件一个基于 .NET 开源、简易、轻量级的进销存管理系统ASP.NET Core Blazor简介和快速入门一基础篇ZR.Admin.NET为.NET开发者打造的效率利器一站式企业级后台开源框架ML.NET 快速入门与实践教程机器学习框架OpenClaw现象级爆红AI智能体的“事实标准”如何改变我们的开发方式.NET面试经典三问什么是.NET、.NET Framework、.NET Core?及相关引申问题都是微软亲儿子WPF凭啥干不掉WinForm这3个场景说明白了点赞和在看就是最大的支持❤️

相关文章:

C#事务处理最佳实践:别再让“主表存了、明细丢了”的破事发生

大家好,我是刚子。做业务开发的时候,经常遇到一个操作要同时更新好几张表的情况。比如保存一张单据,既要写主表,又要写明细,还得写关联条件。这种场景下,要么全部成功,要么全部失败,…...

YOLO26 改进、魔改| 通道-空间注意力与密集多尺度特征融合模块CSDF,通过融合通道注意力、空间注意力和多尺度空洞卷积,增强特征表示能力,提升模型对复杂场景下多尺度目标的识别与分割性能。

遥感图像语义分割任务中面临的三大核心挑战:尺度变化剧烈、类间光谱相似性高、以及空间上下文复杂。传统的卷积神经网络虽能提取局部特征,但其感受野有限,难以建模长距离依赖与多尺度目标;而基于Transformer的方法虽能捕获全局信息…...

Nano-Banana Studio实战案例:输入‘Backpack‘生成极简纯白风平铺拆解图

Nano-Banana Studio实战案例:输入Backpack生成极简纯白风平铺拆解图 1. 案例背景与工具介绍 今天我要分享一个特别实用的AI设计工具实战案例——使用Nano-Banana Studio一键生成背包的极简纯白风格平铺拆解图。 Nano-Banana Studio是一个基于Stable Diffusion XL…...

鱼音频生成 API 集成指南

在这篇文章中,我们将介绍如何集成鱼音频生成 API,该 API 能够通过输入提示词来克隆您的声音。这项技术的应用场景包括语音合成、自动化语音助手、以及任何需要个性化语音输出的应用。 环境准备 在使用鱼音频生成 API 之前,您需要先申请相应…...

EcomGPT-7B多语言模型实战:用同一模型服务中国工厂(中文)与海外买家(英文)

EcomGPT-7B多语言模型实战:用同一模型服务中国工厂(中文)与海外买家(英文) 如果你在做跨境电商,一定遇到过这样的麻烦:工厂给的商品信息是中文的,一堆参数混在一起,而你…...

Java抽象类深度解析(面试必备)

抽象类是Java面试中高频考点,理解它的本质与使用场景,能让你在面试中脱颖而出。本篇文章将从概念、原理、示例到面试高频问题,全方位解析抽象类。 ⏱ 30秒快速回答 抽象类是使用 abstract 修饰的类,不能被实例化,可以…...

测试功能指南 富文本

你好!看起来你输入了“test”,是在测试功能吗?😊 如果有什么具体问题、需要帮助的地方,或者想了解某方面的信息(比如学习、生活、科技、健康等),欢迎随时告诉我,我很乐意…...

Docling Studio 开发札记

当我开始构建 Docling Studio 时,目标很简单:为开发者提供一种可视化方式来检查 Docling 从文档中提取的内容。边界框、分块、元数据——你需要看到才能信任流水线的那些东西。 但任何构建过 RAG 系统的人都知道,真正的问题不在于提取。而在…...

软件可用性管理中的MTTR优化

软件可用性管理中的MTTR优化:提升系统可靠性的关键策略 在数字化时代,软件系统的可用性直接影响用户体验和业务连续性。平均修复时间(MTTR)是衡量系统可靠性的核心指标之一,它反映了从故障发生到问题解决所需的平均时…...

曦智科技开启招股:最高估值160亿港元 4月28日上市 阿里高瓴淡马锡加持

雷递网 雷建平 4月20日上海曦智科技股份有限公司(简称:“曦智科技”,股票代码:“01879”)今日开启招股,准备2026年4月28日在港交所上市。曦智科技发行区间为166.60港元至183.2港元,计划发售约13…...

从‘欠拟合’到‘过拟合’:手把手用AdaBoostRegressor可视化理解集成学习的拟合过程

从‘欠拟合’到‘过拟合’:用AdaBoostRegressor可视化集成学习的拟合演变 当第一次接触机器学习中的集成学习概念时,很多人会被"弱学习器组合成强学习器"的说法所困惑。究竟这些弱学习器是如何协同工作的?为什么增加学习器数量有时…...

PyQt5安装及学习

学习目标 因为毕设需要,所以今天网上学习一下。做一个建议界面,或者后续可以借鉴ai做一下。 pyqt5安装 (yolov8) PS E:\pycharm\2024.11.28open3d> pip install pyqt5-tools -i https://pypi.tuna.tsinghua.edu.cn/simple Looking in indexes: http…...

告别花屏!用Arduino TFT_eSPI库驱动SPI LCD显示中文的保姆级避坑指南

告别花屏!用Arduino TFT_eSPI库驱动SPI LCD显示中文的保姆级避坑指南 第一次点亮SPI接口的LCD屏幕时,那种兴奋感就像打开了新世界的大门。但随之而来的花屏、乱码、内存溢出等问题,又让人瞬间跌入谷底。作为过来人,我完全理解这种…...

计算机网络习题及答案

仅供参考第一章 概述1、计算机网络可以向用户提供哪些服务?答:基于互联网的连通性和共享,计算机网络可以向用户提供:①信息交换服务,如电子邮件(收发信息)、文件传输(上传和下载大文…...

保姆级教程:用Python串口和GBK编码玩转SYN6288 TTS模块(附完整代码)

Python全平台串口控制SYN6288语音合成模块实战指南 第一次听到SYN6288发出清晰的中文语音时,那种"机器开口说话"的奇妙感至今难忘。作为一款性价比极高的中文TTS模块,SYN6288通过简单的串口指令就能实现高质量的语音合成,特别适合智…...

深信服AC1000-B1200到手第一步:从开箱到激活上网的保姆级图文指南

深信服AC1000-B1200设备开箱配置全流程实战手册 当你第一次拿到深信服AC1000-B1200这台企业级网络设备时,可能会被它专业的接口阵列和指示灯搞得有些不知所措。作为一款广泛应用于企业网络边界的安全网关设备,它的初始配置确实需要一些专业指导。本文将带…...

C# 14 AOT编译Dify客户端:从.NET 8到.NET 9 Preview 5,实测启动速度提升92%的5步极简流程

第一章:C# 14 AOT编译Dify客户端:技术演进与价值定位C# 14 引入的原生AOT(Ahead-of-Time)编译能力,标志着.NET平台在云原生与边缘计算场景中迈出了关键一步。当这一能力被应用于构建Dify服务的轻量级客户端时&#xff…...

终极指南:如何用thermalmonitordDisabler解锁iPhone性能限制

终极指南:如何用thermalmonitordDisabler解锁iPhone性能限制 【免费下载链接】thermalmonitordDisabler A tool used to disable iOS daemons. 项目地址: https://gitcode.com/gh_mirrors/th/thermalmonitordDisabler 你是否曾在玩游戏时突然卡顿&#xff1f…...

理解「响应式编程」在Spring WebFlux中的应用

响应式编程在现代高并发系统中扮演着重要角色,而Spring WebFlux作为Spring生态中的响应式框架,为开发者提供了处理异步非阻塞请求的强大工具。理解响应式编程在WebFlux中的应用,不仅能提升系统性能,还能优化资源利用率。本文将围绕…...

如何免费解锁八大网盘全速下载:2025年终极直链下载助手完整指南

如何免费解锁八大网盘全速下载:2025年终极直链下载助手完整指南 【免费下载链接】Online-disk-direct-link-download-assistant 一个基于 JavaScript 的网盘文件下载地址获取工具。基于【网盘直链下载助手】修改 ,支持 百度网盘 / 阿里云盘 / 中国移动云…...

解决Leaflet加载天地图的最大痛点:突破17级缩放限制的两种实战方案

突破Leaflet中天地图17级缩放限制的工程实践 第一次在项目中集成天地图时,那种流畅的加载体验让人印象深刻——直到用户突然问:"为什么这个区域无法继续放大了?"这才发现Leaflet默认的17级缩放限制成了项目交付的绊脚石。作为国内主…...

别再纠结无损格式了!手把手教你用Foobar2000搭配ASIO/WASAPI,榨干Windows电脑的HiFi潜力

解锁PC音质天花板:Foobar2000ASIO/WASAPI实战指南 当大多数人还在纠结是否要花大价钱升级音响设备时,Windows电脑其实隐藏着未被发掘的音频潜力。通过正确的软件配置,你的普通PC也能释放出接近专业级音频设备的音质表现。本文将带你深入探索如…...

别只盯着性能!从RapidJSON和cJSON的源码设计,聊聊C/C++ JSON库的‘优雅’与‘实用’

从RapidJSON与cJSON的源码哲学,解码C/C JSON库的设计艺术 在技术选型时,我们常常被性能指标和功能列表所吸引,却忽略了背后更为重要的设计哲学。RapidJSON和cJSON作为C/C领域最具代表性的两个JSON库,它们的差异远不止于性能数据表…...

ODM(原始设计制造商)模式,本质上是“赚辛苦钱

结合你掌握的信息(ODM模式、大小周、整机等),以下是从职业发展、行业环境、公司治理三个维度的批判性分析与建议: 1. 业务模式的“护城河”与“天花板” (ODM vs. 自有品牌) 批判性分析:ODM(原始设计制造商)模式,本质上是“赚辛苦钱”。虽然公司想做“整机”,但如果没…...

2026跨行业通吃的经管类证书。

先说明一下,这篇文章是我自己这几年在经管专业学习和求职过程中接触到的一些信息整理,每个人情况不同,我说的不一定对,仅供你参考。经管类专业有个特点——看起来就业面很广,银行、互联网、快消、咨询、公务员都能试试…...

Linux环境搭建及基础指令

Xshell 登录主机打开Xshell后, 输入指令 ssh root[自己云服务器的公网地址]输入登录名(一般就是root)及密码后, 看到以上提示, 就说明登陆成功啦!Xshell下的复制粘贴复制: Ctrll Fn insert粘贴: shift Fn insertLinux下的基本指令在学习具体指令前, 得先创建一个框架, 才能…...

杰理SDK开发-杰理之家-实现清除手机APP用户配置功能、重置参数

前言现在为止也开发了许多杰理TWS蓝牙耳机、音响项目SDK的案子,在调试案子时不断的向前辈们学习到了很多关于蓝牙音响、蓝牙TWS耳机专业的知识。想在这里做一个学习汇总,方便各位同行和对杰理芯片SDK感兴趣的小伙伴们学习;本章详细讲解杰理SD…...

Dify 2026缓存机制升级全解析,为什么你的Agent响应慢了3.8倍?(附12个真实压测对比数据)

第一章:Dify 2026缓存机制升级的核心动因与架构演进 Dify 2026 的缓存机制重构并非简单性能调优,而是面向多租户大模型应用平台在高并发、低延迟、强一致性场景下的系统性演进。随着用户侧 RAG 流程平均响应时间突破 850ms,以及 LLM 编排链路…...

Elasticsearch 实战:使用 boost 参数提高字段相关性得分(全文检索权重优化)

Elasticsearch 实战:使用 boost 参数提高字段相关性得分(全文检索权重优化)前言Elasticsearch boost 参数:提高字段相关性权重完整实战一、核心概念:boost 参数是什么?1.1 定义1.2 作用1.3 boost 工作流程图…...

跨境社媒运营别只盯热点 真正能沉淀价值的是栏目化输出

很多团队做跨境社媒时,最容易形成一种惯性: 看到热点就追,看到同行起量就拆,看到某种内容形式火了就立刻跟上。这种方式前期确实有效。 因为热点自带关注度,借势也更容易拿到第一波流量。 但问题是,热点能解…...