Remarque
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de modifier des répertoires.
重要
2027 年 3 月 31 日,使用EXTERNAL DATA SOURCE类型SHARD_MAP_MANAGER的分片映射管理器模式下的弹性查询即将结束支持。 在此日期之后,现有工作负荷将继续运行,但将不再获得支持,并且将不再能够创建新的类型 SHARD_MAP_MANAGER 外部数据源。 有关迁移选项,请参阅 弹性查询分片映射管理器模式的迁移指南。
此文档介绍与弹性数据库工具集成所需的实体框架应用程序中的更改。 重点是使用 Entity Framework Code First 方法撰写分片映射管理和数据相关路由。 EF 的 Code First – 新数据库教程在本文档中充当运行示例。 本文档附带的示例代码是 Visual Studio 代码示例中弹性数据库工具示例的一部分。
注意
本文不适用于 Entity Framework Core (EF Core)。
下载并运行示例代码
若要下载本文的代码:
- 需要 Visual Studio 2012 或更高版本。
- 下载 Elastic DB Tools for Azure SQL - Entity Framework Integration sample(Azure SQL 弹性数据库工具 - 实体框架集成示例)。 将示例解压缩到所选位置。
- 启动 Visual Studio。
- 在 Visual Studio 中,选择“文件”->“打开项目/解决方案”。
- 在 “打开项目 ”对话框中,导航到下载的示例,然后选择
EntityFrameworkCodeFirst.sln打开示例。
要运行该示例,需要在 Azure SQL 数据库中创建三个空数据库:
- 分片映射管理器数据库
- 分片 1 数据库
- 分片 2 数据库
创建这些数据库后,使用服务器名称、数据库名称和凭据填充 Program.cs 中的占位符以连接到数据库。 在 Visual Studio 中生成解决方案。 在生成过程中,Visual Studio 会下载弹性数据库客户端库、Entity Framework 和暂时性故障处理所需的 NuGet 包。 确保已为解决方案启用还原 NuGet 包。 可以通过右键单击 Visual Studio 解决方案资源管理器中的解决方案文件启用此设置。
实体框架工作流
实体框架开发人员依靠以下四个工作流之一构建应用程序并确保应用程序对象的持久性:
- Code First(新数据库) :EF 开发人员在应用程序代码中创建模型,然后 EF 从中生成数据库。
- Code First(现有数据库) :开发人员让 EF 从现有数据库生成模型的应用程序代码。
- Model First:开发人员在 EF 设计器中创建模型,EF 从该模型创建数据库。
- Database First:开发人员使用 EF 工具从现有数据库推断模型。
所有这些方法依靠 DbContext 类为应用程序透明管理数据库连接和数据库架构。 DbContext 基类上的不同构造函数允许对连接创建、数据库引导和架构创建进行不同级别的控制。 挑战主要产生于这一事实:由 EF 提供的数据库连接管理与弹性数据库客户端库提供的数据依赖型路由接口的连接管理功能交叉。
弹性数据库工具假设条件
有关术语定义,请参阅弹性数据库工具词汇表。
借助弹性数据库客户端库,可以定义称为 shardlet 的应用程序数据分区。 Shardlet 由分片键标识,并且映射到特定数据库。 应用程序可以根据需要拥有任意数量的数据库,并分配数据分片以提供足够的容量或性能,以满足当前的业务需求。 分片键值到数据库的映射由弹性数据库客户端 API 提供的分片映射表存储。 这种功能称为 Shard Map Management,简称为 SMM。 分片映射还为带有分片键的请求充当数据库连接的代理。 此功能称为“数据依赖型路由”。
分片映射管理器防止用户在 shardlet 数据中出现不一致视图,当发生并发 shardlet 管理操作时(例如将数据从一个分片重新分配到另一个分片)可能发生此情况。 为此,客户端库管理的分片映射会代理应用程序的数据库连接。 当分片管理操作可能影响为其创建数据库连接的 shardlet 时,此操作允许分片映射功能自动终止该连接。 此方法需要与 EF 的一些功能集成,例如从现有连接创建新连接以检查数据库是否存在。 在通常情况下,我们观察到标准 DbContext 构造函数仅对可安全克隆用于 EF 工作的关闭数据库连接有效。 弹性数据库的设计原则是仅管理打开的连接。 有人可能认为,在交付给 EF DbContext 之前关闭由客户端库代理的连接可能解决此问题。 但是,通过关闭连接并依靠 EF 重新打开它,将放弃由该库执行的验证和一致性检查。 但是,EF 中的迁移功能使用这些连接以对应用程序透明的方式管理基础数据库架构。 理想情况下,将在相同的应用程序中保留和合并所有这些来自弹性数据库客户端库和 EF 的功能。 以下部分更详细地讨论这些属性和要求。
要求
在使用弹性数据库客户端库和 Entity Framework API 时,会希望保留以下属性:
- 向外缩放:需要根据应用程序的容量需求,在分片应用程序的数据层中添加或删除数据库。 这意味着可以控制数据库的创建、删除和管理,并使用弹性分片映射管理器 API 来管理数据库和 shardlet 的映射。
- 一致性:应用程序利用分片,并且使用客户端库的数据依赖型路由功能。 为了避免损坏或错误的查询结果,连接由分片映射管理器进行管理。 此操作还会保留验证和一致性。
- Code First:保留 EF 的 Code First 范例的便利性。 在“代码优先”中,应用程序中的类透明映射到基础数据库结构。 应用程序代码与 DbSets 交互,隐藏了基础数据库处理中涉及的大部分细节。
- 架构:实体框架通过迁移处理初始数据库架构创建和后续架构演变。 通过保留这些功能,随着数据的演变调整应用很容易。
以下指南指导如何满足使用弹性数据库工具的“代码优先”应用程序的这些要求。
使用 EF DbContext 的数据依赖型路由
使用 Entity Framework 的数据库连接通常通过子 DbContext类进行管理。 通过派生自 DbContext 创建这些子类。 在这里,您可以定义 DbSets,用来实现应用程序中由数据库支持的 CLR 对象集合。 在数据依赖型路由的上下文中,可以识别多个有用的属性,这些属性不一定适用于其他 EF Code First 应用程序场景。
- 数据库已经存在,并且已在弹性数据库分片映射中注册。
- 该应用程序的架构已部署到数据库(如下说明)。
- 数据依赖的路由连接到数据库是由分片映射管理的。
若要将 DbContexts 与数据相关的路由集成以实现横向扩展,请执行以下步骤:
- 通过分片映射管理器的弹性数据库客户端接口创建物理数据库连接。
- 使用
DbContext子类包装该连接 - 将该连接向下传递到
DbContext基类以确保 EF 一侧上的所有处理也全部发生。
以下代码示例演示了此方法。 (附带的 Visual Studio 项目中也提供此代码)
public class ElasticScaleContext<T> : DbContext
{
public DbSet<Blog> Blogs { get; set; }
...
// C'tor for data-dependent routing. This call opens a validated connection
// routed to the proper shard by the shard map manager.
// Note that the base class c'tor call fails for an open connection
// if migrations need to be done and SQL credentials are used. This is the reason for the
// separation of c'tors into the data-dependent routing case (this c'tor) and the internal c'tor for new shards.
public ElasticScaleContext(ShardMap shardMap, T shardingKey, string connectionStr)
: base(CreateDDRConnection(shardMap, shardingKey, connectionStr),
true /* contextOwnsConnection */)
{
}
// Only static methods are allowed in calls into base class c'tors.
private static DbConnection CreateDDRConnection(
ShardMap shardMap,
T shardingKey,
string connectionStr)
{
// No initialization
Database.SetInitializer<ElasticScaleContext<T>>(null);
// Ask shard map to broker a validated connection for the given key
SqlConnection conn = shardMap.OpenConnectionForKey<T>
(shardingKey, connectionStr, ConnectionOptions.Validate);
return conn;
}
要点
新的构造函数替换 DbContext 子类中的默认构造函数
新的构造函数接受用于数据依赖型路由的弹性数据库客户端库所需的参数。
- 用于访问数据依赖路由接口的分片映射,
- 用于标识分片单元的分片键,
- 带有到该分片的依赖于数据的路由连接的凭据的连接字符串。
对基类构造函数的调用需要绕行到静态方法,以执行依赖于数据的路由所需的所有步骤。
- 它使用分片映射上的弹性数据库客户端接口的 OpenConnectionForKey 调用来建立开放连接。
- 分片映射负责创建与特定分片键的 shardlet 所在分片之间的开放连接。
- 此开放连接将传递回 DbContext 的基类构造函数以指示此连接由 EF 使用,而不是让 EF 自动创建新连接。 这样,该连接已由弹性数据库客户端 API 标记,以便它可以保证分片映射管理操作下的一致性。
为 DbContext 子类使用新的构造函数而不是代码中的默认构造函数。 以下是示例:
// Create and save a new blog.
Console.Write("Enter a name for a new blog: ");
var name = Console.ReadLine();
using (var db = new ElasticScaleContext<int>(
sharding.ShardMap,
tenantId1,
connStrBldr.ConnectionString))
{
var blog = new Blog { Name = name };
db.Blogs.Add(blog);
db.SaveChanges();
// Display all Blogs for tenant 1
var query = from b in db.Blogs
orderby b.Name
select b;
...
}
新的构造函数会打开到该分片的连接,该分片保存由 tenantid1 的值标识的 shardlet 的数据。
using 块中的代码保持不变以访问 DbSet,进而获取有关对 tenantid1 的分片使用 EF 的博客。 这改变了 using 块中的代码的语义,因此所有数据库操作的范围现在设置为保留 tenantid1 的单个分片。 例如,针对博客 DbSet 的 LINQ 查询只会返回存储在当前分片上的博客,但不会返回存储在其他分片上的博客。
暂时性故障处理
Microsoft 模式和实践团队已发布暂时性故障处理应用程序块。 该库通过弹性缩放客户端库与 EF 结合使用。 但是,确保任何暂时性异常返回到可以确保新构造函数在暂时性故障后被使用的位置,以便任何新连接尝试使用微调过的构造函数来进行。 否则,不保证能连接到正确的分片,并且当分片映射发生更改时,也无法确保连接的稳定性。
以下代码示例演示了如何在新的 DbContext 子类构造函数周围使用 SQL 重试策略:
SqlDatabaseUtils.SqlRetryPolicy.ExecuteAction(() =>
{
using (var db = new ElasticScaleContext<int>(
sharding.ShardMap,
tenantId1,
connStrBldr.ConnectionString))
{
var blog = new Blog { Name = name };
db.Blogs.Add(blog);
db.SaveChanges();
...
}
});
SqlDatabaseUtils.SqlRetryPolicy 示例中的代码定义为 SqlDatabaseTransientErrorDetectionStrategy 重试计数为 10 秒,重试间隔为 5 秒。 此方法类似于 EF 和用户启动事务的指南(请参阅重试执行策略的限制(从 EF6 开始)。 这两种情况都要求应用程序控制暂时性异常返回的范围:要么重新打开事务,要么(如下所示)从适当的构造函数重新创建上下文,该构造函数使用弹性数据库客户端库。
需要控制其中暂时性异常返回范围还使该列不能使用 EF 随附的内置 SqlAzureExecutionStrategy。
SqlAzureExecutionStrategy 将重新打开连接,但不使用该 OpenConnectionForKey 连接,因此绕过在调用过程中 OpenConnectionForKey 执行的所有验证。 该代码示例使用的是 EF 也已随附的内置 DefaultExecutionStrategy。 与SqlAzureExecutionStrategy不同,它可以正确地与暂时性故障处理中的重试策略结合使用。 执行策略在类中 ElasticScaleDbConfiguration 设置。 我们决定不使用 DefaultSqlExecutionStrategy ,因为它建议在发生暂时性异常时使用 SqlAzureExecutionStrategy ,这将导致所讨论的错误行为。 有关不同重试策略和 EF 的详细信息,请参阅 EF 中的连接弹性。
构造函数重写
上方的代码示例演示应用程序所需的默认构造函数重写,以将数据依赖型路由与 Entity Framework 一起使用。 下表将此方法一般化到其他构造函数。
| 当前构造函数 | 数据专用重写的构造函数 | 基构造函数 | 注释 |
|---|---|---|---|
MyContext() |
ElasticScaleContext(ShardMap, TKey) |
DbContext(DbConnection, bool) |
连接需要是分片映射和数据相关路由键的一个函数。 需要通过 EF 绕过自动连接创建,并改用分片映射代理该连接。 |
MyContext(string) |
ElasticScaleContext(ShardMap, TKey) |
DbContext(DbConnection, bool) |
该连接是由分片映射以及依赖于数据的路由键决定的函数。 在它们通过分片映射绕过验证时,固定数据库名称或连接字符串将不起作用。 |
MyContext(DbCompiledModel) |
ElasticScaleContext(ShardMap, TKey, DbCompiledModel) |
DbContext(DbConnection, DbCompiledModel, bool) |
为给定的分片映射和分片键创建连接时,会使用提供的模型。 编译后的模型会传递到基构造函数。 |
MyContext(DbConnection, bool) |
ElasticScaleContext(ShardMap, TKey, bool) |
DbContext(DbConnection, bool) |
该连接需要根据分片映射和键来推断。 无法提供其作为输入(除非该输入已经在使用分片映射和密钥)。 传递布尔值。 |
MyContext(string, DbCompiledModel) |
ElasticScaleContext(ShardMap, TKey, DbCompiledModel) |
DbContext(DbConnection, DbCompiledModel, bool) |
该连接需要从分片映射和密钥中推断。 不能将其作为输入提供(除非该输入使用了分片映射和键)。 编译后的模型被传递。 |
MyContext(ObjectContext, bool) |
ElasticScaleContext(ShardMap, TKey, ObjectContext, bool) |
DbContext(ObjectContext, bool) |
新的构造函数需要确保 ObjectContext 中作为输入传递的任何连接重新路由到由 Elastic Scale 管理的连接。 ObjectContext 的更详细讨论不在本文档的范围内。 |
MyContext(DbConnection, DbCompiledModel, bool) |
ElasticScaleContext(ShardMap, TKey, DbCompiledModel, bool) |
DbContext(DbConnection, DbCompiledModel, bool); |
连接需要通过分片映射和密钥来推断。 无法将连接作为输入提供(除非该输入已经在使用分片映射和键)。 模型和布尔值将传递到基类构造函数。 |
利用 EF 迁移部署分片模式架构
自动架构管理是实体框架提供的一项便利。 在使用弹性数据库工具的应用程序的上下文中,会希望保留此功能以在数据库添加到分片应用程序时,将架构自动设置为新创建的分片。 主要用例是增加使用 EF 的分片应用程序的数据层的容量。 依靠 EF 的架构管理功能可减少在 EF 上构建的分片应用程序的数据库管理工作。
通过 EF 迁移进行模式部署在未打开的连接上效果最好。 这与依靠由弹性数据库客户端 API 提供的打开连接的数据依赖型路由方案相反。 另一个区别是一致性要求:虽然保证所有数据相关路由连接的一致性以防止并发分片图操作是理想的,但这对于初始架构部署到尚未在分片图中注册且尚未分配以保存分片单元的新数据库来说并不是一个问题。 因此,针对这种情况,可以依靠常规的数据库连接,而不是依赖数据路由。
这种方法导致了通过 EF 迁移进行的架构部署与将新数据库注册为应用程序分片映射中的一个分片紧密耦合。 这依靠以下先决条件:
- 该数据库已创建。
- 该数据库为空 - 它未保存任何用户架构和用户数据。
- 该数据库尚不能通过弹性数据库客户端 API 进行数据依赖路由访问。
具备这些先决条件后,可以创建一个常规的未打开的 SqlConnection,以便为架构部署启动 EF 迁移。 以下代码示例演示了此方法。
// Enter a new shard - i.e. an empty database - to the shard map, allocate a first tenant to it
// and kick off EF initialization of the database to deploy schema
public void RegisterNewShard(string server, string database, string connStr, int key)
{
Shard shard = this.ShardMap.CreateShard(new ShardLocation(server, database));
SqlConnectionStringBuilder connStrBldr = new SqlConnectionStringBuilder(connStr);
connStrBldr.DataSource = server;
connStrBldr.InitialCatalog = database;
// Go into a DbContext to trigger migrations and schema deployment for the new shard.
// This requires an un-opened connection.
using (var db = new ElasticScaleContext<int>(connStrBldr.ConnectionString))
{
// Run a query to engage EF migrations
(from b in db.Blogs
select b).Count();
}
// Register the mapping of the tenant to the shard in the shard map.
// After this step, data-dependent routing on the shard map can be used
this.ShardMap.CreatePointMapping(key, shard);
}
此示例演示方法 RegisterNewShard,此方法注册分片映射中的分片,通过 EF 迁移部署架构并将分片键的映射存储到该分片。 它依赖于将 SQL 连接字符串作为输入的 DbContext 子类(ElasticScaleContext 在示例中)的构造函数。 此构造函数的代码很简单,如以下示例所示:
// C'tor to deploy schema and migrations to a new shard
protected internal ElasticScaleContext(string connectionString)
: base(SetInitializerForConnection(connectionString))
{
}
// Only static methods are allowed in calls into base class c'tors
private static string SetInitializerForConnection(string connectionString)
{
// You want existence checks so that the schema can get deployed
Database.SetInitializer<ElasticScaleContext<T>>(
new CreateDatabaseIfNotExists<ElasticScaleContext<T>>());
return connectionString;
}
有人可能使用了从基类继承的构造函数版本。 但是该代码需要确保在连接时使用 EF 的默认初始化程序。 因此在调用带有连接字符串的基类构造函数前,需短暂绕行到静态方法。 分片注册应在不同的应用域或进程中运行,以确保 EF 初始化器的设置不会产生冲突。
限制
本文档中概述的方法存在一些限制:
- 使用
LocalDb的 EF 应用程序在使用弹性数据库客户端库之前,首先需要迁移到常规 SQL Server 数据库。 使用弹性缩放通过分片扩大应用程序不适用于LocalDb。 开发仍可使用LocalDb。 - 任何意味着数据库架构更改的应用程序更改需要在所有分片上通过 EF 迁移。 本文档的示例代码不演示如何执行此操作。 考虑使用带有 ConnectionString 参数的 Update-Database 遍历所有分片,或使用带有 -Script 选项的 Update-Database 提取待迁移的 T-SQL 脚本,然后将其应用到分片。
- 在给定一个请求的情况下,假设它所有的数据库处理都存储在由该请求提供的分片键识别的单个分片内。 但是,此假设并不总是有效。 例如,当无法提供分片键时。 为了解决此问题,客户端库提供了
MultiShardQuery实现连接抽象的类,用于查询多个分片。 学习结合使用MultiShardQuery和 EF 不在本文档的范围内。
结论
通过本文档中所述的步骤,EF 应用程序可以通过重构 EF 应用程序中使用的子类的 DbContext 构造函数,使用弹性数据库客户端库的功能进行数据依赖型路由。 这会将所需的更改限制在 DbContext 已存在类的地方。 此外,EF 应用程序可以通过将调用必要的 EF 迁移的步骤与新分片的注册和分片映射中的映射结合,继续从自动架构部署中受益。
相关内容
尚未使用弹性数据库工具? 请查看入门指南。