mcpField
I
implementing-dapper-queries

implementing-dapper-queries

github.com/bitwarden数据与数据库16247 次安装今天更新
代码仓库

关于 implementing-dapper-queries

面向编码 Agent 的技能,指导实现 Bitwarden 的 Dapper 仓储和 MSSQL 存储过程,涵盖迁移、仓储映射与集成测试,并强调项目特有的数据库约束。

主要功能

  1. 01存储过程与迁移流程
  2. 02精简的 Dapper 仓储方法模式
  3. 03SSDT 与迁移语法规则
  4. 04MSSQL 架构及迁移注意事项
  5. 05EF Core 行为一致性要求

适用场景

  1. 01新增或更新 Bitwarden 数据库过程
  2. 02实现 Dapper 仓储方法
  3. 03创建安全的 MSSQL 迁移
  4. 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 由仓储基类和父级模式自动提供,而不是在单个仓储方法中临时编写。

工作流

  1. 定义/更新存储过程,位于 src/Sql/dbo/Stored Procedures/——使用普通的 CREATE PROCEDURE(SSDT 语法)
  2. 创建迁移脚本,位于 util/Migrator/DbScripts/,用于部署该过程——使用 CREATE OR ALTER PROCEDURE(幂等)
  3. 实现仓储方法,位于 src/Infrastructure/Dapper/Repositories/,使用 DapperServiceProvider 调用该过程
  4. 编写集成测试,使用 [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

延伸阅读

评论

支持 Markdown