培训
模块
在 .NET Aspire 项目中使用数据库 - Training
了解 .NET Aspire 可以使用内置组件连接到的数据库系统。 然后,了解如何配置与关系数据库和非关系数据库中的连接,以及如何在其中存储数据。
在云原生解决方案(例如为创建 .NET.NET Aspire)中,微服务通常需要将数据存储在关系数据库中。 .NET Aspire 包括可用于简化该任务的集成,其中一些集成使用 Entity Framework Core (EF Core) 对象关系映射器 (O/RM) 方法来简化该过程。
开发人员使用 O/RM 通过代码对象而不是 SQL 查询来处理数据库。 EF Core 通过基于 Language-Integrated 查询(LINQ)查询生成 SQL 查询来自动对数据库交互进行编码。 EF Core 支持各种数据库提供程序,包括 SQL Server、PostgreSQL和 MySQL,因此在遵循面向对象的原则的同时,可以轻松地与关系数据库进行交互。
最常用的 .NET AspireEF Core 客户端集成包括:
O/RM 创建与数据库中定义的架构和关系匹配的模型。 针对此模型进行代码查询数据、创建新记录或进行其他更改。 模型中 EF Core 包括:
实体类可能如下所示:
using System.ComponentModel.DataAnnotations;
namespace SupportDeskProject.Data;
public sealed class SupportTicket
{
public int Id { get; set; }
[Required]
public string Title { get; set; } = string.Empty;
[Required]
public string Description { get; set; } = string.Empty;
}
此实体类表示包含三列的数据库表: Id、 Title 和 Description。
上下文类必须从 DbContext 继承,如下所示:
using Microsoft.EntityFrameworkCore;
using System.Reflection.Metadata;
namespace SupportDeskProject.Data;
public class TicketContext(DbContextOptions options) : DbContext(options)
{
public DbSet<SupportTicket> Tickets => Set<SupportTicket>();
}
此上下文表示具有单个支持票证表的数据库。 通常为数据库中每个工作单元创建上下文实例。 例如,工作单元可能是创建新客户,并且需要在“客户”和“地址”表中进行更改。 完成工作单元后,应释放上下文。
备注
要了解有关在EF Core中创建模型的更多信息,请参阅EF Core文档中的创建和配置模型。
创建模型后,可以使用 LINQ 对其进行查询:
using (var db = new TicketContext())
{
var tickets = await db.Tickets
.Where(t => t.Title = "Unable to log on")
.OrderBy(t => t.Description)
.ToListAsync();
}
.NET .NET Aspire 旨在帮助构建由多个微服务组成的可观察的、生产就绪的云原生解决方案。 它协调多个项目,其中每个项目可能是由专用团队编写的微服务,并相互连接。 它提供集成,使连接到常见服务(如数据库)变得容易。
如果你想在任何微服务中使用EF Core,.NET Aspire可以通过以下方式帮助:
在 App Host 项目中集中管理数据库容器或与现有数据库的连接,并将其引用传递给使用它的任何项目。
重要
在 .NET Aspire中,EF Core 由客户端集成实现,而不是托管集成。 应用主机中数据库的集中管理并不涉及 EF Core,而是在使用微服务项目中运行。 有关详细信息,请参阅 Cosmos DB 托管集成、 MySQL Pomelo 托管集成、 Oracle 托管集成、 PostgreSQL 托管集成或 SQL Server 托管集成。
提供 EF Core感知集成,使在微服务项目中轻松创建上下文。 有一些EF Core集成用于SQL Server、MySQL、PostgreSQL、Oracle、Cosmos DB和其他常用数据库系统。
要在微服务中使用EF Core,您必须:
定义EF Core模型和查询数据库在.NET Aspire项目中与任何其他EF Core应用中相同。 但是,创建数据上下文会有所不同。 本文的其余部分介绍如何在.NET Aspire项目中创建和配置EF Core上下文。
在 EF Core中,上下文 是用于与数据库交互的类。 上下文继承自 DbContext 类。 它们通过 DbSet<T>
类型的属性提供对数据库的访问权限,其中每个 DbSet
表示数据库中实体的表或集合。 上下文还管理数据库连接、跟踪对实体的更改,以及处理保存数据和执行查询等作。
每个 .NET AspireEF Core 客户端集成都包含名为 Add{DatabaseSystem}DbContext
的扩展方法,其中 {DatabaseSystem} 是标识要使用的数据库产品的名称。 例如,考虑 SQL ServerEF Core 客户端集成,该方法命名为 AddSqlServerDbContext,对于 PostgreSQL 客户端集成,该方法命名为 AddNpgsqlDbContext。
这些.NET.NET Aspire添加了上下文方法。
DbContext
选项,请应用这些选项。DbContext
添加到启用上下文池的 DI 容器中。如果想要简单的方法来创建上下文,并且尚不需要高级 .NET Aspire 自定义,请使用这些 EF Core 添加上下文方法。
builder.AddSqlServerDbContext<ExampleDbContext>(connectionName: "database");
提示
有关 SQL Server 托管和客户端集成的详细信息,请参阅 .NET AspireSQL ServerEntity Framework Core 集成。
builder.AddNpgsqlDbContext<ExampleDbContext>(connectionName: "database");
提示
有关 PostgreSQL 托管和客户端集成的详细信息,请参阅 .NET AspirePostgreSQLEntity Framework Core 集成。
builder.AddOracleDatabaseDbContext<ExampleDbContext>(connectionName: "database");
提示
有关 Oracle 数据库托管和客户端集成的详细信息,请参阅 .NET AspireOracleEntity Framework Core 集成。
builder.AddMySqlDbContext<ExampleDbContext>(connectionName: "database");
提示
有关 MySQL 托管和客户端集成的详细信息,请参阅 .NET Aspire Pomelo MySQLEntity Framework Core 集成。
使用与任何其他服务相同的方式从 DI 容器获取 ExampleDbContext
对象:
public class ExampleService(ExampleDbContext context)
{
// Use context...
}
或者,可以使用标准 EF CoreAddDbContextPool 方法将上下文添加到 DI 容器,这在非.NET.NET Aspire 项目中通常使用:
builder.Services.AddDbContextPool<ExampleDbContext>(options =>
{
var connectionString = builder.Configuration.GetConnectionString("database")
?? throw new InvalidOperationException("Connection string 'database' not found.");
options.UseSqlServer(connectionString);
});
builder.Services.AddDbContextPool<ExampleDbContext>(options =>
{
var connectionString = builder.Configuration.GetConnectionString("database")
?? throw new InvalidOperationException("Connection string 'database' not found.");
options.UseNpgsql(connectionString);
});
builder.Services.AddDbContextPool<ExampleDbContext>(options =>
{
var connectionString = builder.Configuration.GetConnectionString("database")
?? throw new InvalidOperationException("Connection string 'database' not found.");
options.UseOracle(connectionString);
});
builder.Services.AddDbContextPool<ExampleDbContext>(options =>
{
var connectionString = builder.Configuration.GetConnectionString("database")
?? throw new InvalidOperationException("Connection string 'database' not found.");
options.UseMySql(connectionString);
});
以这种方式创建上下文时具有更大的灵活性,例如:
默认情况下,以这种方式配置的上下文不包括 .NET.NET Aspire 功能,例如遥测和运行状况检查。 若要添加这些功能,每个 .NET AspireEF Core 客户端集成都包含一个名为 Enrich\<DatabaseSystem\>DbContext
的方法。 这些丰富上下文的方法:
备注
在调用扩充方法之前,必须将上下文添加到 DI 容器。
builder.EnrichSqlServerDbContext<ExampleDbContext>(
configureSettings: settings =>
{
settings.DisableRetry = false;
settings.CommandTimeout = 30; // seconds
});
builder.EnrichNpgsqlDbContext<ExampleDbContext>(
configureSettings: settings =>
{
settings.DisableRetry = false;
settings.CommandTimeout = 30; // seconds
});
builder.EnrichOracleDatabaseDbContext<ExampleDbContext>(
configureSettings: settings =>
{
settings.DisableRetry = false;
settings.CommandTimeout = 30; // seconds
});
builder.EnrichMySqlDbContext<ExampleDbContext>(
configureSettings: settings =>
{
settings.DisableRetry = false;
settings.CommandTimeout = 30; // seconds
});
使用与前面的示例相同的代码从 DI 容器获取上下文:
public class ExampleService(ExampleDbContext context)
{
// Use context...
}
EF Core 拦截器允许开发人员在执行数据库查询和命令的不同阶段插入并修改数据库操作。 可以使用它们通过自己的代码记录、修改或禁止操作。 您的拦截器必须从 IInterceptor 接口实现一个或多个接口。
.NET
.NET Aspire
Add\<DatabaseSystem\>DbContext
方法不支持依赖于 DI 服务的侦听器。 使用 EF CoreAddDbContextPool 方法并在选项生成器中调用 AddInterceptors 方法:
builder.Services.AddDbContextPool<ExampleDbContext>((serviceProvider, options) =>
{
options.UseSqlServer(builder.Configuration.GetConnectionString("database"));
options.AddInterceptors(serviceProvider.GetRequiredService<ExampleInterceptor>());
});
builder.EnrichSqlServerDbContext<ExampleDbContext>(
configureSettings: settings =>
{
settings.DisableRetry = false;
settings.CommandTimeout = 30; // seconds
});
builder.Services.AddDbContextPool<ExampleDbContext>((serviceProvider, options) =>
{
options.UseNpgsql(builder.Configuration.GetConnectionString("database"));
options.AddInterceptors(serviceProvider.GetRequiredService<ExampleInterceptor>());
});
builder.EnrichNpgsqlDbContext<ExampleDbContext>(
configureSettings: settings =>
{
settings.DisableRetry = false;
settings.CommandTimeout = 30; // seconds
});
builder.Services.AddDbContextPool<ExampleDbContext>((serviceProvider, options) =>
{
options.UseOracle(builder.Configuration.GetConnectionString("database"));
options.AddInterceptors(serviceProvider.GetRequiredService<ExampleInterceptor());
});
builder.EnrichOracleDatabaseDbContext<ExampleDbContext>(
configureSettings: settings =>
{
settings.DisableRetry = false;
settings.CommandTimeout = 30; // seconds
});
builder.Services.AddDbContextPool<ExampleDbContext>((serviceProvider, options) =>
{
options.UseMySql(builder.Configuration.GetConnectionString("database"));
options.AddInterceptors(serviceProvider.GetRequiredService<ExampleInterceptor>());
});
builder.EnrichMySqlDbContext<ExampleDbContext>(
configureSettings: settings =>
{
settings.DisableRetry = false;
settings.CommandTimeout = 30; // seconds
});
备注
有关 EF Core 侦听器及其使用的详细信息,请参阅 侦听器。
大多数微服务始终使用相同的凭据和其他设置连接到同一数据库,因此除非基础结构发生重大更改,否则它们始终使用相同的连接字符串。 但是,可能需要更改每个请求的连接字符串。 例如:
对于这些要求,可以使用代码来构建 动态连接字符串,然后使用它访问数据库并运行查询。 但是,.NET.NET AspireAdd\<DatabaseSystem\>DbContext
方法不支持此方法。 相反,必须使用 EF Core 方法创建上下文,然后对其进行扩充:
var connectionStringWithPlaceHolder = builder.Configuration.GetConnectionString("database")
?? throw new InvalidOperationException("Connection string 'database' not found.");
var connectionString = connectionStringWithPlaceHolder.Replace("{DatabaseName}", "ContosoDatabase");
builder.Services.AddDbContext<ExampleDbContext>(options =>
options.UseSqlServer(connectionString
?? throw new InvalidOperationException("Connection string 'database' not found.")));
builder.EnrichSqlServerDbContext<ExampleDbContext>(
configureSettings: settings =>
{
settings.DisableRetry = false;
settings.CommandTimeout = 30; // seconds
});
var connectionStringWithPlaceHolder = builder.Configuration.GetConnectionString("database")
throw new InvalidOperationException("Connection string 'database' not found.");
var connectionString = connectionStringWithPlaceHolder.Replace("{DatabaseName}", "ContosoDatabase");
builder.Services.AddDbContext<ExampleDbContext>(options =>
options.UseNpgsql(connectionString
?? throw new InvalidOperationException("Connection string 'database' not found.")));
builder.EnrichNpgsqlDbContext<ExampleDbContext>(
configureSettings: settings =>
{
settings.DisableRetry = false;
settings.CommandTimeout = 30; // seconds
});
var connectionStringWithPlaceHolder = builder.Configuration.GetConnectionString("database")
throw new InvalidOperationException("Connection string 'database' not found.");
var connectionString = connectionStringWithPlaceHolder.Replace("{DatabaseName}", "ContosoDatabase");
builder.Services.AddDbContext<ExampleDbContext>(options =>
options.UseOracle(connectionString
?? throw new InvalidOperationException("Connection string 'database' not found.")));
builder.EnrichOracleDatabaseDbContext<ExampleDbContext>(
configureSettings: settings =>
{
settings.DisableRetry = false;
settings.CommandTimeout = 30; // seconds
});
var connectionStringWithPlaceHolder = builder.Configuration.GetConnectionString("database")
throw new InvalidOperationException("Connection string 'database' not found.");
var connectionString = connectionStringWithPlaceHolder.Replace("{DatabaseName}", "ContosoDatabase");
builder.Services.AddDbContext<ExampleDbContext>(options =>
options.UseMySql(connectionString
?? throw new InvalidOperationException("Connection string 'database' not found.")));
builder.EnrichMySqlDbContext<ExampleDbContext>(
configureSettings: settings =>
{
settings.DisableRetry = false;
settings.CommandTimeout = 30; // seconds
});
上述代码在运行时将连接字符串中的占位符 {DatabaseName}
替换为字符串 ContosoDatabase
,并在创建上下文之前增强它。
EF Core 上下文是一个对象,设计用于单个工作单元。 例如,如果要向数据库添加新客户,可能需要在“Customers”表中添加一行,以及在“地址”表中添加一行。 应该获取EF Core上下文,将新客户和地址实体添加到其中,调用SaveChangesAsync,然后释放上下文。
在许多类型的 Web 应用程序中,例如 ASP.NET 应用程序,每个 HTTP 请求都与针对数据库的单个工作单元紧密对应。 如果 .NET Aspire 微服务是 ASP.NET 应用程序或类似的 Web 应用程序,则可以使用上述标准 EF CoreAddDbContextPool 方法注册绑定到当前 HTTP 请求的上下文。 请记住调用 .NET.NET AspireEnrich\<DatabaseSystem\>DbContext
方法以获取运行状况检查、跟踪和其他功能。 使用此方法时,上下文生命周期将与 Web 请求关联。 完成工作单元时,无需调用 Dispose 方法。
其他应用程序类型(如 ASP.NET CoreBlazor)不一定将每个请求与工作单元保持一致,因为它们将依赖项注入用于不同的服务范围。 在这些应用中,可能需要在单个 HTTP 请求和响应中执行多个工作单元,每个工作单元具有不同的上下文。 若要实现此方法,可以通过调用 EF CoreAddPooledDbContextFactory 方法注册上下文工厂。 此方法还与 .NET.NET AspireEnrich\<DatabaseSystem\>DbContext
方法很好地合作:
builder.Services.AddPooledDbContextFactory<ExampleDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("database")
?? throw new InvalidOperationException("Connection string 'database' not found.")));
builder.EnrichSqlServerDbContext<ExampleDbContext>(
configureSettings: settings =>
{
settings.DisableRetry = false;
settings.CommandTimeout = 30; // seconds
});
builder.Services.AddPooledDbContextFactory<ExampleDbContext>(options =>
options.UseNpgsql(builder.Configuration.GetConnectionString("database")
?? throw new InvalidOperationException("Connection string 'database' not found.")));
builder.EnrichNpgsqlDbContext<ExampleDbContext>(
configureSettings: settings =>
{
settings.DisableRetry = false;
settings.CommandTimeout = 30; // seconds
});
builder.Services.AddPooledDbContextFactory<ExampleDbContext>(options =>
options.UseOracle(builder.Configuration.GetConnectionString("database")
?? throw new InvalidOperationException("Connection string 'database' not found.")));
builder.EnrichOracleDatabaseDbContext<ExampleDbContext>(
configureSettings: settings =>
{
settings.DisableRetry = false;
settings.CommandTimeout = 30; // seconds
});
builder.Services.AddPooledDbContextFactory<ExampleDbContext>(options =>
options.UseMySql(builder.Configuration.GetConnectionString("database")
?? throw new InvalidOperationException("Connection string 'database' not found.")));
builder.EnrichMySqlDbContext<ExampleDbContext>(
configureSettings: settings =>
{
settings.DisableRetry = false;
settings.CommandTimeout = 30; // seconds
});
请注意,上述代码在 DI 容器中添加和扩充 上下文工厂。 从容器中检索此内容时,必须添加一行代码,以从中创建 上下文:
public class ExampleService(IDbContextFactory<ExampleDbContext> contextFactory)
{
using (var context = contextFactory.CreateDbContext())
{
// Use context...
}
}
以这种方式从工厂创建的上下文不会被自动销毁,因为它们未绑定到 HTTP 请求的生命周期。 你必须确保代码释放它们。 在此示例中,using
代码块确保了废弃物的安全处置。
在 EF Core 中,上下文对象的创建和释放相对较快,因此大多数应用程序可以根据需要灵活设置这些对象,而不会影响其性能。 但是,开销不是零,因此,如果微服务密集地创建上下文,你可能会观察到性能欠佳。 在这种情况下,请考虑使用上下文池。
上下文池是 EF Core的一项功能。 上下文会正常创建,但在释放其中一个时,不会销毁,而是被重置并存储在池中。 下次代码创建上下文时,将返回存储的上下文,以避免创建新上下文的额外开销。
在 .NET.NET Aspire 耗用项目中,有三种方法可以使用上下文池:
使用 .NET.NET AspireAdd\<DatabaseSystem\>DbContext
方法创建上下文。 这些方法会自动创建上下文池。
调用 EF CoreAddDbContextPool 方法,而不是 EF CoreAddDbContext 方法。
builder.Services.AddDbContextPool<ExampleDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("database")
?? throw new InvalidOperationException("Connection string 'database' not found.")));
builder.Services.AddDbContextPool<ExampleDbContext>(options =>
options.UseNpgsql(builder.Configuration.GetConnectionString("database")
?? throw new InvalidOperationException("Connection string 'database' not found.")));
builder.Services.AddDbContextPool<ExampleDbContext>(options =>
options.UseOracle(builder.Configuration.GetConnectionString("database")
?? throw new InvalidOperationException("Connection string 'database' not found.")));
builder.Services.AddDbContextPool<ExampleDbContext>(options =>
options.UseMySql(builder.Configuration.GetConnectionString("database")
?? throw new InvalidOperationException("Connection string 'database' not found.")));
调用 EF CoreAddPooledDbContextFactory 方法,而不是 EF CoreAddDbContextFactory 方法。
builder.Services.AddPooledDbContextFactory<ExampleDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("database")
?? throw new InvalidOperationException("Connection string 'database' not found.")));
builder.Services.AddPooledDbContextFactory<ExampleDbContext>(options =>
options.UseNpgsql(builder.Configuration.GetConnectionString("database")
?? throw new InvalidOperationException("Connection string 'database' not found.")));
builder.Services.AddPooledDbContextFactory<ExampleDbContext>(options =>
options.UseOracle(builder.Configuration.GetConnectionString("database")
?? throw new InvalidOperationException("Connection string 'database' not found.")));
builder.Services.AddPooledDbContextFactory<ExampleDbContext>(options =>
options.UseMySql(builder.Configuration.GetConnectionString("database")
?? throw new InvalidOperationException("Connection string 'database' not found.")));
请记得使用最后两种方法后扩充上下文,如上所述。
重要
只有在基本上下文状态返回到池中时才会重置。 如果已手动更改 DbConnection
或其他服务的状态,则还必须手动重置它。 此外,上下文池可防止使用 OnConfiguring
来配置上下文。 有关详细信息,请参阅 DbContext 池化。
培训
模块
在 .NET Aspire 项目中使用数据库 - Training
了解 .NET Aspire 可以使用内置组件连接到的数据库系统。 然后,了解如何配置与关系数据库和非关系数据库中的连接,以及如何在其中存储数据。