Hi.Ltd.SqlServer 专题 · 精简加深版第 1 期|合并旧稿第1+2期
NuGet:Hi.Ltd.SqlServer 2026.9.7.1642
目标框架:net462/net481/net6.0-windows7.0/net8.0-windows7.0
依赖:Hi.Ltd(Result 拼写Successed,载荷Content)、Microsoft.Data.SqlClient(≥7.0.2);Windows TFM 另含 SMO
读者:工控上位机 / HMI 宿主 / 配方·工单·报警·班次 / 采集落库 / MES 线边库
本期目标
把一条产线从「开机连库」走到「工单落库可查」整条通路钉死在 2026.9.7.1642 公开面上:
- 分清
SqlEntities(显式实例) 与Factory(全局句柄 + 模型同步 + 自愈 CRUD) 两条入口,知道各自该用在什么工位。 - 所有连接 / DDL / DML 按 XML 使用
Result/Result<T>,成败看Successed(不是 Succeeded),业务值看Content。 - 用
ConfigureSqlServer→LoadModelAssembly→InitialSqlServer(或最小路径CreateDatabase+CreateTable+InsertTable)完成首条工单写入。 - 理解
[Auto]何时适合原型热补结构、何时必须改成部署期一次同步;知道SchemaSyncOptions生产默认该怎么关风险开关。 - 对照「手写 ADO / 散落 SQL」看清本包在上位机场景里真正省掉的样板与风险点。
读完你应能在 WinForm / WPF / 触摸屏工控机项目里,复制本期完整样例,改连接参数后直接跑通「建库 → 建工单表 → 插入 → Inqure 回读」。
适用范围
| 场景 | 为什么适合 Hi.Ltd.SqlServer |
|---|---|
| WinForm / WPF / 嵌入式触摸屏上位机落库 | POCO + 特性映射,建表/CRUD/TVP/过程一体,少写 ADO 样板 |
| 配方、工单、报警、班次、采集点位元数据 | 后续可用内置 Tables.Ics.* / Tables.Sys.*;本期先自建 POCO 跑通 |
| 与 MES / 线边库共用 SQL Server | Factory 多 Profile、InitialSqlServer / SchemaStartupAdvisor 适合部署对齐 |
| 需要批量采集落库 | 公开入口是 InsertTables(复数),可配 SqlBulkInsertSettings |
| 进程内希望句柄失效自动重建 | Factory.Insert / Query 等带 ExecuteSafe 自愈重试 |
不该用 / 慎用
| 场景 | 说明 |
|---|---|
| 当完整 ORM / 变更迁移生态 | AlterTable / SyncTableSchemaWhenExists 有风险;生产结构变更要走评审 + 备份 |
| 毫秒级时序库 / 海量原始采样曲线 | 联查默认物化为 List;超大数据请分页或 ExecuteReader 流式(后续期展开) |
把 InitialSqlServer 当每次扫码热路径 | 全量同步是部署/班次切换级操作,不是工单扫码级 |
抄 README 的 bool connected = sql.Connection() | XML:Connection → Result<bool>,必须判 Successed 再看 Content |
| 跳过权限与备份的「一键 Drop」 | DropDatabase / DropTable 不可逆 |
工控故事线:A 线早班开工单
假设你在做 A 产线上位机。早晨班长在 HMI 点「开工」,系统要:
- 确认能连上线边 SQL Server(
192.168.10.20\SQL2019,库名LineA_Mes)。 - 确认工单表、报警表结构与程序集里的 POCO 一致(新版本可能多了「计划产量」「班次码」列)。
- 写入一条工单:
WO-20260920-A001,产品SKU-泵体-01,计划量1200,班次早班。 - 操作员在「在制工单」画面能立刻查到这条记录。
- 若中午 SQL 服务重启导致连接句柄异常,下午扫码入库不应整机崩掉——至少 Factory 路径要能自愈重试一次。
这条故事线贯穿本期:先讲 Result 约定与双入口,再讲 Factory 启动骨架与 Auto,最后用完整可运行样例把「首条工单」落库。
现场还有 B 线共用同一套上位机程序、不同库——这就引出 多连接 Profile;配方表希望首次写入自动补表——引出 [Auto];结构漂移要先报告再改——引出 SchemaSyncOptions.SyncTableSchemaWhenExists=false 与顾问(第6期深化)。
怎么用
0. 安装引用
Install-Package Hi.Ltd.SqlServer -Version 2026.9.7.1642
dotnet add package Hi.Ltd.SqlServer --version 2026.9.7.1642
<PackageReference Include="Hi.Ltd.SqlServer" Version="2026.9.7.1642" />
using Hi.Ltd; // Result / Successed / Content
using Hi.Ltd.SqlServer;
TFM 提醒:net6.0-windows7.0 / net8.0-windows7.0 带 Windows Forms 框架引用与 SMO;纯控制台也能引用,但包面向 Windows 工控宿主。
1. Result 约定
本包大量 API 的 XML returns 写明返回 Result / Result<T>(依赖 Hi.Ltd):
- 成功执行:
r.Successed == true - 业务载荷:在
Content(部分 XML 措辞仍写 Value,与Result<T>.Content同属载荷侧;本系列示例统一用Content) - 失败:
Successed == false,看Message(可再接 Logging) - 拼写是
Successed(少一个 e),写成Succeeded会编译失败
对 Result<bool> 还要分两层:
| 情况 | 含义 | 上位机该怎么做 |
|---|---|---|
Successed=false | 执行层失败(异常被包装进 Result) | 弹 Message,记日志,禁止继续 DDL/DML |
Successed=true 且 Content=false | 命令跑完,但结论为「未通过/未连上」 | 同样当失败处理,不要只看 Successed |
Successed=true 且 Content=true | 真正可用 | 进入业务 |
Result<bool> conn = sql.Connection();
if (!conn.Successed)
{
$"SQL 连接执行失败: {conn.Message}".Error();
return;
}
if (!conn.Content)
{
"SQL 连接测试未通过(Content=false)".Warn();
return;
}
"SQL 连接 OK".Info();
2. 最小路径:SqlEntities 显式实例(适合单机调试 / 测试隔离)
SqlEntities 提供两个入口(XML):
| 成员 | 说明 |
|---|---|
SqlEntities.Create | 公共静态只读字段,默认单例式入口 |
SqlEntities.CreateNew() | 每次新建实例;测试隔离或 Factory 句柄替换时用 |
自行持有引用时:没有 Factory 的 ExecuteSafe 重建包装——句柄生命周期与异常由你负责。
var sql = SqlEntities.CreateNew();
sql.DataSource = @"192.168.10.20\SQL2019";
sql.InitialCatalog = "LineA_Mes";
sql.UserId = "hmi_line_a"; // 不填则走 Windows 身份验证路径
sql.Password = Environment.GetEnvironmentVariable("LINEA_SQL_PWD");
sql.Timeout = 15;
sql.Encrypt = false; // 现场内网常关;跨网段按规范开 TLS
sql.TrustServerCertificate = true; // 仅明确信任自签时
Result<bool> conn = sql.Connection();
if (!conn.Successed || !conn.Content)
throw new InvalidOperationException(conn.Message);
Result<bool> db = sql.CreateDatabase("LineA_Mes");
if (!db.Successed) throw new InvalidOperationException(db.Message);
Result<bool> table = sql.CreateTable<WorkOrderRow>("LineA_Mes");
if (!table.Successed) throw new InvalidOperationException(table.Message);
Result<bool> ins = sql.InsertTable(new WorkOrderRow
{
OrderCode = "WO-20260920-A001",
ProductCode = "SKU-泵体-01",
PlanQty = 1200m,
ShiftCode = "早班",
CreatedAt = DateTime.Now
});
if (!ins.Successed || !ins.Content)
throw new InvalidOperationException(ins.Message);
// 批量采集请用 InsertTables(复数),不要用 README 里的 InsertTable(List)
// Result<bool> bulk = sql.InsertTables(list);
对应 POCO(特性全部来自 Hi.Ltd.SqlServer 命名空间,不要混当成 EF 同名特性「一定等价」):
[Table("WorkOrder", Schema = "dbo")]
public class WorkOrderRow
{
[Key]
public int Id { get; set; }
[Required, MaxLength(64)]
public string OrderCode { get; set; }
[MaxLength(64)]
public string ProductCode { get; set; }
[Decimal(18, 4)]
public decimal PlanQty { get; set; }
[MaxLength(32)]
public string ShiftCode { get; set; }
public DateTime CreatedAt { get; set; }
}
DLL 校正提醒:
MaxLength无参时 默认长度 500(XML 原文;README 写 50 的以 DLL 为准)。短码字段请显式[MaxLength(64)]。- 查询方法名是
InqureTable(不是 Inquire)。 - 批量插入方法名是
InsertTables(复数)。 - 认证相关枚举名是
IntergratedSecurity(少字母 g)。
3. 进阶路径:Factory 全局句柄 + InitialSqlServer
适合:进程内单例访问、启动对齐库表、与 [Auto] 配合、多产线 Profile、希望 CRUD 句柄自愈。
Factory.ConfigureSqlServer(c =>
{
c.DataSource = @"192.168.10.20\SQL2019";
c.InitialCatalog = "LineA_Mes";
c.UserId = "hmi_line_a";
c.Password = Environment.GetEnvironmentVariable("LINEA_SQL_PWD");
c.Encrypt = false;
c.TrustServerCertificate = true;
c.Timeout = 15;
});
Factory.LoadModelAssembly(typeof(WorkOrderRow).Assembly);
var options = new SchemaSyncOptions
{
EnsureDatabase = true,
EnsureTable = true,
EnsureTableType = true,
EnsureMergeProcedure = true,
EnsureForeignKeys = true,
SyncTableSchemaWhenExists = false, // 生产建议先关,改用顾问报告
ValidateModelsBeforeSync = true,
// StandardBuiltInTables = StandardTable.AllIcs // 需要内置工控表时再开
};
Result<SchemaSyncReport> sync = Factory.InitialSqlServer(typeof(WorkOrderRow).Assembly, options);
if (!sync.Successed)
{
$"结构同步失败: {sync.Message}".Error();
return;
}
SchemaSyncReport report = sync.Content;
if (report != null && !report.Successed)
{
// 外层 Result 成功,但报告标记业务级未完全收敛(如 TVP/过程漂移)
$"同步告警: {report}".Warn();
}
Result<bool> conn = Factory.SqlServer.Connection();
if (!conn.Successed || !conn.Content)
{
$"连接失败: {conn.Message}".Error();
return;
}
Result<bool> ins = Factory.Insert(new WorkOrderRow
{
OrderCode = "WO-20260920-A001",
ProductCode = "SKU-泵体-01",
PlanQty = 1200m,
ShiftCode = "早班",
CreatedAt = DateTime.Now
});
if (!ins.Successed)
{
$"工单插入失败: {ins.Message}".Error();
return;
}
Result<List<WorkOrderRow>> q = Factory.Query(new WorkOrderRow { OrderCode = "WO-20260920-A001" }, fuzzy: false);
if (q.Successed)
{
foreach (var row in q.Content)
{
// 绑定到 HMI 在制工单列表
}
}
Factory 关键 API(公开面摘要)
| API | 作用 |
|---|---|
Factory.SqlServer | 全局 ISqlServer;内部 EnsureSqlServer(),必要时新建并应用连接快照 |
ConfigureSqlServer / SetSqlServer / Initialize | 配置或替换句柄并捕获快照 |
UseSqlEntitiesCreate | 恢复为与 SqlEntities.Create 对齐的句柄 |
LoadModelAssembly / ModelTypes | 登记 POCO |
Synchronize / InitialSqlServer | → Result<SchemaSyncReport> |
Insert / Update / Delete / Query + *Async | Result 包装;句柄失效时重建重试一次 |
InsertTables / InsertTablesAsync(Factory 侧亦有明确批量入口) | 避免与 InsertAsync(List) 歧义 |
RegisterProfile / SetDefaultProfile / With / ResolveSqlServer | 多连接 Profile |
EmitStartupAutoHint | 启动期 Auto 提示 |
ClearProfiles / RemoveProfile / ProfileNames / DefaultProfile | Profile 管理 |
Initialize vs InitialSqlServer:前者侧重替换/初始化句柄;后者 = 加载模型 + Synchronize,返回 Result<SchemaSyncReport>。不要混名。
4. 多连接 Profile(A 线 + B 线)
Factory.RegisterProfile("LineA", c =>
{
c.DataSource = "10.0.0.11";
c.InitialCatalog = "LineA_Mes";
c.UserId = "u_a";
c.Password = "p_a";
c.Encrypt = false;
});
Factory.RegisterProfile("LineB", c =>
{
c.DataSource = "10.0.0.12";
c.InitialCatalog = "LineB_Mes";
c.UserId = "u_b";
c.Password = "p_b";
c.Encrypt = false;
});
Factory.SetDefaultProfile("LineA");
using (FactoryProfileScope scope = Factory.With("LineB"))
{
// 作用域内走 LineB
Factory.Insert(new WorkOrderRow { OrderCode = "WO-B-001", ProductCode = "SKU-X", PlanQty = 100m, ShiftCode = "早班", CreatedAt = DateTime.Now });
}
// 模型也可标注默认 Profile(特性详解见第2期)
// [SqlProfile("LineA"), Table("WorkOrder")]
RegisterProfile / SetDefaultProfile 本身也返回 Result——Profile 名拼错时先判 Successed,不要假设一定成功。
5. [Auto] 自动托管(原型友好,生产要克制)
XML:AutoAttribute —— 标记表模型由运行期自动托管结构工件(库/表/类型/过程/外键)。仅在与 [Table] 同时存在时生效;只有 [Auto] 没有 [Table] 会被忽略。
[Table("AlarmRecord", Schema = "dbo"), Auto]
public class AlarmRecordRow
{
[Key] public int Id { get; set; }
[Required, MaxLength(64)] public string AlarmCode { get; set; }
[MaxLength(256)] public string AlarmText { get; set; }
public DateTime AlarmTime { get; set; }
}
效果要点:
InsertTable/UpdateTable/DeleteTable/InqureTable(及对应 Async)路径会EnsureAutoArtifacts(库/表/TVP/MERGE 过程/外键等,受内部 Auto 选项约束)。Factory.Insert/Query落到同一套 API,行为一致。- 全量
Synchronize/InitialSqlServer成功后会AutoTableRegistry.InvalidateEnsured(),下次 DML 重新校验 Auto 就绪状态。 - 默认自愈策略与
AutoSelfHealPolicy相关(如AddMissing);DML 遇 schema 漂移 SqlException 时是否自愈重试,由策略与安全门控制。
推荐分工:
- 部署期一次对齐:关键业务表不加 Auto,启动
InitialSqlServer,结构变更走评审。 - 原型 / 临时报警缓冲表:
[Table, Auto]+ 程序集已LoadModelAssembly。 - 禁止:所有表都 Auto,又打开
SyncTableSchemaWhenExists——难审计、难回滚。
6. SchemaSyncOptions 常用开关(部署清单)
| 属性 | 含义 | 生产建议 |
|---|---|---|
EnsureDatabase / LazyCreateDatabase | 库是否确保 / 懒创建 | 线边库可开 Ensure;中央库慎自动建 |
EnsureTable | 缺表则建 | 部署期可开 |
SyncTableSchemaWhenExists | 已存在时纠偏列 | 默认建议 false,先顾问报告 |
EnsureTableType / EnsureMergeProcedure / EnsureForeignKeys | TVP / 过程 / FK | 用到 MERGE/TVP 再开 |
StandardBuiltInTables | StandardTable 掩码 | 需要内置 Ics/Sys 时再设 |
IncludeOnlyTableNames / ExcludeTableNames / TypeFilter / IncludeNamespacePrefixes / AutoOnly | 筛选 | 大程序集务必筛选 |
ValidateModelsBeforeSync | 同步前 POCO 校验 | 建议 true |
ColumnTypeMismatch | 类型不一致策略 | 按变更流程选型 |
外层 Result.Successed=true 但 SchemaSyncReport.Successed=false 很常见:表已演进,TVP/过程仍是旧版。从 2026.4.21 起 UpdateTableType 会先删引用过程再重建;升级旧库建议先全量同步再进 DML(TVP 细节见第5期)。
7. 回读工单:Inqure(拼写勿「纠正」)
Result<List<WorkOrderRow>> list = Factory.SqlServer.InqureTable(new WorkOrderRow
{
OrderCode = "WO-20260920-A001"
}, fuzzy: false);
if (!list.Successed)
{
$"查询失败: {list.Message}".Error();
return;
}
var rows = list.Content ?? new List<WorkOrderRow>();
公开 API 就是 Inqure。写成 Inquire 会找不到成员——这不是文档笔误,是 DLL 成员名(拼写错误,后续批量纠正)。
手写对照优势
| 维度 | 手写 ADO / 散落 SQL | Hi.Ltd.SqlServer 2026.9.7.1642 |
|---|---|---|
| 连接自检 | SqlConnection.Open + try/catch 满天飞 | Connection() → Result<bool>,统一 Successed/Content/Message |
| 建库建表 | 手写 IF DB_ID / CREATE TABLE 脚本,易与 POCO 漂移 | CreateDatabase / CreateTable<T> 或 InitialSqlServer |
| 工单插入 | 参数拼装、列顺序、自增回填各写一套 | InsertTable / Factory.Insert;批量 InsertTables |
| 结构升级 | 运维脚本与程序版本两套真相 | 模型程序集 + SchemaSyncOptions(仍要评审,但真相源在代码) |
| 句柄失效 | 自己捕获 ObjectDisposed,自己重建连接 | Factory.* CRUD 捕获可恢复异常并重建重试一次 |
| 多产线 | 多套连接字符串散落配置 | RegisterProfile + With + [SqlProfile] |
| 原型补表 | 忘建表就运行时炸 | [Auto] 热路径 Ensure(生产仍建议部署期同步) |
| 统一失败语义 | 有的抛、有的返回 bool、有的返回 -1 | 公开面统一 Result;拼写固定为 Successed |
本包不是要替代 DBA 的变更管理,而是让上位机团队把「落库工具箱」收敛到一套公开 API,减少现场「脚本与 exe 版本对不上」的事故面。
完整可运行样例(可抄进项目)
下面示例假设:本地或线边已有 SQL Server;账号有建库建表权限;用 SqlEntities 最小路径演示首条工单,再用 Factory 演示启动同步与查询。演示密码请换成现场密钥/环境变量。
using System;
using System.Collections.Generic;
using System.Windows.Forms;
using Hi.Ltd;
using Hi.Ltd.SqlServer;
namespace LineA.Hmi
{
[Table("WorkOrder", Schema = "dbo")]
public class WorkOrderRow
{
[Key]
public int Id { get; set; }
[Required, MaxLength(64)]
public string OrderCode { get; set; }
[MaxLength(64)]
public string ProductCode { get; set; }
[Decimal(18, 4)]
public decimal PlanQty { get; set; }
[MaxLength(32)]
public string ShiftCode { get; set; }
public DateTime CreatedAt { get; set; }
}
[Table("AlarmRecord", Schema = "dbo"), Auto]
public class AlarmRecordRow
{
[Key]
public int Id { get; set; }
[Required, MaxLength(64)]
public string AlarmCode { get; set; }
[MaxLength(256)]
public string AlarmText { get; set; }
public DateTime AlarmTime { get; set; }
}
public static class LineBootstrap
{
public static bool Boot(out string message)
{
// —— 路径 A:显式实例自检 + 建库建表 + 首条工单 ——
var sql = SqlEntities.CreateNew();
sql.DataSource = "(local)";
sql.InitialCatalog = "LineA_Mes";
sql.UserId = "sa";
sql.Password = "***"; // 演示;产线用环境变量/密钥库
sql.Timeout = 15;
sql.Encrypt = false;
sql.TrustServerCertificate = true;
Result<bool> conn = sql.Connection();
if (!conn.Successed)
{
message = "连接执行失败: " + conn.Message;
return false;
}
if (!conn.Content)
{
message = "连接测试未通过: " + conn.Message;
return false;
}
Result<bool> db = sql.CreateDatabase("LineA_Mes");
if (!db.Successed)
{
message = "建库失败: " + db.Message;
return false;
}
Result<bool> tb = sql.CreateTable<WorkOrderRow>("LineA_Mes");
if (!tb.Successed)
{
message = "建工单表失败: " + tb.Message;
return false;
}
Result<bool> ins = sql.InsertTable(new WorkOrderRow
{
OrderCode = "WO-20260920-A001",
ProductCode = "SKU-泵体-01",
PlanQty = 1200m,
ShiftCode = "早班",
CreatedAt = DateTime.Now
});
if (!ins.Successed || !ins.Content)
{
message = "工单插入失败: " + ins.Message;
return false;
}
// —— 路径 B:注入 Factory,做模型同步与查询 ——
Factory.SetSqlServer(sql);
Factory.LoadModelAssembly(typeof(WorkOrderRow).Assembly);
var options = new SchemaSyncOptions
{
EnsureDatabase = true,
EnsureTable = true,
EnsureTableType = false,
EnsureMergeProcedure = false,
EnsureForeignKeys = false,
SyncTableSchemaWhenExists = false,
ValidateModelsBeforeSync = true
};
Result<SchemaSyncReport> sync = Factory.InitialSqlServer(typeof(WorkOrderRow).Assembly, options);
if (!sync.Successed)
{
message = "InitialSqlServer 失败: " + sync.Message;
return false;
}
Result<List<WorkOrderRow>> q = Factory.Query(
new WorkOrderRow { OrderCode = "WO-20260920-A001" },
fuzzy: false);
if (!q.Successed)
{
message = "查询失败: " + q.Message;
return false;
}
// Auto 表:首次写入触发确保(原型场景)
Result<bool> alarm = Factory.Insert(new AlarmRecordRow
{
AlarmCode = "E001",
AlarmText = "进料压力低",
AlarmTime = DateTime.Now
});
if (!alarm.Successed)
{
message = "报警写入失败: " + alarm.Message;
return false;
}
message = "OK,工单与报警通路已通,查询行数=" + (q.Content?.Count ?? 0);
return true;
}
}
// MainForm_Load 示例
public partial class MainForm : Form
{
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
if (!LineBootstrap.Boot(out var msg))
{
MessageBox.Show(msg, "数据库启动", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
else
{
// 正常进入班次/工单画面
}
}
}
}
采集批量落库补充(同一 sql 实例):
var batch = new List<WorkOrderRow>();
for (int i = 0; i < 100; i++)
{
batch.Add(new WorkOrderRow
{
OrderCode = $"WO-BATCH-{i:D4}",
ProductCode = "SKU-泵体-01",
PlanQty = 10m,
ShiftCode = "早班",
CreatedAt = DateTime.Now
});
}
Result<bool> bulk = sql.InsertTables(batch); // 复数!
if (!bulk.Successed || !bulk.Content)
throw new InvalidOperationException(bulk.Message);
避坑清单
-
README 仍写
bool Connection()
2026.9.7.1642 XML:ISqlServer.Connection→Result<bool>。照 README 抄会编译不过或误判。 -
Successed拼成Succeeded
Hi.Ltd 既定拼写。全系列、全项目统一搜一遍。 -
只判
Successed不看Content(对Result<bool>)
连接测试「执行成功但连不上」会漏掉。 -
InqureTable/Visiable/IntergratedSecurity/ICreateProcdure
历史拼写是公开 API 的一部分,勿「纠正」。 -
批量用错方法名
单条InsertTable;批量InsertTables。README 若写InsertTable(List)以 DLL 为准。 -
MaxLength 默认 500
不是 50。配方短码、工单号请显式长度,避免索引键过宽或误导评审。 -
InitialSqlServer当热路径
扫码、心跳、每秒采集不要全量同步。部署/升级/班次切换再跑。 -
生产打开
SyncTableSchemaWhenExists
可能改列。先备份,先顾问报告,先在预发验证。 -
[Auto]但未LoadModelAssembly
类型未登记,热路径可能不按你预期补齐。 -
外层同步 Successed=true、报告 Successed=false
TVP/过程漂移。先收敛结构再 DML,不要只看外层。 -
多实例共用默认调度却不做负载预设
多线程采集请后续结合ApplyWorkloadPreset(第7期工具篇)。 -
用 sa 上产线
演示可以,产线用最小权限账号;建库权限与日常 DML 权限分离。 -
命名实例反斜杠
C# 字符串写@"host\INSTANCE"或"host\\INSTANCE"。 -
Encrypt 随 SqlClient 变严
升级驱动后突连失败,显式配置Encrypt/TrustServerCertificate(第2期展开)。 -
把 XML 未出现的本地实验 API 写进产线
本系列只认 2026.9.7.1642 已发布公开面。
FAQ
Q:必须用 Factory 吗?
A:不必。单机上位机 SqlEntities.CreateNew() 足够。要启动同步、[Auto]、多 Profile、句柄自愈时用 Factory。
Q:和 EF Core 比?
A:本库偏「工控落库工具箱」(建库表、TVP、过程、联查、内置 Ics 表、结构同步顾问),不是完整变更迁移生态。MES 中台若已统一 EF,线边 HMI 仍可用本包做采集落库。
Q:异步怎么用?
A:有 InsertTableAsync / InsertTablesAsync / InqureTableAsync / Factory.InsertAsync / QueryAsync 等,支持 CancellationToken。采集线程建议异步 + 有界队列,避免 UI 线程堵死。
Q:内置工单表能不能直接用?
A:包内有 Tables.Ics.IcsWorkOrder、IcsAlarmRecord、IcsShift 等(第7期)。本期用自建 POCO 跑通通路;正式项目评估 StandardTable 掩码。
Q:Factory.Insert 和 sql.InsertTable 差别?
A:前者走全局句柄 + 可恢复异常重建重试,失败多转为 Result;后者直接打你持有的实例。对 [Auto] 类型,两者都会触发自动结构确保。
Q:能否注入自定义 ISqlServer?
A:可以,Factory.SetSqlServer(myImpl)。测试替身或特殊连接池包装时有用。
Q:同步失败但库表其实已存在?
A:看 Message 与报告明细。已存在通常仍可能走成功路径;真正失败常见于权限、类型不兼容、过程占用 TVP。用 ExistsDatabase / ExistsTable 做分支诊断。
Q:首条工单插入成功但画面查不到?
A:确认查询条件、是否连错 Profile/库、模糊查询 fuzzy 是否误开、以及是否查的是同步前的旧库名。
深化:为什么上位机需要「双入口」而不是单一全局单例
产线软件和互联网后台有一个关键差异:进程生命周期与网络抖动绑在一起。触摸屏工控机可能连续跑几个班次不关;也有人中午断电、下午换班登录;还有人在维护窗口重启 SQL Server 服务却不忘关上位机。
因此本包同时提供:
- SqlEntities:你看得见、摸得着的
ISqlServer实例。适合单元测试、临时工具窗体、维护程序、以及「我就想自己持有引用」的清晰控制流。 - Factory:进程级句柄 + 连接快照 + 模型目录 + Profile 路由 + CRUD 自愈。适合正式 HMI 主程序:启动配一次,页面到处
Factory.Insert/Factory.Query。
两者不是互斥。常见现场组合是:
- 启动窗体用
SqlEntities.CreateNew()做连接自检与首次建库; - 自检通过后
Factory.SetSqlServer(sql)注入; - 再
LoadModelAssembly+InitialSqlServer; - 业务窗体只碰 Factory。
这样「谁创建了连接」有据可查,又享受全局 CRUD 便利。
若反过来:业务页直接改 SqlEntities.Create 静态字段上的属性,而 Factory 另有快照——容易出现「页面以为改了库名,同步还在打旧库」的幽灵问题。配置入口要单一:要么全程 SqlEntities 自持,要么全程 ConfigureSqlServer / Profile,不要两边偷偷改。
深化:首条工单通路的现场检查单
把故事线拆成可执行检查项,班组长与软件工程师可以共用:
- 网络:工控机能否 ping 通 SQL 主机;命名实例是否启用浏览器服务或写死端口。
- 认证:SQL 混合模式是否开启;
hmi_line_a是否被策略锁定;Windows 认证时服务账户是否有登录权。 - 库:
LineA_Mes是否已存在;不存在时账号是否有CREATE DATABASE。 - 表:
dbo.WorkOrder是否已存在;存在时列是否与 POCO 一致(尤其新增ShiftCode)。 - 权限:日常账号是否只需 DML;部署账号是否临时提升。
- 结果语义:所有步骤是否判了
Successed,Result<bool>是否还判了Content。 - 回读:
InqureTable/Factory.Query条件是否精确匹配工单号。 - 日志:失败
Message是否写入本地日志,便于夜班交接。
很多现场事故不是「API 不会用」,而是第 6 步只写了 if (r.Successed) 就继续——连接测试 Content=false 被当成成功,后续插入才报一长串登录失败,排查成本翻倍。
深化:班次切换与模型同步的节奏
建议把结构同步从「业务热路径」里剥离:
| 时机 | 建议动作 | 不建议 |
|---|---|---|
| 软件首次安装 | EnsureDatabase/EnsureTable=true,完整 InitialSqlServer | 让操作工在生产画面点「同步」 |
| 版本升级(多了列) | 预发验证 → 备份 → 维护窗口同步;SyncTableSchemaWhenExists 仅在评审后短时开启 | 生产高峰自动纠偏列类型 |
| 每班开机 | Connection() 自检 + 可选 SchemaStartupAdvisor 只读体检 | 全量 Synchronize |
| 扫码/采集 | 仅 DML:Insert / InsertTables / Inqure | 任何 DDL |
| 临时试验表 | [Auto] 允许热补 | 把关键工单表也标 Auto 且依赖热路径改结构 |
班次切换时,若只是换 ShiftCode 字典或当前班次全局变量,不必同步结构。只有程序集里的表模型相对库发生了「形状变化」,才需要同步。
深化:SchemaSyncReport 双层 Successed 怎么读
现场日志里最容易误解的是:
InitialSqlServer: Successed=true
SchemaSyncReport.Successed=false
Message: … TVP / 过程 …
请按两层解读:
- 外层 Result:同步命令链路有没有「炸」(异常、权限直接失败等)。
Successed=true表示流程跑完并给出了报告。 - 报告 Successed:业务上是否认为结构已收敛。
false通常表示部分工件(尤其 TVP、MERGE 过程、外键)仍有漂移或失败项。
上位机启动策略建议:
- 外层失败:阻止进入生产画面,弹模态错误。
- 外层成功但报告失败:按配置决定「仅告警可登录」还是「阻止登录」。对依赖 TVP/MERGE 的落库路径,建议阻止;对只用普通
InsertTable的简单工单,可告警后放行并建运维工单。
升级旧库时,优先安排一次全量收敛,再谈「告警是否可登录」。
深化:Factory CRUD 自愈边界(不要神话)
Factory.Insert / Update / Delete / Query 在捕获 ObjectDisposedException / NullReferenceException 等「句柄失效」时,会重建实例、应用连接快照并重试一次。这解决的是:
- 长时间运行后句柄被释放;
- 某些路径误 Dispose;
- 快照仍有效时的快速恢复。
这不解决:
- 密码错、账号锁、防火墙丢包(会以失败 Result 返回,不会靠重试变成功);
- 业务唯一键冲突;
- 表不存在且未走 Auto/同步;
- 死锁与超时(需业务层重试策略,可结合后续并发预设)。
所以自愈是「基础设施级」的,不是「业务失败自动抹掉」。HMI 仍要提示操作工:插入失败时不要重复猛点,先看 Message。
深化:多产线 Profile 的配置纪律
双线共屏(一台工控机管 A/B 线)时,推荐:
- 启动时
RegisterProfile("LineA", …)/RegisterProfile("LineB", …),全部返回值判Successed。 SetDefaultProfile设为当前物理线或上次选择。- 画面切线时用
using (Factory.With("LineB")) { … },避免全局改默认导致后台采集线程写错库。 - 模型上可加
[SqlProfile("LineA")]固定某些表永远打 A 库(例如只在 A 线存在的专用校准表)。 - 切线时写审计:谁在什么时间把作用域切到哪条线。
反模式:在定时器回调里改 Factory.SqlServer.InitialCatalog,同时 UI 线程 With 另一个 Profile——竞态下会出现「报警进了工单库」。Profile 的价值就是把连接参数变成命名快照,而不是运行时散改属性。
深化:与 MES 对接时的边界
本包擅长线边落库与结构对齐;MES 常在另一套库或另一台服务器。推荐边界:
- 线边库:工单执行、报警、班次、采集摘要——用 Hi.Ltd.SqlServer。
- MES 库:计划下发、质量闭环——由 MES 接口或中间服务写入;上位机只读或通过受控账号写回有限字段。
- 同步方向:MES → 线边可用接口落「当日计划工单」;线边 → MES 用批量出站,不必让 HMI 直接对 MES 库跑
InitialSqlServer。
若强行让上位机对 MES 中心库 EnsureTable=true,一旦模型写错,风险面远大于线边库。权限上也应禁止 HMI 账号在 MES 库执行 DDL。
深化:最小权限模型(演示 sa,产线分离)
| 角色 | 权限 | 使用时机 |
|---|---|---|
| 部署账号 | 建库/建表/建过程/备份 | 安装与升级维护窗口 |
| 运行账号 | 业务库 DML + 有限读系统视图 | 日常班次 |
| 只读账号 | 报表查询 | 看板/审计 |
InitialSqlServer 用部署账号;Factory.ConfigureSqlServer 在维护结束后可切回运行账号(或维护程序与 HMI 分进程)。不要因为演示代码写了 sa 就复制到产线。
深化:完整样例之外的「失败注入」练习
建议在预发环境故意制造失败,确认 HMI 文案与日志:
- 改错密码 → 应看到
Successed=false或Content=false,画面不可进入。 - 删掉
WorkOrder表且关闭 Auto →Insert失败信息可读。 - 打开 Auto 再插入临时表 → 表被补回(仅限试验表)。
- 注册错误 Profile 名 →
SetDefaultProfile失败。 - 同步时关掉
EnsureTable且表不存在 → 报告/后续 DML 暴露问题。
没有失败注入,等于没验收 Result 语义。
深化:代码评审时对照的「必须出现」项
给同事做 Code Review 时,打开落库相关 PR,核对:
- 包版本是否锁定 2026.9.7.1642(或团队统一更高已发布版本,但本系列示例锁定该版)。
- 是否
using Hi.Ltd且判断Successed。 - 是否出现错误拼写
Succeeded/InquireTable/InsertTable(list)。 - 字符串字段是否显式
MaxLength,避免误用默认 500 却按 50 做 UI 校验。 - 生产配置里
SyncTableSchemaWhenExists默认值。 - 是否把同步塞进采集定时器。
- 密钥是否进仓库。
- 是否区分部署账号与运行账号。
这八条能挡下大半「能在开发机跑、到产线爆炸」的提交。
深化:工单号、班次码与时间字段的建模提示(预告第2期)
即使本期只用最小特性,也建议尽早养成:
- 工单号:
[Required, MaxLength(64)],业务唯一时考虑唯一索引(可用后续 Execute 建索引或 DBA 脚本)。 - 班次码:短字符串,显式长度;不要靠默认 500。
- 时间:
DateTime用服务器本地还是 UTC,全厂统一;默认值可用第2期DefaultSql。 - 数量:
[Decimal(18,4)],不要用float落金额/重量。 - 主键:默认
[Key]的 Identity 语义适合绝大多数工单表;业务主键用DatabaseGenerated.None(第2期)。
特性是「模型即文档」。产线新人读 POCO 应能猜出表长什么样。
深化:启动日志模板(可粘贴)
[LineA] boot start
[LineA] Connection Successed={0} Content={1} Message={2}
[LineA] CreateDatabase Successed={0} Message={1}
[LineA] CreateTable WorkOrder Successed={0} Message={1}
[LineA] InitialSqlServer Successed={0} Report.Successed={1} Message={2}
[LineA] Insert WO Successed={0} Content={1} Message={2}
[LineA] Query count={0}
[LineA] boot end ok={0}
把 {0} 换成实际值。夜班若只说「连不上」,没有这七行,几乎无法远程判断是网络、认证、库名还是同步报告告警。
深化:何时该停下来读第2期而不是继续堆 POC
若你已经:
- 能插入工单并回读;
- 理解 Successed/Content;
- 能选 SqlEntities 或 Factory;
下一步卡点通常是:
- 主从表外键怎么标;
- 列名与属性名不一致;
- 中文排序规则;
- 连接加密突然失败;
- Windows 认证与 SQL 认证切换。
这些正是 v2 第2期 的范围。不要在第1期 POC 里手写半套特性映射器——那是重复造轮子。
深化:一条产线从「空库」到「可报工」的九十秒口述稿
给你的实施同事一段可以照着讲的话,方便培训:
「上位机启动先配数据源和库名,调用 Connection,先看 Successed,再看 Content。通了以后,要么用 CreateDatabase 和 CreateTable 建工单表,要么走 Factory 的 ConfigureSqlServer、LoadModelAssembly、InitialSqlServer。同步时生产环境先把 SyncTableSchemaWhenExists 关掉,只确保库表存在。然后 Factory.Insert 写开工单,用 Query 或 InqureTable 回读。批量采集一定要走 InsertTables 复数方法。Result 的拼写是 Successed,少一个 e。字符串 MaxLength 默认五百,短码自己写长度。查询方法名叫 Inqure,不是 Inquire。这一套是 NuGet 二零二六九点七点一六四二的公开面,不要抄 README 里返回 bool 的老例子。」
把这段话练顺,现场支持电话里就能挡住一半错误示范。
深化:工单状态机与落库时机(业务层,不在包内)
本包不内置工单状态机,但上位机几乎总会有。建议状态与落库时机解耦:
| 状态 | 含义 | 落库建议 |
|---|---|---|
| 草稿 | 班长预填未确认 | 可写本地或线边,允许改 |
| 已下达 | MES/计划确认 | 插入或更新线边工单主表 |
| 生产中 | 开工按钮 | 更新状态字段 + 写班次 |
| 暂停 | 设备故障 | 写报警 + 更新状态,勿删主单 |
| 完工 | 数量达标 | 更新实际产量,出站给 MES |
| 作废 | 取消 | 软删除标记,保留审计 |
用 UpdateTable / Factory.Update 改状态时,务必带主键;条件更新要清楚 fuzzy 语义,避免模糊匹配误更新多行(CRUD 专期展开)。本期只要保证「已下达」能插入成功、「生产中」能查到。
深化:采集落库与工单落库的队列隔离
工单是低频、要强一致提示的操作;采集是高频、可批量的操作。架构上建议:
- UI 线程:工单 Insert/Update,失败立刻 MessageBox。
- 后台采集线程:入内存队列,批量
InsertTables,失败写重试文件。 - 两者可共享
Factory句柄,但不要共享「未完成的事务观感」——工单失败不应卡采集,采集堆积不应堵开工按钮。
本包提供 API,不强制线程模型;但若把每秒上百点位写成每次 InsertTable 单条,SQL 与网络会被打满。批量入口存在的意义就在这里。
深化:AutoSelfHeal 与「表被 DBA 改过」现场
真实产线常有:DBA 半夜加了列、改了可空、加了默认值,而上位机 POCO 未更新。表现可能是:
- 插入仍成功(多列有默认值);
- 或插入失败(强制非空无默认);
- 或 Auto 自愈尝试补齐/对齐时与 DBA 变更冲突。
策略建议:
- 关键列变更走「先改 POCO 再同步」的发布流程。
- DBA 独占管理的列打
ExternallyManaged(第2期),避免 Auto ALTER。 - 生产关闭激进纠偏;Auto 仅用于明确约定的附属表。
- 出现漂移 SqlException 时,把
Message完整留存,对照SchemaStartupAdvisor(后续期)。
不要指望 Auto 替代变更管理会议。
深化:SqlEntities.Create 与 CreateNew 的选择矩阵
| 需求 | 选择 | 原因 |
|---|---|---|
| 正式 HMI 主程序 | CreateNew + Factory.SetSqlServer 或直接 ConfigureSqlServer | 生命周期清晰,便于测试替换 |
| 快速控制台实验 | SqlEntities.Create | 少一行 |
| 并行测试用例 | 每例 CreateNew | 避免静态状态串扰 |
| Factory 内部重建 | 框架走 CreateNew 路径 | 与快照重绑 |
避免在多个窗体各自 CreateNew 又各自改连接,导致「一屏连 A 库、一屏连 B 库」且没有 Profile 审计。能收敛到 Factory 就收敛。
深化:InitialSqlServer 参数怎么选程序集
LoadModelAssembly / InitialSqlServer(..., assembly, ...) 会扫描候选表模型。大解决方案里常见坑:
- 扫到了测试工程里的演示表;
- 扫到了已废弃但未删除的旧 POCO;
- 扫到了桌面控件项目里误加的
[Table]。
对策:
- 表模型集中在
LineA.Mes.Models程序集,只 Load 它。 - 用
IncludeNamespacePrefixes/ExcludeTableNames/TypeFilter/AutoOnly收紧。 - 废弃表先去特性再删类,或排除名单。
- 同步前看
Factory.ModelTypes列表,打印到启动日志。
「扫错程序集」比「连错服务器」更隐蔽,因为连接成功、只是多建了怪表。
深化:成功标准(本期验收)
完成下列项,第1期才算过关:
- 在目标 SQL Server 上,空库或已有库,跑通完整样例,日志七行齐全。
- 故意输错密码,确认无法进入主画面。
- 插入工单后,用 SSMS 能看见行;用
InqureTable/Query也能看见。 - 批量
InsertTables插入不少于一百行试验数据,耗时可接受。 - 代码库中全局搜索不到
Succeeded、InquireTable、bool connected = .*Connection(。 - 生产配置默认
SyncTableSchemaWhenExists=false。 - 培训口述稿能不看笔记讲完。
未达验收不要急着上特性与联查——地基不牢,后面每期都会反复踩连接与 Result 的坑。
深化:与旧 README 示例的逐条对照(防误抄)
| README 常见写法 | 2026.9.7.1642 正确写法 |
|---|---|
bool connected = sql.Connection(); | Result<bool> r = sql.Connection(); if (!r.Successed || !r.Content) … |
| 暗示 MaxLength 默认 50 | XML:默认 500 |
InsertTable(list) 批量 | InsertTables(list) |
| 文档脚注旧版本号 | 包版本以 2026.9.7.1642 为准 |
| 过程接口名按英文直觉拼 | 类型名 ICreateProcdure(方法仍可能叫 CreateProcedure) |
把这张表贴到团队 Wiki,比反复口头提醒有效。
深化:报警表用 Auto、工单表不用 Auto 的理由再讲一遍
工单表是审计与产量统计的根;结构变更要留变更单、要备份、要告知 MES。Auto 热路径补列可能:
- 在生产高峰加锁;
- 生成与 DBA 规范不一致的列定义;
- 让「线上结构」与「发布说明」脱节。
报警缓冲表、临时调试表、设备侧上传的宽表,往往更适合 Auto:宁可不丢报警,也不要因为忘建表导致整屏红叉。
同一程序集里混合策略是正常的——不要教条「全开」或「全关」。
深化:Content 为空列表与失败的区别
Query / InqureTable 成功且 Content 为空列表,表示「查成功,但没有行」——工单号打错、班次滤错、连错库,都可能。这不是异常。
只有 Successed=false 才是执行失败。HMI 文案要区分:
- 「没有符合条件的工单」→ 业务空结果;
- 「查询失败:…」→ 技术失败,需要维护。
把空列表当成失败去弹错误色,会训练操作工忽略真正的红字报警。
深化:从第1期跳到后续期的阅读地图
- 还不会标外键、小数、并发列 → 第2期 Attributes。
- 开机连不上、证书报错 → 第2期 Connection。
- 要备份还原、附加分离 → 第3期。
- 要更新删除复制批量调优 → 第4期。
- 主从联查、过程、TVP → 第5期。
- 结构顾问、Execute、事务、索引 → 第6期。
- 统计工具、内置 Ics/Sys、视图全文、总避坑 → 第7期。
第1期是门禁;门禁过了再加深,效率最高。
深化:产线早会可以用的「落库健康」五问
每天早会不必投影代码,问五句就够:
第一,昨夜有没有结构同步失败或报告告警?有则先看 SchemaSyncReport,再决定是否允许开产。
第二,开工单是否在三秒内成功?失败是权限、唯一键还是连接?
第三,采集队列有没有堆积?堆积是不是单条插入没用 InsertTables?
第四,A/B 线有没有写串库?看 Profile 审计。
第五,有没有人把 SyncTableSchemaWhenExists 临时打开忘了关?
这五问把本包能力翻译成管理语言,便于设备、工艺、信息化三方对齐。
深化:首次部署脚本化步骤(人工版)
即使你用本包 API,也建议保留人工检查序:
步骤一,确认 SQL Server 版本与兼容性级别满足公司规范。
步骤二,创建运行账号与部署账号,拒绝共用 sa。
步骤三,用维护工具执行 ConfigureSqlServer(部署账号)与 InitialSqlServer。
步骤四,抽查关键表:工单、报警、班次、配方是否存在。
步骤五,切换为运行账号配置,执行 Connection 自检。
步骤六,插入一笔测试工单并删除或作废,确认 DML 权限足够且无 DDL 权限。
步骤七,打开 HMI 走一遍早班开工,保留截图与日志归档。
步骤八,备份新建库,作为「空白可用基线」。
自动化不是省略步骤,而是让步骤可重复。缺了基线备份,第一次 SyncTableSchemaWhenExists 试错都会令人心跳加速。
深化:对象命名与架构(Schema)约定
[Table("WorkOrder", Schema = "dbo")] 只是起点。建议厂内约定:
业务表放 dbo 或 mes;
设备采集宽表放 acq;
只读对接视图放 viz;
内部技术表放 sysx(避免与 SQL 的 sys 混淆)。
Factory 同步时 Schema 来自特性。若有人手工建了 mes.WorkOrder,而 POCO 写 dbo.WorkOrder,你会得到「两张像双胞胎的表」,画面查一张、接口写另一张。同步日志里打印「架构+表名」全名,能早点发现。
深化:时间、时区与班次切割
工控机常跑本地时区,MES 可能用 UTC。本期插入用 DateTime.Now 仅作演示。正式约定应写进规范:
库内时间一律 UTC,显示再转本地;或
库内一律工厂本地时间,禁止混用服务器时区不同的机器。
班次切割不要只靠「上午八点」写死在客户端:夏令、节假日加班、跨零点班次都会让统计错位。班次表(后续内置 IcsShift)应成为时间归属的依据;工单上的 ShiftCode 是冗余便捷字段,统计以班次日历为准。
深化:唯一性与工单号生成
OrderCode 通常需要唯一。本包特性建模以列型与键为主,唯一索引可用后续 Execute 或 DBA 脚本补充。业务上:
工单号建议服务端或统一发号服务生成,避免两台 HMI 同时本地拼时间戳撞车。
若必须客户端生成,加上产线号与随机段,并在库侧建唯一约束,插入失败时提示「工单号冲突」而不是泛型错误。
InsertTable 返回失败时,把 Message 映射成操作工能懂的句子,是产品化的一部分。
深化:UI 绑定与 Content 空值防护
if (q.Successed)
{
var data = q.Content ?? new List<WorkOrderRow>();
grid.DataSource = data;
}
else
{
grid.DataSource = null;
ShowError(q.Message);
}
永远假设 Content 可能为 null(失败路径或实现细节)。WinForm 绑定到 null 有时不抛、有时抛,取决于控件。统一空列表更稳。
深化:日志中的敏感信息
Message 偶发可能含连接相关信息;自定义日志不要无条件把 Password 打出去。ConfigureSqlServer 闭包里读取环境变量即可,不要把密码常量写进仓库。连接失败时,日志给「账号、数据源、库名、Encrypt 开关」,足够排查;密码只存在密钥介质。
深化:版本锁定与还原策略
NuGet 允许浮动版本,但产线应锁定。本系列以 2026.9.7.1642 为唯一口径。升级时:
先读发行说明与 XML 变更;
在预发跑完整样例与失败注入;
确认 Successed 拼写与 InsertTables 等名称未变;
再滚动升级工控机。
「顺便升包」是现场经典事故源。
深化:与触摸屏性能有关的期望管理
触摸屏工控机 CPU/磁盘一般弱于办公 PC。期望:
启动同步:数秒到数十秒可接受(视表数量)。
开工插入:应在一秒内级反馈。
批量采集:靠 InsertTables 与队列,不靠提高同步频率。
查询默认前一千行类接口要避免当全表浏览;做条件查询与分页(后续期)。
不要在 UI 线程做 InitialSqlServer;放启动 Splash 或后台并禁用「开工」直到完成。
深化:教育新人时的三个演示顺序
演示一,只连 Connection,展示 Successed 与 Content 两种失败。
演示二,建表插入一条,SSMS 打开对照。
演示三,Factory 同步 + Query + 故意 Dispose 场景看自愈(若可安全演示)。
一次讲完双入口、Auto、Profile、TVP,新人会晕。第1期克制范围,是为了留记忆锚点:「先连上、先写入、先判 Result」。
深化:常见失败 Message 的阅读姿势
看到登录失败,先别改代码,先看 UserId 与 SQL 是否允许远程。
看到超时,先看 Timeout 与网络,再看是否同步太重。
看到对象名无效,先看 InitialCatalog 与表是否在当前库。
看到权限不足,区分 DDL 与 DML。
看到类型转换或截断,回头查 MaxLength 与 Decimal。
Message 是给工程师的原材料,产品层再翻译。直接把英文原样扔给操作工,只会换来「电脑坏了」的工单。
深化:本期知识图谱(便于内化)
中心节点是 Result。
连出去两条边:SqlEntities 与 Factory。
Factory 再连出:Configure、LoadModel、InitialSqlServer、Profile、CRUD 自愈、Auto。
SqlEntities 连出:Connection、CreateDatabase、CreateTable、InsertTable、InsertTables、InqureTable。
横切约束:Successed 拼写、Content 载荷、DLL 历史拼写、MaxLength 默认五百、生产关闭激进纠偏。
你能凭记忆画出这张图,第1期就真正学到手了。
深化:写给架构师的边界声明
Hi.Ltd.SqlServer 解决的是「Windows 工控宿主到 SQL Server 的结构化落库与结构对齐」。它不试图:
取代消息总线;
取代时序库;
取代完整 ORM 工作流;
取代 DBA 的备份与权限体系;
在未经评审时自动改造生产表结构。
在边界内,它非常强:POCO、Result、同步、批量、联查、过程、内置工控表。越界使用导致的问题,不应算作包的缺陷,而应算作架构选择问题。第1期明确边界,是为后续期的深度功能留安全绳。
深化:把「首条工单」做成自动化冒烟
建议在预发机器放一个控制台或测试项目,每晚跑:
配置连接(专用冒烟库,禁止指向生产);
Connection 必须成功;
InitialSqlServer 外层必须成功;
插入固定工单号前先按条件删除或使用随机后缀;
Insert 必须 Successed;
Query 必须能查回;
最后清理试验行。
冒烟失败就阻断次日发布。这比靠人「感觉能连」可靠得多。注意冒烟库也要备份策略,避免磁盘被试验数据撑满。
深化:HMI 画面文案示例(可直接改)
连接执行失败:无法执行数据库连接命令,请联系维护,并提供日志编号。
连接测试未通过:服务器拒绝或不可达,请检查网络、实例名与账号。
结构同步失败:启动对齐未完成,禁止开产,请维护员查看同步报告。
结构同步告警:可登录但限制依赖 TVP 的功能,请在维护窗口收敛。
工单插入失败:开工资讯未保存,请勿重复连续点击,先查看失败原因。
工单查询为空:没有符合条件的在制工单,请核对工单号与产线。
工单查询失败:读取在制工单时发生错误,请维护员处理。
文案区分「空结果」与「技术失败」,操作工体验会好一截。
深化:多解决方案复用模型程序集
若同厂多条线共用模型,把 POCO 放到独立程序集,版本随 MES 合同冻结。HMI 只引用模型包与 Hi.Ltd.SqlServer。同步选项按线区分:A 线 Include 表名单与 B 线不同。这样不会因为某线试验表污染另一线同步。模型包升级走变更单,与 HMI 发布列车可以偶合也可以解耦,但必须能回答「当前产线模型版本是多少」。
深化:从零到一的时间盒建议
给自己三个小时:
第一小时,只做 Connection 与 Result 判断,写日志。
第二小时,CreateTable 与 InsertTable,SSMS 对照。
第三小时,切 Factory,InitialSqlServer,Query,再做一次错误密码演练。
超时还没跑通,多半是环境(SQL 远程、防火墙、认证)而不是 API。把时间花在环境确认上,不要无改包版本碰运气。
深化:本期结语(进入第2期之前)
你已经具备:在工控上位机里用 Hi.Ltd.SqlServer 二零二六九点七点一六四二建立首条工单通路的能力;理解 SqlEntities 与 Factory 的分工;遵守 Successed 与 Content 的双层判断;知道 Auto 与同步的节奏;记住 Inqure、InsertTables、MaxLength 默认五百、Intergrated 等 DLL 拼写。
下一期把「表长什么样」与「怎么稳稳连上」一次讲透:属性建模决定结构质量,连接参数决定开机成败。两块都偏基础,却最常在现场被抄错。请带着本期跑通的工程继续,不要另起一个完全不同的连接写法,以免系列示例无法对照。
深化:附录——启动伪代码(逻辑序,便于板书)
第一步,读取配置中的数据源、库名、账号、超时、加密开关。
第二步,创建 SqlEntities 实例或调用 ConfigureSqlServer。
第三步,执行 Connection,若 Successed 为假或 Content 为假则中止启动。
第四步,若需要部署对齐,则 LoadModelAssembly 并 InitialSqlServer,解读双层 Successed。
第五步,将可用句柄交给 Factory(若尚未配置)。
第六步,业务允许后,插入工单并查询校验。
第七步,启动采集队列,批量写入走 InsertTables。
第八步,任何失败写日志与用户可读文案,不吞异常也不只弹英文。
把这八步画在白板上,比贴一堆 API 列表更有利于班组培训。细节参数仍以本文样例与 XML 为准。
深化:附录——团队约定模板(可复制到制度)
约定一,生产环境禁止默认打开同步纠偏开关。
约定二,所有数据库调用必须判断 Successed,布尔结果还要判断 Content。
约定三,禁止提交包含 Succeeded 或 InquireTable 或错误批量方法名的代码。
约定四,模型程序集变更必须附带同步影响说明与回滚方式。
约定五,sa 仅允许在隔离实验环境出现。
约定六,系列文档与示例以 NuGet 二零二六九点七点一六四二公开面为准,未发布 API 不得用于产线。
约定七,Auto 仅用于明确清单内的表。
约定八,连接密钥不入仓库,不入截图,不入聊天记录。
制度越短越能执行;执行越严,后面联查与过程篇才敢写「可运行样例」。
深化:附录——与采集网关共存时的注意点
不少工厂已有独立采集网关服务负责把 PLC 点位写入 SQL。此时上位机不要再对同一张原始点位表高频 InsertTables,以免双写。上位机应:
消费网关写好的摘要表或视图;
自己只管工单、配方、报警确认、班次;
若必须写同一库,表名前缀或架构分开,权限分开。
Hi.Ltd.SqlServer 很适合「上位机业务库」;与网关分工清晰后,InitialSqlServer 的表清单会小很多,启动更快,冲突更少。遇到「点位表被同步改列」这类事故,先查是否错误 Load 了网关模型程序集。
深化:附录——验收签字单(纸质可打)
项目名称:____________________ 产线:__________ 日期:__________
验收人确认:已使用包版本二零二六九点七点一六四二。
验收人确认:Connection 双层判断已演示通过。
验收人确认:首条工单插入与回读成功。
验收人确认:批量 InsertTables 试验完成。
验收人确认:生产配置未打开激进结构纠偏。
验收人确认:代码扫描无错误拼写 Succeeded 与 Inquire。
验收人确认:运行账号非 sa,密钥未入仓库。
签字:软件________ 设备________ 信息化________
没有签字单也能上线,但有签字单时,事后扯皮会少很多。建议与冒烟脚本一起归档。
深化:附录——读者自测十题(心里答即可)
Successed 少的是哪个字母?Content 是什么?Connection 返回什么类型?批量插入方法名是什么?查询方法名怎么拼?MaxLength 默认多长?Auto 能否单独生效?生产默不默认可纠偏已存在表结构?Factory 与 SqlEntities 谁带句柄自愈?外层同步成功但报告失败说明什么?
若十题都能快速作答,你可以进入第2期;若有两题含糊,请把本文避坑清单再读一遍,并在预发环境亲手踩一次对应失败。自测不是考试,是为了减少产线电话。
深化:附录——写完首条工单后的心情管理
第一次在产线工控机上看到工单网格里出现自己插入的记录,很值得高兴,但请立刻做两件事:备份当前库,把配置从 sa 换成运行账号再验证一次。高兴而不收尾,是现场事故的序章。收好尾,再打开第2期,把属性与连接补硬,后续 CRUD 与联查才会稳。
班组若仍有疑问,请带着日志中的 Successed、Content 与 Message 三字段来问,而不是只说「数据库坏了」;三字段齐全时,维护响应会快得多,产线停机也会更短。
深化:附录——和「只抄最小示例」说再见
最小示例能帮你建立信心,但不能直接当产线架构。产线至少要补齐:配置与密钥分离、启动日志模板、失败文案、Profile 纪律、同步节奏、批量队列、权限分离、冒烟与签字。本文后半大量「深化」段落,就是在逼你把最小示例升级成可交接的工程习惯。若你只把完整样例粘进 MainForm_Load 就发布,请至少把避坑清单打印贴在工控机侧门内侧,作为补丁级补偿措施。
深化:附录——变更冻结期怎么用本包
节假日与出货高峰常有「变更冻结」。冻结期内:禁止 InitialSqlServer 纠偏;禁止打开 SyncTableSchemaWhenExists;禁止新增 Auto 表依赖;只允许 Connection 自检与纯 DML。若必须热修程序,确保模型程序集相对库「只增不可见逻辑、不改表形」。冻结解除后再走维护窗口同步。把这条写进发布日历,比任何技术细节都更能减少节前事故。
冻结期若有人临时打开纠偏开关,必须在交接班记录里留下姓名、时间、原因与关闭确认;没有关闭确认,次日开产前视为高风险项,优先复查表结构与备份。
深化:附录——本文目录回顾(便于检索)
本文依次覆盖:目标与适用范围、早班工单故事线、安装与 Result 约定、SqlEntities 最小路径、Factory 与 InitialSqlServer、多 Profile、Auto、SchemaSyncOptions、Inqure 回读、手写对照、完整样例、避坑、FAQ,以及多组深化附录(双入口、检查单、班次节奏、报告双层、自愈边界、MES 边界、权限、失败注入、评审项、日志模板、口述稿、状态机、队列、命名、时区、唯一性、UI、密钥、版本、性能、演示顺序、Message、知识图谱、架构边界、冒烟、文案、复用模型、时间盒、结语、板书伪代码、团队约定、采集网关、签字单、自测十题、心情管理、冻结期)。检索时按需跳转,不必一次读完所有附录,但产线发布前建议至少读完避坑与验收相关段落。
下期预告
第2期:Attributes 全量建模 + Connection/Encrypt

139

被折叠的 条评论
为什么被折叠?



