快速入门:在 ASP.NET Core 应用中使用 Microsoft.Data.SqlClient

在这个快速入门中,你将使用 Microsoft.Data.SqlClient 7.1.1 构建一个 ASP.NET Core 最小 API。

你从一个空文件夹、.NET SDK和一个可以从电脑访问的SQL Server或Azure SQL数据库开始。 最后你会用一个 Web API 向表中添加消息,并通过 ID 将其读回。

在此过程中,应用程序:

  • 从ASP.NET Core配置中读取其连接字符串。 对于本地开发,该值来自用户机密,因此它不会出现在你的项目文件夹里。
  • 通过依赖注入对数据类进行注册。
  • 为每个请求开启并丢弃连接,允许默认连接池重用物理连接。
  • 将请求的 CancellationToken 传递给每个数据库调用。

该应用使用与 快速入门:使用 Microsoft.Data.SqlClient 进行连接和查询 相同的 dbo.SqlClientQuickstart 表。 你可以先做任意一个快速入门。

Prerequisites

当前的 ASP.NET Core 运行在 .NET 上,而不是 .NET 框架。 要使用 .NET Framework 的驱动,请参见 .NET Framework 项目。

数据库身份需要具备连接权限,以及创建表、向表中插入数据和从表中查询数据的权限。

创建项目

在一个空文件夹中,执行以下命令:

dotnet new web --framework net10.0 --name SqlClientWebQuickstart
cd SqlClientWebQuickstart
dotnet add package Microsoft.Data.SqlClient --version 7.1.1
dotnet add package Microsoft.Data.SqlClient.Extensions.Azure --version 7.1.1
dotnet user-secrets init

dotnet user-secrets init 在项目文件中添加UserSecretsId。 ASP.NET Core 仅在应用运行于 Development 环境中时加载用户机密,而这对于 dotnet run 是默认设置。

扩展包提供驱动程序的 Microsoft Entra 认证模式。 如果你的应用只使用 SQL 认证,可以省略 Microsoft.Data.SqlClient.Extensions.Azure。 使用两个软件包时,保持它们的版本相同。

配置连接

将 连接字符串 作为 ConnectionStrings:Quickstart 存储在用户机密中。 用户机密存储在你的用户配置文件里,而不是项目文件夹里,所以它们不会提交到源代码控制。 不要在appsettings.json中放入密码或生产连接字符串。

选择这些起始点之一并替换占位符。 dotnet user-secrets set PowerShell 和 Bash 的命令使用单引号。 在命令提示符中,使用双引号,因为命令提示符将单引号作为键和值的一部分存储。

Azure SQL with Microsoft Entra ID

使用拥有数据库访问权限的 Microsoft Entra 身份登录。 本地开发时,可以使用开发者工具,如 Azure CLI:

az login

然后存储 连接字符串。 从Azure SQL数据库复制出准确的服务器和数据库名称。 同样的 连接字符串 在 Microsoft Fabric 中用于 SQL 数据库,只要你使用来自 SQL 数据库项项的服务器和数据库名称,正如控制台快速启动中描述的那样。

dotnet user-secrets set 'ConnectionStrings:Quickstart' 'Server=tcp:<server>,1433;Database=<database>;Authentication=Active Directory Default;Encrypt=Strict;Connect Timeout=30'

此连接字符串不包含机密。 Active Directory Default 在本地使用你的开发者登录信息。 当你部署到 Azure 应用服务 或 Azure 容器应用 时,授予该应用的托管标识数据库访问权限,将值改为 Authentication=Active Directory Managed Identity,并在主机配置中将其设置为 ConnectionStrings__Quickstart。 有关其他选项,请参见 使用 Microsoft.Data.SqlClient 的 Microsoft Entra 身份验证。

带SQL认证的本地SQL Server

存储一个没有可信证书的本地开发实例的连接字符串。 使用单引号防止 PowerShell 和 Bash 扩展密码中的 $ 字符。

dotnet user-secrets set 'ConnectionStrings:Quickstart' 'Server=tcp:<server>,1433;Database=<database>;User ID=<user-id>;Password=<password>;Encrypt=true;TrustServerCertificate=true;Connect Timeout=30'

Caution

TrustServerCertificate=true 跳过服务器证书验证。 仅可与本地开发实例一起使用。 对于共享或生产型 SQL Server 实例,安装客户端信任的证书并移除 TrustServerCertificate=true。 有关详细信息,请参阅 Microsoft.Data.SqlClient 中的加密和证书验证。

添加代码

用以下代码替换 Program.cs 的内容:

using System.Data;
using Microsoft.Data.SqlClient;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddSingleton<MessageStore>();

var app = builder.Build();

await app.Services.GetRequiredService<MessageStore>()
    .EnsureTableAsync(app.Lifetime.ApplicationStopping);

app.MapPost("/messages", async (
    NewMessage input, MessageStore store, CancellationToken cancellationToken) =>
{
    if (string.IsNullOrWhiteSpace(input.Text) || input.Text.Length > 200)
    {
        return Results.BadRequest("Text must be 1 to 200 characters.");
    }

    Message message = await store.AddAsync(input.Text, cancellationToken);
    return Results.Created($"/messages/{message.Id}", message);
});

app.MapGet("/messages/{id:int}", async (
    int id, MessageStore store, CancellationToken cancellationToken) =>
    await store.GetAsync(id, cancellationToken) is Message message
        ? Results.Ok(message)
        : Results.NotFound());

app.Run();

record NewMessage(string Text);

record Message(int Id, string Text, DateTimeOffset CreatedAt);

sealed class MessageStore(IConfiguration configuration)
{
    private readonly string _connectionString =
        configuration.GetConnectionString("Quickstart")
        ?? throw new InvalidOperationException(
            "Set the ConnectionStrings:Quickstart configuration value.");

    public async Task EnsureTableAsync(CancellationToken cancellationToken)
    {
        const string sql = """
            IF OBJECT_ID(N'dbo.SqlClientQuickstart', N'U') IS NULL
            BEGIN
                CREATE TABLE dbo.SqlClientQuickstart
                (
                    Id int IDENTITY(1, 1) PRIMARY KEY,
                    Message nvarchar(200) NOT NULL,
                    CreatedAt datetimeoffset NOT NULL
                        CONSTRAINT DF_SqlClientQuickstart_CreatedAt
                        DEFAULT sysdatetimeoffset()
                );
            END;
            """;

        await using var connection = new SqlConnection(_connectionString);
        await connection.OpenAsync(cancellationToken);
        await using var command =
            new SqlCommand(sql, connection) { CommandTimeout = 30 };
        await command.ExecuteNonQueryAsync(cancellationToken);
    }

    public async Task<Message> AddAsync(
        string text, CancellationToken cancellationToken)
    {
        const string sql = """
            INSERT INTO dbo.SqlClientQuickstart (Message)
            OUTPUT INSERTED.Id, INSERTED.Message, INSERTED.CreatedAt
            VALUES (@message);
            """;

        await using var connection = new SqlConnection(_connectionString);
        await connection.OpenAsync(cancellationToken);
        await using var command =
            new SqlCommand(sql, connection) { CommandTimeout = 30 };
        command.Parameters.Add("@message", SqlDbType.NVarChar, 200).Value = text;

        await using SqlDataReader reader =
            await command.ExecuteReaderAsync(cancellationToken);
        await reader.ReadAsync(cancellationToken);
        return new Message(
            reader.GetInt32(0), reader.GetString(1), reader.GetDateTimeOffset(2));
    }

    public async Task<Message?> GetAsync(
        int id, CancellationToken cancellationToken)
    {
        const string sql = """
            SELECT Id, Message, CreatedAt
            FROM dbo.SqlClientQuickstart
            WHERE Id = @id;
            """;

        await using var connection = new SqlConnection(_connectionString);
        await connection.OpenAsync(cancellationToken);
        await using var command =
            new SqlCommand(sql, connection) { CommandTimeout = 30 };
        command.Parameters.Add("@id", SqlDbType.Int).Value = id;

        await using SqlDataReader reader =
            await command.ExecuteReaderAsync(cancellationToken);
        return await reader.ReadAsync(cancellationToken)
            ? new Message(
                reader.GetInt32(0), reader.GetString(1), reader.GetDateTimeOffset(2))
            : null;
    }
}

运行应用

在固定端口启动应用:

dotnet run --urls http://localhost:5000

启动时,如果表格不存在,应用会生成该表。 当应用准备好时,输出内容包括以下几行:

info: Microsoft.Hosting.Lifetime[14]
      Now listening on: http://localhost:5000
info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
      Hosting environment: Development

如果应用无法访问数据库,启动时会停止并显示SqlException。 常见原因请参见 故障排除。

调用 API

让应用继续运行,打开第二个终端。 通过 POST 请求添加一条消息,然后通过 ID 使用 GET 请求将其读回。 如果表格已经有行,例如来自控制台快速入门的行,请将 GET 请求中的 1 替换为 POST 请求返回的 id。

对于 PowerShell:

Invoke-RestMethod -Method Post -Uri http://localhost:5000/messages -ContentType application/json -Body (@{ text = 'Hello from ASP.NET Core' } | ConvertTo-Json)
Invoke-RestMethod http://localhost:5000/messages/1

PowerShell 将每个 JSON 响应格式化为表。 第一行是 201 Created 对POST请求的响应,第二行是GET响应:

id text                    createdAt
-- ----                    ---------
 1 Hello from ASP.NET Core 2026-10-21T00:04:43.3588978+00:00
 1 Hello from ASP.NET Core 2026-10-21T00:04:43.3588978+00:00

Windows PowerShell 5.1 显示createdAt为 API 返回的字符串。 PowerShell 7 将其转换为当地时间的日期,例如 10/20/2026 7:04:43 PM。

在 Windows PowerShell 中,curl 是 Invoke-WebRequest 的别名,而 Invoke-WebRequest 不接受 curl 选项。 使用PowerShell命令,或者从命令提示符运行 curl.exe 。

命令提示符:

curl -X POST http://localhost:5000/messages -H "Content-Type: application/json" -d "{\"text\":\"Hello from ASP.NET Core\"}"
curl http://localhost:5000/messages/1

命令提示符不把单引号当作引号字符处理,所以 JSON 正文使用双引号并带反斜杠转义。 每个命令打印 JSON 响应:

{"id":1,"text":"Hello from ASP.NET Core","createdAt":"2026-10-21T00:05:15.4936763+00:00"}
{"id":1,"text":"Hello from ASP.NET Core","createdAt":"2026-10-21T00:05:15.4936763+00:00"}

对于 Bash:

curl -X POST http://localhost:5000/messages -H "Content-Type: application/json" -d '{"text":"Hello from ASP.NET Core"}'
curl http://localhost:5000/messages/1
{"id":1,"text":"Hello from ASP.NET Core","createdAt":"2026-10-20T20:44:17.8634925+00:00"}
{"id":1,"text":"Hello from ASP.NET Core","createdAt":"2026-10-20T20:44:17.8634925+00:00"}

你的ID和时间戳不同。 如果你先运行控制台快速入门,ID 会从其中的记录继续。 请求不存在的 ID 会返回 404 Not Found。 空消息返回 400 Bad Request。

在第一个终端按Ctrl+C可以停止应用。

代码的工作原理

  • 该应用将 MessageStore 注册为单例。 它只保存连接字符串,并且只从 IConfiguration 读取一次。 它无法保持连接。
  • 每个方法创建一个 SqlConnection,打开它,运行一个命令,并用 await using 将其释放。 处置时将物理连接返回连接池,下一个请求会重用它。 连接池默认开启,所以不需要配置。 关于调优选项,请参见使用 Microsoft.Data.SqlClient 的 SQL Server 连接池。
  • ASP.NET Core 将 CancellationToken 参数绑定到请求。 如果客户端断开连接,令牌会取消打开操作、命令和读取操作。
  • @message和@id参数与列类型和大小相匹配。 API 在将该值发送到数据库之前,会验证消息长度。
  • OUTPUT INSERTED.Id, INSERTED.Message, INSERTED.CreatedAt 返回插入的行,包括服务器生成的ID和时间戳,无需第二次查询。
  • 在其他环境中,ASP.NET Core 从环境变量(如 ConnectionStrings:Quickstart)或其他配置提供程序(如 Azure 密钥保管库)读取相同的 ConnectionStrings__Quickstart 密钥。

Troubleshoot

错误或症状 需要检查的事项 更多信息
Set the ConnectionStrings:Quickstart configuration value. 应用没找到连接字符串。 在项目文件夹里运行 dotnet user-secrets list ,检查应用是否在 Development 环境中运行。 在 ASP.NET Core 中开发时安全存储应用机密
System.ArgumentException: Cannot find an authentication provider for 'ActiveDirectoryDefault' 启动时 连接字符串 使用Active Directory认证模式,但项目不引用 Microsoft.Data.SqlClient.Extensions.Azure。 添加与 Microsoft.Data.SqlClient 相同版本的软件包。 Microsoft Entra 认证在 7.0 版本中迁移到扩展包
SqlException 启动时 应用无法连接或创建该表。 错误编号用于识别原因,例如,登录失败时为 18456。 控制台快速入门中的故障排除
415 Unsupported Media Type 请求未设置 Content-Type: application/json。 最简 API 快速指南
Cannot bind parameter 'Headers' 你在 Windows PowerShell 中运行了 curl 命令,其中使用 curl 运行 Invoke-WebRequest。 使用“ 调用API”中的PowerShell命令。 Invoke-RestMethod
''' is an invalid start of a value 你在命令提示符里运行了 Bash curl 命令,它会把单引号作为正文的一部分发送。 在 “调用 API”中使用命令提示符命令。 调用API

清理资源

用任何查询工具删除该表:

DROP TABLE IF EXISTS dbo.SqlClientQuickstart;

移除存储的 连接字符串。 从项目文件夹中运行:

dotnet user-secrets clear

然后删除项目文件夹。 从父文件夹中,在PowerShell中执行以下命令:

Remove-Item -Recurse -Force SqlClientWebQuickstart

对于 Bash:

rm -rf SqlClientWebQuickstart

后续步骤