使用英语阅读

通过


Entity Framework Core 概述

选择 .NET Aspire Entity Framework Core 客户端集成

在云原生解决方案(例如为创建 .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 客户端集成包括:

概述 EF 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;
}

此实体类表示包含三列的数据库表: IdTitleDescription

上下文类必须从 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();
}

备注

EF Core 还支持创建、修改和删除的记录和复杂的查询。 有关详细信息,请参阅查询数据和保存数据

如何 .NET.NET Aspire 提供帮助

.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使用实体类和上下文类定义模型。
  • 使用从应用主机传递的引用创建数据上下文的实例,并将其添加到依赖项注入 (DI) 容器。
  • 如果要与数据库交互,请从 DI 获取上下文,并正常使用它对数据库执行 LINQ 查询,就像进行常规代码操作一样。

一个显示.NET Aspire如何利用EF Core与数据库交互的图表。

定义EF Core模型和查询数据库在.NET Aspire项目中与任何其他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添加了上下文方法。

  • 检查依赖项注入 (DI) 容器中是否尚未注册同一类型的上下文。
  • 使用您传递给方法的连接名称,从应用程序生成器中获取返回的连接字符串。 此连接名称必须与将相应资源添加到应用主机项目时使用的名称匹配。
  • 如果您传递了 DbContext 选项,请应用这些选项。
  • 将指定的 DbContext 添加到启用上下文池的 DI 容器中。
  • 使用建议的默认设置,除非您已通过 .NET AspireEF Core 设置将其禁用。
    • 启用跟踪。
    • 启用健康检查。
    • 启用连接弹性。

如果想要简单的方法来创建上下文,并且尚不需要高级 .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 Core 添加和扩充上下文

或者,可以使用标准 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的方法。 这些丰富上下文的方法:

  • 如果传递了 EF Core 设置对象,则应用该对象。
  • 配置连接重试设置。
  • 使用建议的默认设置,除非您已通过 .NET AspireEF Core 设置将其禁用。
    • 启用跟踪。
    • 启用运行状况检查。
    • 启用连接弹性。

备注

在调用扩充方法之前,必须将上下文添加到 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 侦听器与 .NET Aspire 配合使用

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 侦听器及其使用的详细信息,请参阅 侦听器

在 EF Core 中将 .NET Aspire 与动态连接字符串配合使用

大多数微服务始终使用相同的凭据和其他设置连接到同一数据库,因此除非基础结构发生重大更改,否则它们始终使用相同的连接字符串。 但是,可能需要更改每个请求的连接字符串。 例如:

  • 可以将服务提供给多个租户,并需要使用不同的数据库,具体取决于客户发出请求。
  • 可能需要使用不同的数据库用户帐户对请求进行身份验证,具体取决于哪个客户发出了请求。

对于这些要求,可以使用代码来构建 动态连接字符串,然后使用它访问数据库并运行查询。 但是,.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 中使用 .NET Aspire 上下文工厂

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 中使用 .NET Aspire 进行上下文池化

在 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 池化

另请参阅


其他资源