ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

LIS系统实战:WPF+SQL Server下的条码扫码与状态流转设计

LIS系统实战:WPF+SQL Server下的条码扫码与状态流转设计 先讲一个我最近还在帮人排查的现场问题检验科窗口的同事用扫码枪连续扫了两支标本系统里只进了一条记录再扫第二支时界面没反应同事以为是机器卡了又补扫一次结果第三支标本的条码被重复登记最后只能靠人工核对把标本重新对上。这种问题在医院检验科的信息化系统里不算罕见表面上是“扫个码而已”实际上牵扯到条码规则、状态机、并发控制和输入设备特性一环扣一环。我做过几年LISLaboratory Information System检验信息系统相关项目日常工作就是跟WPF客户端、SQL Server数据库和一堆型号各异的检验仪器打交道。LIS听起来就是个“登记结果、打印报告”的系统真做起来才发现里面全是看似简单、实则暗藏玄机的功能点。本文从真实项目里扒几个典型场景出来说说用WPFSQL怎么实现以及这些实现背后到底在防什么坑。内容适合正在做或打算做LIS/MIS类系统的开发、实施和二次开发人员参考也适合刚接触医疗信息化的朋友读一读看看医院里这些“看不见”的软件到底是怎么工作的。1. LIS系统的业务流程与整体设计思路1.1 一条检验标本从开单到报告要过多少道关在开始讲编程细节之前得先理解LIS里最基本的一张“业务全景图”。一条检验标本的完整生命周期大概是这样的临床医生在HIS医院信息系统里开检验申请护士采集标本后贴上条码标本通过物流送到检验科检验科签收并核收然后分发到对应专业组上机检测仪器出结果后由检验技师审核审核通过后报告发布到临床。任何一个环节出了问题比如标本丢失、结果异常、报告未审核都要能追溯到具体节点。这个流程在数据库设计上通常对应一张标本主表加一个状态字段。我参与的项目里状态码用的是整数区间制10-19表示已采集相关20-29表示已核收相关30-39表示上机检测相关40-49表示审核相关90表示作废/退回。为什么不用连续数字1、2、3因为业务中间很可能要插入新状态比如“已离心”、“已复查”。如果一开始用连续数字后期插入一个状态就会导致整个状态码体系重映射历史数据全要跟着改。用区间制的话只要在属于该区间的空余编号里挑一个就行不需要动其他状态。这个经验是我们踩过坑之后总结出来的早期某套系统就是连续数字后来加了“已离心”状态前后花了两天去刷数据。除了当前状态字段还要单独建一张状态流转记录表字段大概是SampleId、OldStatus、NewStatus、Operator、OperateTime、Remark。状态流转表的核心价值在于追溯当临床打电话来说“这个报告到底谁审的为什么能发出去”的时候你不需要去翻日志文件直接查这张表就能还原整个操作链条。所以我一直坚持一个设计原则状态更新和流转记录写入必须在同一个数据库事务里完成缺了任何一边这个设计就等于没做。1.2 为什么WPFSQL Server这套组合在LIS里依然是主力现在一聊技术选型很多人第一反应是Web前端或者.NET MAUI跨平台。但在医院检验科的真实环境里WPFSQL Server依然是非常常见、甚至可以说是最务实的组合。首先检验科的工作站几乎全是Windows系统而且不少是老机器、老系统从Windows 7到Windows 11都有WPF在这类环境下的兼容性很稳不需要像Web前端那样去处理浏览器兼容问题。其次检验科有很多本地外设扫码枪、标签打印机、报告打印机、各种仪器通讯串口。WPF可以方便地调用串口类、打印组件和本地驱动接口这些在医院内网环境下比Web方案更直接。比如打印报告单WPF的FixedDocument和打印队列管理器用起来非常顺Web方案要折腾报表服务、PDF生成、浏览器打印设置维护成本明显更高。SQL Server在医院信息科里更是存量巨大我们接触过的医院从SQL Server 2008 R2到SQL Server 2019都有2016和2019最常见。所以标题里说的“WPFSQL”不是刻意选老技术而是医院内网这个大环境里最成熟、最容易维护的组合。这不是说Web技术和.NET MAUI不好而是LIS这类系统核心工作站短期内很难脱离Windows桌面环境WPF依然是非常可靠的选择。2. 场景一标本条码与流转状态——最容易被忽略的并发边界2.1 条码编号规则与Code128打印校验位和打印机偏移都要管先说说条码本身。LIS里最常见的标本条码编码规则是“标本类型代码2位日期8位当日流水号4-6位校验位”比如“XB20250115000123”这种样子。标本类型代码用来区分静脉血、末梢血、尿液、粪便等方便在物流分拣时按类型分流日期加流水号保证当天内条码唯一跨天流水号归零。加上校验位是为了防止手工输入时出现单字符错误常见做法是把前面所有字符按一定权重加权取模生成一位校验字符。这里有一个不起眼但很重要的细节条码号一旦生成并且打印到标签上原则上就不应该允许修改。如果录入错误或标签报废应该作废原条码并生成新条码而不是在原条码上做编辑。否则后续流转记录、标本追踪、报告关联全部都会出现错乱。我在项目里见过有同事为了省事直接修改条码号的结果报告关联到错误的患者这种错误在医疗场景里后果很严重。说回打印。WPF里生成Code128条码的方式有很多种有些项目用条码字体比如Code128.ttf有些用第三方控件有些直接调用服务端生成图片。用条码字体时要注意文本内容与字体的映射规则Code128有A/B/C三种字符集切换不正确打印出来的条码可能扫不出来。另外标签打印机的页面设置必须跟标签纸实际尺寸一致WPF的FixedPage在部分打印机驱动里会有边距偏移打出来条码可能被裁掉。我们踩过的坑是某款标签纸标称50mm宽实际有效打印区域只有47mm如果不按实际尺寸设置页面条码右侧数字会被裁掉导致扫描枪读不出来。解决方法是打印前用校准页测试一次把页面宽度设置成标签实际可用宽度。2.2 状态流转的SQL原子更新UPDATE加WHERE比先查后改可靠得多标本从“已核收”到“上机检测”这个状态切换看起来就是改一个数字但实际并发场景很多。检验科有多个专业组工作台可能同时扫描同一个条码进行分发或接收如果代码写成先SELECT当前状态再到程序里判断是不是20最后再UPDATE那么在第一步和第三步之间别的线程可能已经把状态给改了最后更新就会覆盖掉合法操作。正确做法是让数据库来保证状态迁移的原子性。核心就是用一条带条件的UPDATE语句UPDATE TestOrder SET Status 30, Operator user, OperateTime GETDATE() WHERE SampleNo sampleNo AND Status 20;这条语句执行后返回的影响行数如果在ADO.NET或Dapper里是1说明状态确实从20迁移到了30更新成功如果返回0说明当前状态已经不是20要么是已经被其他工作站处理了要么是状态根本就是错的这时程序要提示“该标本当前状态不允许此操作”绝对不能用UPDATE不带WHERE条件去强行覆盖。同样的思路也用在报告发布上。报告发布是LIS里比较敏感的操作发布后临床立马就能看到结果撤销和修改都会留下记录。所以发布动作也要加状态条件UPDATE TestResult SET PublishFlag 1, PublishUser user, PublishTime GETDATE() WHERE ReportId reportId AND PublishFlag 0;返回0时就要提示“报告已发布或已撤销请刷新后重试”。这种写法我称为“乐观锁式状态迁移”本质上就是利用数据库的行锁和条件更新来避免并发竞态比在应用层加锁简单得多性能也更好。因为WPF客户端是桌面程序同一台机器上不会开太多线程去抢同一条数据真正的并发来自多个工作站同时操作所以让数据库在更新时自动判断状态是最省心也最不容易出错的方式。2.3 扫码枪输入的特殊性与WPF界面的防呆设计扫码枪在Windows下默认模拟键盘输入扫完条码后会自动输入一串字符并附带一个回车键。这个特性在WPF开发里经常坑人。如果界面上有个TextBox绑定了TextChanged事件扫码枪每扫一个字符就触发一次事件代码里如果写的是实时查询那么一次扫码可能触发十几次查询把数据库打得一愣一愣的。正确的处理方式是捕获键盘事件等收到回车键后再触发后续逻辑。我常用的方案是给输入框挂PreviewKeyDown事件判断e.Key是否为Key.Enter如果回车键到了并且输入内容长度符合条码规则才执行查询/登记操作。同时还要用IsBusy标记或者SemaphoreSlim防止重复扫码因为有些扫码枪支持连续扫描第二次扫描事件触发时上一次数据库查询还没返回这时候如果直接处理就会出现开头提到的“连续扫了两支只进一条记录”甚至“重复登记”的问题。还有一个容易被忽略的细节输入法干扰。有些工作站会在WPF窗口里打开中文输入法扫码枪输入字符串时可能会被输入法截获导致条码内容变成中文乱码。解决方法是扫码输入框要禁掉输入法WPF里可以对TextBox设置InputMethod.IsInputMethodEnabledFalse或者在窗口初始化时关闭输入法扫描完成后再恢复。3. 场景二危急值自动推送——轮询、规则与防重复3.1 危急值规则表怎么设计才够灵活检验结果出现严重异常比如血钾高到危险水平、心肌标志物明显升高医学上要求检验科立刻通知临床医生这就是危急值Critical Value流程。LIS里要做的是在仪器结果出现并审核时自动判断是否命中危急值规则如果命中就触发推送通知。危急值规则不能写死在程序里因为每个医院的参考区间、每个项目的判定值都可能不同而且会随检验科主任和临床科室的沟通结果随时调整。所以必须建规则表我常用的结构是CREATE TABLE CriticalRule ( RuleId INT PRIMARY KEY, ItemCode VARCHAR(20), Gender VARCHAR(10), -- ALL/MALE/FEMALE MinAgeDays INT, MaxAgeDays INT, LowValue DECIMAL(18,4), HighValue DECIMAL(18,4), TextResult VARCHAR(50), -- 文本型结果专用 Priority INT, IsEnabled BIT );规则匹配的逻辑是按项目性别年龄区间找到对应的低值和高值区间。为什么年龄用最小天数/最大天数表示而不是用“新生儿/儿童/成人”这种枚举因为不同项目的年龄分层不一样比如有些项目的儿童参考上限是18岁有些是12岁枚举根本不够灵活。用天数区间需要在查询时把患者的出生日期换算成天数然后找到MinAgeDays ageDays MaxAgeDays的规则。数值型项目判断起来相对简单结果值低于LowValue或高于HighValue就命中。文本型结果就麻烦一些比如某些项目结果直接是“阳性”、“检出”、“1000”这种文本没法用数值区间判断。这种情况我在规则表里加了一个TextResult字段回传的结果字符串如果等于规则里的文本值也算命中危急值。需要说明的是文本型危急值规则在不同实验室的做法不完全一样以上是我在项目里采用并验证过的设计仅供参考。3.2 轮询任务的实现后台线程与WPF UI通知的配合危急值推送在不少LIS里做成了服务端服务但在一些规模较小的医院或者老项目中客户端定时轮询依然是简单可行的做法。我参与的项目里就有一个场景是检验科的大屏客户端用WPF跑着每隔30秒去查一次“最近5分钟有没有审核通过但是还没推送过的危急值结果”查到就弹窗报警。WPF里实现定时轮询有两种常见方式。一种是DispatcherTimer它的Tick事件在UI线程执行可以直接更新界面另一种是后台Task定时循环查询到结果后通过Dispatcher.BeginInvoke切回UI线程。我的经验是数据库查询这种耗时操作不要在DispatcherTimer的Tick里直接做否则查询一慢界面就卡死。更合理的做法是用Task.Run把SQL查询放到后台线程然后用Dispatcher.BeginInvoke把查询结果抛回UI线程做展示。轮询间隔不能设太短。有些刚入行的同事会把轮询间隔设为2秒觉得这样危急值能更快推出去。实际上医院内网没那么多危急值30秒轮询完全够用间隔太短只会增加数据库压力。而且SQL查询建议加时间窗口条件只查最近5分钟内审核通过的记录避免每次都去扫整个结果表。这里有一个WPF的经典坑后台线程直接修改ObservableCollection会触发异常。危急值弹窗列表如果是绑定到ObservableCollection上后台线程把查询结果Add进去界面会直接抛“调用线程无法访问此对象”。所以必须把集合更新也放进Dispatcher.BeginInvoke里。同理MVVM模式下可以借助消息机制把危急值对象作为消息发到UI层界面订阅消息后再更新集合。3.3 推送失败重试和状态标记别让临床护士的手机响个不停危急值推送最怕两个问题一是漏推二是重复推。漏推会导致临床没能及时处理属于医疗安全隐患重复推则是打扰临床把狼来了变成狼来了还来最后真正危急时反而没人重视。防重复的核心是给结果表加推送状态字段。我在结果表上设计了CriticalFlag是否命中危急值、PushFlag是否已推送、PushTime、PushCount这几个字段。推送动作的代码模式是这样的UPDATE TestResult SET PushFlag 1, PushTime GETDATE(), PushCount PushCount 1, PushUser user WHERE ResultId resultId AND PushFlag 0;返回1表示抢占成功这条危急值归你推了返回0表示别人已经推过或者正在推你什么都不用做。这样就避免了两个客户端同时查到同一条待推送数据、然后各推一次的问题。推送失败要有重试机制。比如大屏客户端弹窗如果操作员没有确认不能简单地再推一次。我设计的逻辑是推送动作写入一张推送记录表每次推送尝试都记录时间、推送端、接收端、状态。操作员确认推送后PushFlag保持不变但推送记录表会多加一条“已确认”记录状态机里明确“待推送-已推送-已确认”三个级别。如果推送接口超时Update会先把PushFlag改回0等下一次轮询再试但PushCount加1超过3次就归入“推送失败列表”由专人处理。这个流程不算复杂但能避免很多扯皮。4. 场景三Levey-Jennings质控图——不用图表库也能画4.1 质控数据表结构与分组聚合适配检验科每天都要做室内质控目的是确保仪器给出的结果稳定可靠。具体做法是用固定的质控品每天测一次或多次把测出来的值画到质控图上观察有没有超出统计学允许范围。这张图就是Levey-Jennings质控图横轴是时间纵轴是测量值中间画均值线上下画±1SD、±2SD、±3SD参考线。质控数据在数据库里可以存成一张表CREATE TABLE QCResult ( QcId INT PRIMARY KEY, ItemCode VARCHAR(20), QcLevel VARCHAR(10), BatchNo VARCHAR(30), InstrumentCode VARCHAR(10), ReagentBatch VARCHAR(30), MeasureValue DECIMAL(18,4), MeasureTime DATETIME, Operator VARCHAR(50) );这里最关键的是ReagentBatch字段。如果换了一盒新试剂它的靶值和SD区间可能和旧试剂完全不同质控数据如果混在一起算均值会被拉偏质控图会出现系统性漂移。我们项目前期没有按试剂批号分组结果换了新试剂后图上一连串点全部超出±2SD搞得检验科天天打电话问是不是仪器坏了。后来改成查询时强制Group By ReagentBatch并按批号分别计算均值和SD问题立刻消失。这个坑值得所有做LIS的人记住质控统计的分组维度必须包含试剂批号。4.2 用SQL算均值、SD和Westgard规则匹配计算均值可以用SQL Server的AVG函数标准差可以用STDEV函数。但这里有一个非常容易被忽略的细节SQL Server的STDEV计算的是样本标准差分母是n-1不是总体标准差。质控图的理论模型中如果我们计算的是当前批次全部数据通常期望的是总体标准差。两种计算方式在数据量大时差别很小但质控数据量小比如只有20个点时差别就不能忽略了。如果项目明确要求总体标准差就不能直接用STDEV而应该用平方和方式自己算SELECT AVG(MeasureValue) AS MeanValue, SQRT((SUM(MeasureValue * MeasureValue) - SUM(MeasureValue) * SUM(MeasureValue) / COUNT(*)) / COUNT(*)) AS PopulationStdDev FROM QCResult WHERE ItemCode itemCode AND ReagentBatch reagentBatch;这里要说明一下SUM(MeasureValue * MeasureValue)减去均值相关项再除以总数就是总体标准差的公式。在实际项目中到底用哪种口径一定要跟检验科确认清楚。有些检验科习惯用试剂说明书上给的靶值和SD来做质控图这时候根本不需要自己算均值SD只需要把说明书上的值手工维护进去然后图上画对应的参考线即可。所以一个灵活的质控模块应该支持两种模式自算均值SD和手工维护靶值。Westgard规则是判断质控是否失控的一组规则比如1-3s单个点超出±3SD属于失控1-2s属于警告2-2s连续两个点同方向超出±2SD属于失控R-4s同一水平两个点之差超过4SD也属于失控。在C#里实现这些规则不算复杂无非就是遍历质控图上的数据点按规则条件做布尔判断。但有一个容易漏的地方R-4s规则需要同一个质控品同一次检测能产生两个不同水平的质控值Level 1和Level 2如果某个项目只测一个水平R-4s规则应该自动禁用否则每次都会误报失控。我们的做法是把规则配置做成一条表里面写了每条规则适用的质控水平匹配不到就不执行。4.3 WPF绘制质控图的坐标计算与常见坑绘制质控图不一定要引第三方图表库。用WPF自带的Canvas加DrawingVisual完全能画出可用的质控图关键是坐标转换要对。假设Canvas高度是300像素纵轴显示范围是均值减去4个SD到均值加上4个SD。那么测量值value对应的Y坐标是double plotMargin 30; double plotHeight 300 - plotMargin * 2; double yMin mean - 4 * sd; double yMax mean 4 * sd; double y plotMargin (value - yMin) / (yMax - yMin) * plotHeight;这里要注意Canvas坐标系是Y轴向下为正方向所以值越大Y坐标越小。很多新手第一次画图就把点画反了质控图上高值跑到了下方。另外纵轴范围不能完全按照 [min, max] 来定否则超出±3SD的点正好戳在图的边缘失控看起来不明显。我习惯把范围设为实际点集的min/max向外再扩2个SD让异常点能被明显看到。数据点数量不多时比如几十个点用Ellipse控件是可以的但如果是上千个点每个点都创建一个UIElementWPF的渲染性能会明显下降。我的做法是重写Canvas的OnRender用DrawingContext一次性把所有参考线和数据点画上去这样性能好很多。如果要实现鼠标悬停显示点对应的日期和测量值可以在MouseMove事件里遍历数据点做距离判断或者用DrawingVisual的HitTest逻辑都不复杂。5. 场景四统计报表与慢SQL优化——查询写得烂再快的机器也扛不住5.1 三种常见统计需求的SQL写法对比检验科每天都要做工作量统计今天收了多少标本、每个专业组做了多少项目、每个开单科室送检了多少样本。这类查询在数据库里就是GROUP BY加COUNT看起来简单但表数据量大了之后写法差异对性能影响非常大。举一个典型需求按开单科室统计最近30天每天的标本量。最直接的写法是SELECT DeptName, CONVERT(VARCHAR(10), CreateTime, 120) AS Day, COUNT(*) AS SampleCount FROM TestOrder WHERE CreateTime DATEADD(DAY, -30, GETDATE()) GROUP BY DeptName, CONVERT(VARCHAR(10), CreateTime, 120) ORDER BY Day DESC;这个查询的问题在于CONVERT函数把CreateTime转成字符串后参与GROUP BY这个表达式没法直接使用CreateTime字段上的普通索引。数据量小没事到了几百万条记录就会明显变慢。更好的做法是用一个冗余的“日期”字段或者用范围分组比如先按天做子查询。SQL Server 2008 R2里没有原生的按天截断索引友好的函数常见的替代方案是维护一个DataDate字段插入数据时就把日期部分存进去查询时直接GROUP BY DataDate索引就能被有效利用。收入统计则要复杂一些因为一个标本可能对应多个收费项目涉及收费明细表和检验结果表关联。这里必须注意LEFT JOIN和INNER JOIN的语义区别如果只是统计数量INNER JOIN没问题如果要做应收金额汇总而且有些项目没有收费记录INNER JOIN会丢掉数据导致金额偏少。我们遇到过财务部门投诉当月收入比实际上报少了追查下来就是JOIN用错了后来改成LEFT JOIN并加上COALESCE(ChargeAmount, 0)才解决。月度趋势、累计工作量这类需求可以用窗口函数。SQL Server 2012以上支持SUM(COUNT(*)) OVER (ORDER BY ...)可以很方便地做累计值。但如果你的服务器还是2008 R2有些窗口函数不支持只能在C#里循环计算。这也会影响设计写SQL之前先确认服务器版本别把新写法写进老系统里。5.2 执行计划、索引设计和窗口函数实践慢SQL优化在LIS项目里是常客尤其是过了两三年数据量上来之后之前秒开的报表现在转十几秒。我的一般排查流程是先看执行计划找表扫描和键查找次数再结合查询条件看索引命中和缺失索引建议。常见的坑有这几类。第一类WHERE条件里的函数包裹导致索引失效。比如WHERE LEFT(CreateTime, 6) 202501这种写法会让CreateTime字段上的索引直接失效因为数据库要先对每一行套LEFT函数才能比较。正确写法是使用范围条件WHERE CreateTime 2025-01-01 AND CreateTime 2025-02-01。第二类OR条件导致索引失效。如果WHERE Status 1 OR Status 2这样写数据库可能放弃索引扫描全表。解决方法是改成IN (1, 2)或者在低版本SQL Server里拆成两条SQL用UNION合并。第三类状态字段的组合索引。LIS里高频查询往往是“按状态时间查询待办列表”比如待核收、待审核、待推送。这种查询索引应该建组合索引比如(Status, CreateTime)这样数据库既能按状态过滤又能按时间排序。单建Status索引效果有限因为同一状态下的数据可能非常多扫描量依然不小。如果是SQL Server 2016以上的版本报表类的历史明细表可以考虑列存索引。列存索引对需要扫描大量行的聚合查询提升非常明显。我参与的一个项目中结果明细表有500多万行查询某个月的项目量统计要十几秒给历史归档表建了列存索引后同样的查询变成两秒以内。但要注意列存索引不适合频繁单行插入更新的OLTP表所以通常是给归档表用而不是给正在用的主表用。5.3 报表导出的内存问题与缓存策略WPF里显示报表数据通常用DataGrid。数据量一大DataGrid渲染会变慢甚至卡死。我见过一个案例一张报表要显示两万多行DataGrid一次性绑定后界面直接假死。后来检查发现加载时把这两万行数据一股脑全部塞进ObservableCollectionDataGrid默认的虚拟化没有生效因为行高度被设成了自动导致UI线程要计算每行的高度卡得不行。解决办法一是DataGrid要启用EnableRowVirtualization和EnableColumnVirtualization行高不要设成Auto数据量大时用固定行高二是尽量分页加载或者只加载最近N天的数据而不是全量。报表不一定非要一次性展示所有数据用户真正需要的是快速看到前几百行并导出Excel。导出Excel的坑也不少。千万不要用服务器端COM组件去调Excel生成文件一是性能差二是服务器环境通常没装Office会直接报错。我们项目里用的方案是NPOI或者ClosedXML纯C#读写Excel不用依赖Office。导出大量行时建议用流式写入避免把所有单元格对象一次性加载到内存里。另外导出的Excel如果要在检验科上系统里做二次统计表头要固定、金额字段要设置成数字格式、日期字段要设置成日期格式不然临床说“导出来的数字不是数字”。这些都是很小的事但做不好就会被反复投诉。报表数据的另一层优化是缓存。有些报表查询频率高数据变化却不大比如“科室工作量月报”月中每天查结果都差不多。我习惯用内存缓存键是“统计维度统计时间范围”值为聚合结果缓存5到10分钟失效。要注意的是WPF客户端是桌面程序缓存只对当前客户端有效如果医院有多个后勤科室同时看报表数据会有几分钟的不一致。这个延迟一般都能接受但要在代码注释里写清楚免得后续维护的同事以为缓存逻辑是bug。6. 常见问题排查速查表与项目铁律6.1 高频故障现象与处理方案把我在LIS项目实际执行中遇到的高频问题整理一张表方便后来人直接对照排查。现象可能原因解决思路扫码后界面没反应或条码内容有中文输入法拦截了扫码枪输入扫码输入框禁用输入法回车键触发逻辑连续扫码只录到一条或多录重复上次扫码查询未结束新事件已触发用IsBusy标记或SemaphoreSlim防重入状态更新提示“不允许操作”其他工作站已改变状态使用UPDATE...WHERE原子更新并判断影响行数危急值重复推送两个客户端同时查到同一条待推送数据推送前用UPDATE抢占PushFlag危急值漏推查询条件用状态字段过滤但状态未及时更新检查状态迁移时间确保审核提交即更新状态质控图整体漂移混入了不同试剂批号的质控数据按试剂批号分组统计不要混算均值SD质控图高值点跑到图下方Canvas Y轴方向搞反注意Y轴向下为正转换时做取反处理报表查询很慢WHERE条件里函数包裹索引字段改写成范围查询或增加冗余日期字段DataGrid加载两万行假死虚拟化未启用或行高为Auto固定行高启用行/列虚拟化分页加载导出Excel报错服务器使用COM组件调用Excel改用NPOI/ClosedXML纯托管代码导出排查这类问题的时候最有用的工具其实是SQL Server Profiler或扩展事件。数据库里每秒执行了什么语句、耗时多少、返回多少行一抓一个准。很多“系统卡了”“功能没反应”的反馈最后定位到的都是某条SQL写法问题而不是WPF界面本身的bug。6.2 我们项目里沉淀下来的一些铁律做完几个LIS项目之后我总结出几条自己一直在遵守的规则写在这里供大家参考。第一所有敏感操作必须留痕。审核、发布、撤销、作废这些操作不仅改状态还要往操作日志表写一条完整记录包含操作人、操作工作站、操作时间、旧值、新值。哪怕当时觉得“这一步有必要吗”也先写上将来出问题的时候它就是你的救命稻草。第二状态迁移禁止回退。除非有专门的功能和权限控制否则一条标本的状态只能向前走不能从40跳回20。如果确实需要退回要生成一个新的流程记录而不是直接改原状态。第三先预览后打印。不管是条码标签还是报告单打印前一定先显示打印预览让操作员确认纸张和内容。直接打印的后果是标签纸浪费、报告单格式不对医院的反馈是“系统太烂了”。第四数据库连接用完就关。WPF桌面程序如果每个窗口都持有连接不释放很快会把连接池占满整个系统的其他模块也跟着卡。养成using用完即关的习惯或者使用Dapper等轻量ORM让连接生命周期可控。第五发布版本前先备份数据库和可执行文件。医院系统最怕发布出事故一旦线上更新后出了问题必须能快速回滚。我见过有实施人员在医院现场直接编译发布连备份都不做结果把线上系统弄崩了最后折腾了一个通宵才恢复。备份是上线流程的底线没有备份坚决不能动线上环境。7. 写在最后的一点个人体会做LIS这类医疗信息化系统跟做互联网产品最大的区别是你写的每一条UPDATE语句都可能直接影响患者的诊断和治疗流程。危急值没有及时推送、报告状态错乱、条码重复导致报告张冠李戴这些bug不是“体验不好”这么简单而是实打实的医疗安全隐患。所以做这个领域技术能力是一方面更重要的是能不能站在检验科技师和临床医生的角度去思考每一步操作的合理性。回到开头那个扫码问题它给我的教训是LIS里的每个“简单功能”背后都是业务规则、设备特性和边界条件的综合体。别看条码扫描只是把一串字符读进来要把扫码枪、输入法、并发状态、数据库事务和WPF界面这些环节理顺才能做到真正的稳定可靠。希望这几次项目的经验整理能帮正在做LIS或打算转行做医疗信息化的同行少踩一些坑。后面如果再碰到好玩的场景我再继续写。
返回列表