关于 implementing-dapper-queries
面向编码 Agent 的技能,指导实现 Bitwarden 的 Dapper 仓储和 MSSQL 存储过程,涵盖迁移、仓储映射与集成测试,并强调项目特有的数据库约束。
主要功能
- 01存储过程与迁移流程
- 02精简的 Dapper 仓储方法模式
- 03SSDT 与迁移语法规则
- 04MSSQL 架构及迁移注意事项
- 05EF Core 行为一致性要求
适用场景
- 01新增或更新 Bitwarden 数据库过程
- 02实现 Dapper 仓储方法
- 03创建安全的 MSSQL 迁移
- 04按项目规范检查 SQL 变更
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ bitwarden/ ai-plugins --skill implementing-dapper-queries安装后在 Claude Code 中输入/implementing-dapper-queries确认已加载。
详细介绍
Repository 模式
所有 Dapper 实现都位于 src/Infrastructure/Dapper/Repositories/。每个仓储类都实现 src/Core/ 中的一个接口,并使用存储过程执行所有数据库操作。仓储方法有意保持精简——它将 C# 参数映射为 SQL 参数,并将结果集映射回领域对象。
使用存储过程,而非内联 SQL
默认模式是所有 Dapper 数据库操作都使用存储过程。某些情况下会使用内联 SQL——这些 SQL 由仓储基类和父级模式自动提供,而不是在单个仓储方法中临时编写。
工作流
- 定义/更新存储过程,位于
src/Sql/dbo/Stored Procedures/——使用普通的CREATE PROCEDURE(SSDT 语法) - 创建迁移脚本,位于
util/Migrator/DbScripts/,用于部署该过程——使用CREATE OR ALTER PROCEDURE(幂等) - 实现仓储方法,位于
src/Infrastructure/Dapper/Repositories/,使用DapperServiceProvider调用该过程 - 编写集成测试,使用
[DatabaseData]属性
存储过程是 MSSQL 查询行为的事实来源。Dapper 仓储方法保持精简——只负责映射参数和结果。
存储过程命名约定
过程遵循 {Entity}_{Action} 模式:User_Create、Cipher_ReadManyByUserId、Organization_DeleteById。工具和代码生成依赖此约定,将仓储方法映射到对应的存储过程。
容易让 AI 助手出错的关键决策
CREATE OR ALTER 与 CREATE PROCEDURE 的选择取决于文件位置
Bitwarden 会在不同上下文中维护每个存储过程的两份副本,且两者的工具链约束不同:
| 上下文 | 位置 | 必需语法 |
|---|---|---|
| SSDT 架构源文件 | src/Sql/dbo/Stored Procedures/ | CREATE PROCEDURE(普通形式) |
| 迁移脚本 | util/Migrator/DbScripts/ | CREATE OR ALTER PROCEDURE |
两者为何不同:
- SSDT 项目不支持
CREATE OR ALTER——使用它会导致构建错误。SSDT 通过自己的部署模型管理对象生命周期,因此每个源文件都必须包含单独的CREATE PROCEDURE。 - 迁移脚本必须是幂等的,因为它们可能会重新运行。无论存储过程是否已存在,
CREATE OR ALTER都能正常工作。切勿在迁移中使用单独的CREATE PROCEDURE。
SSDT 表文件需要 GO 批次分隔符
在 src/Sql/dbo/Tables/ 中,SSDT 要求在 CREATE TABLE 与后续任何 CREATE INDEX 或 CREATE NONCLUSTERED INDEX 语句之间使用 GO 批次分隔符。
-- CORRECT — GO separates DDL statements for SSDT
CREATE TABLE [dbo].[Example] (
[Id] UNIQUEIDENTIFIER NOT NULL,
[Name] NVARCHAR(256) NOT NULL,
CONSTRAINT [PK_Example] PRIMARY KEY CLUSTERED ([Id] ASC)
)
GO
CREATE NONCLUSTERED INDEX [IX_Example_Name]
ON [dbo].[Example] ([Name] ASC)
GO
新增参数必须可空且有默认值
向现有存储过程添加参数时,始终使用 @NewParam DATATYPE = NULL。现有调用方不会传入新参数——没有默认值就会导致调用失败。
NOT NULL 列:使用内联默认值,而非 ALTER-UPDATE-ALTER
先将列添加为可空、更新所有行,再将其改为 NOT NULL,会导致全表扫描。应改用 ADD [Column] INT NOT NULL CONSTRAINT DF_Table_Column DEFAULT 0——在 SQL Server 中,这只是元数据操作。这是 AI 助手在 Bitwarden 迁移中最常犯的错误。
切勿在迁移脚本中为大型表创建索引
在迁移脚本中为 dbo.Cipher、dbo.OrganizationUser 或其他大型表创建索引可能导致服务中断。脚本中切勿指定 ONLINE = ON——生产环境会自动处理此事,而且该选项在不支持的 SQL Server 版本中会失败。大型索引操作应放在 DbScripts_manual 中。
仅对数值类型使用默认值
对 BIT、TINYINT、INT、BIGINT 使用默认值。切勿对 VARCHAR、NVARCHAR 或 MAX 类型使用默认值。SQL Server 对这些类型的处理方式不同,对字符串设置默认值会导致 EF Core 迁移出现意外行为。
视图需要刷新元数据
修改表后,所有引用该表的视图都会出现过期的元数据。对受影响的视图调用 sp_refreshview。更改视图后,对依赖它的存储过程调用 sp_refreshsqlmodule。这是最常被遗忘的步骤。
GUID 列使用 UNIQUEIDENTIFIER
所有实体 ID 都是 UNIQUEIDENTIFIER,由应用代码中的 CoreHelpers.GenerateComb() 填充,而非由 SQL Server 填充。切勿在存储过程中使用 NEWID() 或 NEWSEQUENTIALID()。
EF 一致性要求
每个存储过程的行为都必须在 EF Core 实现中得到完全复现。编写新的存储过程时,请考虑 EF 实现将如何复现相同的筛选、排序和副作用。如果存储过程执行了复杂操作(例如条件更新、多表操作),请清楚记录预期行为,以便 EF 实现能够与之保持一致。
关键规则
以下是最常被违反的约定。Claude 无法在运行时获取链接文档,因此这些规则在此处列出:
- 每个存储过程开头都必须有
SET NOCOUNT ON - 参数命名: 使用 PascalCase 格式的
@ParamName,与 C# 属性名称一致 - 迁移脚本必须是幂等的 — 在
util/Migrator/DbScripts/中使用CREATE OR ALTER;在 SSDT 源代码(src/Sql/dbo/)中使用普通的CREATE PROCEDURE - 约束命名:
PK_TableName、FK_Child_Parent、IX_Table_Column、DF_Table_Column - 存储过程文件命名: 每个文件包含一个存储过程,并命名为
{Entity}_{Action}.sql
示例
创建存储过程 — SSDT 源代码与迁移脚本
-- SSDT source file: src/Sql/dbo/Stored Procedures/User_ReadById.sql
-- Use plain CREATE PROCEDURE (SSDT does not support CREATE OR ALTER)
CREATE PROCEDURE [dbo].[User_ReadById]
@Id UNIQUEIDENTIFIER
AS
BEGIN
SET NOCOUNT ON
SELECT * FROM [dbo].[User] WHERE [Id] = @Id
END
-- Migration script: util/Migrator/DbScripts/YYYY-MM-DD_00_AddUser_ReadById.sql
-- Use CREATE OR ALTER for idempotency
CREATE OR ALTER PROCEDURE [dbo].[User_ReadById]
@Id UNIQUEIDENTIFIER
AS
BEGIN
SET NOCOUNT ON
SELECT * FROM [dbo].[User] WHERE [Id] = @Id
END
添加 NOT NULL 列
-- CORRECT — metadata-only operation, no table scan
ALTER TABLE [dbo].[Organization]
ADD [UseCustomPermissions] BIT NOT NULL CONSTRAINT DF_Organization_UseCustomPermissions DEFAULT 0
-- WRONG — causes full table scan on large tables
ALTER TABLE [dbo].[Organization] ADD [UseCustomPermissions] BIT NULL
UPDATE [dbo].[Organization] SET [UseCustomPermissions] = 0
ALTER TABLE [dbo].[Organization] ALTER COLUMN [UseCustomPermissions] BIT NOT NULL
为现有存储过程添加参数
-- CORRECT — existing callers won't break
CREATE OR ALTER PROCEDURE [dbo].[Cipher_Create]
@Id UNIQUEIDENTIFIER,
@NewField NVARCHAR(MAX) = NULL -- default protects existing callers
-- WRONG — breaks all existing callers immediately
CREATE OR ALTER PROCEDURE [dbo].[Cipher_Create]
@Id UNIQUEIDENTIFIER,
@NewField NVARCHAR(MAX) -- no default = required parameter