弹性数据库客户端库与实体框架Elastic Database client library with Entity Framework

此文档介绍与弹性数据库工具集成所需的实体框架应用程序中的更改。This document shows the changes in an Entity Framework application that are needed to integrate with the Elastic Database tools. 重点是使用 Entity Framework Code First 方法撰写分片映射管理数据相关路由The focus is on composing shard map management and data-dependent routing with the Entity Framework Code First approach. EF 的 Code First – 新数据库教程在本文档中充当运行示例。The Code First - New Database tutorial for EF serves as the running example throughout this document. 本文档附带的示例代码是 Visual Studio 代码示例中弹性数据库工具示例的一部分。The sample code accompanying this document is part of elastic database tools' set of samples in the Visual Studio Code Samples.

下载和运行示例代码Downloading and Running the Sample Code

若要下载本文的代码:To download the code for this article:

  • 需要 Visual Studio 2012 或更高版本。Visual Studio 2012 or later is required.
  • 下载 Elastic DB Tools for Azure SQL - Entity Framework Integration sample(Azure SQL 弹性数据库工具 - 实体框架集成示例)。Download the Elastic DB Tools for Azure SQL - Entity Framework Integration sample. 将示例解压缩到所选位置。Unzip the sample to a location of your choosing.
  • 启动 Visual Studio。Start Visual Studio.
  • 在 Visual Studio 中,选择“文件”->“打开项目/解决方案”。In Visual Studio, select File -> Open Project/Solution.
  • 在“打开项目” 对话框中,导航到已下载的示例,并选择 EntityFrameworkCodeFirst.sln 打开该示例。In the Open Project dialog, navigate to the sample you downloaded and select EntityFrameworkCodeFirst.sln to open the sample.

要运行该示例,需要在 Azure SQL 数据库中创建三个空数据库:To run the sample, you need to create three empty databases in Azure SQL Database:

  • 分片映射管理器数据库Shard Map Manager database
  • 分片 1 数据库Shard 1 database
  • 分片 2 数据库Shard 2 database

创建这些数据库后,使用 Azure SQL DB 服务器名称、数据库名称以及连接到数据库的凭据填充 Program.cs 中的占位符。 Once you have created these databases, fill in the place holders in Program.cs with your Azure SQL DB server name, the database names, and your credentials to connect to the databases. 在 Visual Studio 中生成解决方案。Build the solution in Visual Studio. 在生成过程中,Visual Studio 会下载弹性数据库客户端库、Entity Framework 和暂时性故障处理所需的 NuGet 包。Visual Studio downloads the required NuGet packages for the elastic database client library, Entity Framework, and Transient Fault handling as part of the build process. 确保已为解决方案启用还原 NuGet 包。Make sure that restoring NuGet packages is enabled for your solution. 可以通过右键单击 Visual Studio 解决方案资源管理器中的解决方案文件启用此设置。You can enable this setting by right-clicking on the solution file in the Visual Studio Solution Explorer.

实体框架工作流Entity Framework workflows

实体框架开发人员依靠以下四个工作流之一构建应用程序并确保应用程序对象的持久性:Entity Framework developers rely on one of the following four workflows to build applications and to ensure persistence for application objects:

  • Code First(新数据库) :EF 开发人员在应用程序代码中创建模型,然后 EF 从中生成数据库。Code First (New Database): The EF developer creates the model in the application code and then EF generates the database from it.
  • Code First(现有数据库) :开发人员让 EF 从现有数据库生成模型的应用程序代码。Code First (Existing Database): The developer lets EF generate the application code for the model from an existing database.
  • Model First:开发人员在 EF 设计器中创建模型,EF 从该模型创建数据库。Model First: The developer creates the model in the EF designer and then EF creates the database from the model.
  • Database First:开发人员使用 EF 工具从现有数据库推断模型。Database First: The developer uses EF tooling to infer the model from an existing database.

所有这些方法依靠 DbContext 类为应用程序透明管理数据库连接和数据库架构。All these approaches rely on the DbContext class to transparently manage database connections and database schema for an application. DbContext 基类上的不同构造函数允许对连接创建、数据库引导和架构创建进行不同级别的控制。Different constructors on the DbContext base class allow for different levels of control over connection creation, database bootstrapping, and schema creation. 挑战主要产生于这一事实:由 EF 提供的数据库连接管理与弹性数据库客户端库提供的数据依赖型路由接口的连接管理功能交叉。Challenges arise primarily from the fact that the database connection management provided by EF intersects with the connection management capabilities of the data-dependent routing interfaces provided by the elastic database client library.

弹性数据库工具假设条件Elastic database tools assumptions

有关术语定义,请参阅弹性数据库工具词汇表For term definitions, see Elastic Database tools glossary.

借助弹性数据库客户端库,可以定义称为 shardlet 的应用程序数据分区。With elastic database client library, you define partitions of your application data called shardlets. Shardlet 由分片键标识,并且映射到特定数据库。Shardlets are identified by a sharding key and are mapped to specific databases. 应用程序可以具有任意所需数量的数据库,并根据当前业务需求分发 shardlet 以提供足够的容量或性能。An application may have as many databases as needed and distribute the shardlets to provide enough capacity or performance given current business requirements. 分片键值到数据库的映射由弹性数据库客户端 API 提供的分片映射存储。The mapping of sharding key values to the databases is stored by a shard map provided by the elastic database client APIs. 此功能称为“分片映射管理”或简称为 SMM。 This capability is called Shard Map Management, or SMM for short. 分片映射还为带有分片键的请求充当数据库连接的代理。The shard map also serves as the broker of database connections for requests that carry a sharding key. 此功能称为“数据依赖型路由”。 This capability is known as data-dependent routing.

分片映射管理器防止用户在 shardlet 数据中出现不一致视图,当发生并发 shardlet 管理操作时(例如将数据从一个分片重新分配到另一个分片)可能发生此情况。The shard map manager protects users from inconsistent views into shardlet data that can occur when concurrent shardlet management operations (such as relocating data from one shard to another) are happening. 为此,客户端库管理的分片映射会代理应用程序的数据库连接。To do so, the shard maps managed by the client library broker the database connections for an application. 当分片管理操作可能影响为其创建数据库连接的 shardlet 时,此操作允许分片映射功能自动终止该连接。This allows the shard map functionality to automatically kill a database connection when shard management operations could impact the shardlet that the connection has been created for. 此方法需要与 EF 的一些功能集成,例如从现有连接创建新连接以检查数据库是否存在。This approach needs to integrate with some of EF's functionality, such as creating new connections from an existing one to check for database existence. 在通常情况下,我们观察到标准 DbContext 构造函数仅对可安全克隆用于 EF 工作的关闭数据库连接有效。In general, our observation has been that the standard DbContext constructors only work reliably for closed database connections that can safely be cloned for EF work. 弹性数据库的设计原则是仅代理打开的连接。The design principle of elastic database instead is to only broker opened connections. 有人可能认为,在交付给 EF DbContext 之前关闭由客户端库代理的连接可能解决此问题。One might think that closing a connection brokered by the client library before handing it over to the EF DbContext may solve this issue. 但是,通过关闭连接并依靠 EF 重新打开它,将放弃由该库执行的验证和一致性检查。However, by closing the connection and relying on EF to reopen it, one foregoes the validation and consistency checks performed by the library. 但是,EF 中的迁移功能使用这些连接以对应用程序透明的方式管理基础数据库架构。The migrations functionality in EF, however, uses these connections to manage the underlying database schema in a way that is transparent to the application. 理想情况下,将在相同的应用程序中保留和合并所有这些来自弹性数据库客户端库和 EF 的功能。Ideally, you will retain and combine all these capabilities from both the elastic database client library and EF in the same application. 以下部分更详细地讨论这些属性和要求。The following section discusses these properties and requirements in more detail.

要求Requirements

在使用弹性数据库客户端库和 Entity Framework API 时,会希望保留以下属性:When working with both the elastic database client library and Entity Framework APIs, you want to retain the following properties:

  • 向外缩放:需要根据应用程序的容量需求,在分片应用程序的数据层中添加或删除数据库。Scale-out: To add or remove databases from the data tier of the sharded application as necessary for the capacity demands of the application. 这意味着可以控制数据库的创建和删除,以及使用弹性数据库分片映射管理器 API 管理数据库和 shardlet 的映射。This means control over the creation and deletion of databases and using the elastic database shard map manager APIs to manage databases, and mappings of shardlets.
  • 一致性:应用程序利用分片,并且使用客户端库的数据依赖型路由功能。Consistency: The application employs sharding, and uses the data-dependent routing capabilities of the client library. 若要避免损坏或错误的查询结果,连接通过分片映射管理器进行代理。To avoid corruption or wrong query results, connections are brokered through the shard map manager. 此操作还会保留验证和一致性。This also retains validation and consistency.
  • Code First:保留 EF 的 Code First 范例的便利性。Code First: To retain the convenience of EF's code first paradigm. 在“代码优先”中,应用程序中的类透明映射到基础数据库结构。In Code First, classes in the application are mapped transparently to the underlying database structures. 应用程序代码与 DbSet 交互以为基础数据库处理中涉及的大部分方面提供掩码。The application code interacts with DbSets that mask most aspects involved in the underlying database processing.
  • 架构:实体框架通过迁移处理初始数据库架构创建和后续架构演变。Schema: Entity Framework handles initial database schema creation and subsequent schema evolution through migrations. 通过保留这些功能,随着数据的演变调整应用很容易。By retaining these capabilities, adapting your app is easy as the data evolves.

以下指南指导如何满足使用弹性数据库工具的“代码优先”应用程序的这些要求。The following guidance instructs how to satisfy these requirements for Code First applications using elastic database tools.

使用 EF DbContext 的数据依赖型路由Data-dependent routing using EF DbContext

使用实体框架的数据库连接通常通过 DbContext的子类来管理。Database connections with Entity Framework are typically managed through subclasses of DbContext. 通过从 DbContext派生创建这些子类。Create these subclasses by deriving from DbContext. 这是定义 DbSet 的位置,它可为应用程序实现支持数据库的 CLR 对象的集合。This is where you define your DbSets that implement the database-backed collections of CLR objects for your application. 在数据依赖型路由的上下文中,可以标识多个有用的属性,这些属性不一定会为其他 EF Code First 应用程序方案保存:In the context of data-dependent routing, you can identify several helpful properties that do not necessarily hold for other EF code first application scenarios:

  • 数据库已经存在,并且已在弹性数据库分片映射中注册。The database already exists and has been registered in the elastic database shard map.
  • 该应用程序的架构已部署到数据库(如下说明)。The schema of the application has already been deployed to the database (explained below).
  • 到数据库的数据相关路由连接由分片映射代理。Data-dependent routing connections to the database are brokered by the shard map.

DbContext 与依赖于数据的路由集成以进行扩大:To integrate DbContexts with data-dependent routing for scale-out:

  1. 通过分片映射管理器的弹性数据库客户端接口创建物理数据库连接,Create physical database connections through the elastic database client interfaces of the shard map manager,
  2. 使用 DbContext 子类包装该连接。Wrap the connection with the DbContext subclass
  3. 将该连接向下传递到 DbContext 基类以确保 EF 一侧上的所有处理也全部发生。Pass the connection down into the DbContext base classes to ensure all the processing on the EF side happens as well.

以下代码示例演示了此方法。The following code example illustrates this approach. (附带的 Visual Studio 项目中也提供此代码)(This code is also in the accompanying Visual Studio project)

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;
    }

要点Main points

  • 新的构造函数替换 DbContext 子类中的默认构造函数A new constructor replaces the default constructor in the DbContext subclass

  • 新的构造函数采用数据依赖型路由通过弹性数据库客户端库所需的参数:The new constructor takes the arguments that are required for data-dependent routing through elastic database client library:

    • 用于访问依赖于数据的路由接口的分片映射,the shard map to access the data-dependent routing interfaces,
    • 用于标识 shardlet 的分片键,the sharding key to identify the shardlet,
    • 带有到该分片的依赖于数据的路由连接的凭据的连接字符串。a connection string with the credentials for the data-dependent routing connection to the shard.
  • 对基类构造函数的调用需要绕行到静态方法,以执行依赖于数据的路由所需的所有步骤。The call to the base class constructor takes a detour into a static method that performs all the steps necessary for data-dependent routing.

    • 它使用分片映射上的弹性数据库客户端接口的 OpenConnectionForKey 调用来建立开放连接。It uses the OpenConnectionForKey call of the elastic database client interfaces on the shard map to establish an open connection.
    • 分片映射创建到保存特定分片键的 shardlet 的分片的开放连接。The shard map creates the open connection to the shard that holds the shardlet for the given sharding key.
    • 此开放连接将传递回 DbContext 的基类构造函数以指示此连接由 EF 使用,而不是让 EF 自动创建新连接。This open connection is passed back to the base class constructor of DbContext to indicate that this connection is to be used by EF instead of letting EF create a new connection automatically. 这样,该连接已由弹性数据库客户端 API 标记,以便它可以保证分片映射管理操作下的一致性。This way the connection has been tagged by the elastic database client API so that it can guarantee consistency under shard map management operations.

为 DbContext 子类使用新的构造函数而不是代码中的默认构造函数。Use the new constructor for your DbContext subclass instead of the default constructor in your code. 以下是示例:Here is an example:

// 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 的数据。The new constructor opens the connection to the shard that holds the data for the shardlet identified by the value of tenantid1. using 块中的代码保持不变以访问 DbSet,进而获取有关对 tenantid1 的分片使用 EF 的博客。The code in the using block stays unchanged to access the DbSet for blogs using EF on the shard for tenantid1. 这改变了 using 块中的代码的语义,因此所有数据库操作的范围现在设置为保留 tenantid1 的单个分片。This changes semantics for the code in the using block such that all database operations are now scoped to the one shard where tenantid1 is kept. 例如,博客 DbSet 上的 LINQ 查询将仅返回当前分片上存储的博客,不返回存储在其他分片上的博客。For instance, a LINQ query over the blogs DbSet would only return blogs stored on the current shard, but not the ones stored on other shards.

暂时性故障处理Transient faults handling

Microsoft 模式和实践团队已发布暂时性故障处理应用程序块The Microsoft Patterns & Practices team published the The Transient Fault Handling Application Block. 该库通过弹性缩放客户端库与 EF 结合使用。The library is used with elastic scale client library in combination with EF. 但是,确保任何暂时性异常返回到可以确保新构造函数在暂时性故障后被使用的位置,以便任何新连接尝试使用微调过的构造函数来进行。However, ensure that any transient exception returns to a place where you can ensure that the new constructor is being used after a transient fault so that any new connection attempt is made using the constructors you tweaked. 否则,不保证连接到正确分片,并且无法保证当分片映射发生更改时保持连接。Otherwise, a connection to the correct shard is not guaranteed, and there are no assurances the connection is maintained as changes to the shard map occur.

以下代码示例演示如何围绕新的 DbContext 子类构造函数使用 SQL 重试策略:The following code sample illustrates how a SQL retry policy can be used around the new DbContext subclass constructors:

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 秒。SqlDatabaseUtils.SqlRetryPolicy in the code above is defined as a SqlDatabaseTransientErrorDetectionStrategy with a retry count of 10, and 5 seconds wait time between retries. 此方法类似于 EF 和用户启动事务的指南(请参阅重试执行策略的限制(从 EF6 开始)This approach is similar to the guidance for EF and user-initiated transactions (see Limitations with Retrying Execution Strategies (EF6 onwards). 这两种情况都要求应用程序控制返回暂时性异常的范围:重新打开事务,或者(如下所示)从使用弹性数据库客户端库的适当构造函数重新创建上下文。Both situations require that the application program controls the scope to which the transient exception returns: to either reopen the transaction, or (as shown) recreate the context from the proper constructor that uses the elastic database client library.

需要控制其中暂时性异常返回范围还使该列不能使用 EF 随附的内置 SqlAzureExecutionStrategyThe need to control where transient exceptions take us back in scope also precludes the use of the built-in SqlAzureExecutionStrategy that comes with EF. SqlAzureExecutionStrategy 会重新打开连接,但不会使用 OpenConnectionForKey,从而绕过了调用 OpenConnectionForKey 期间执行的所有验证。SqlAzureExecutionStrategy would reopen a connection but not use OpenConnectionForKey and therefore bypass all the validation that is performed as part of the OpenConnectionForKey call. 该代码示例使用的是 EF 也已随附的内置 DefaultExecutionStrategyInstead, the code sample uses the built-in DefaultExecutionStrategy that also comes with EF. SqlAzureExecutionStrategy相反,它能与暂时性故障处理中的重试策略正常配合工作。As opposed to SqlAzureExecutionStrategy, it works correctly in combination with the retry policy from Transient Fault Handling. 执行策略在 ElasticScaleDbConfiguration 类中设置。The execution policy is set in the ElasticScaleDbConfiguration class. 请注意,我们决定不使用 DefaultSqlExecutionStrategy,因为在发生暂时性异常时,最好使用 SqlAzureExecutionStrategy - 这会导致所述的错误行为。 Note that we decided not to use DefaultSqlExecutionStrategy since it suggests using SqlAzureExecutionStrategy if transient exceptions occur - which would lead to wrong behavior as discussed. 有关不同重试策略和 EF 的详细信息,请参阅 EF 中的连接弹性For more information on the different retry policies and EF, see Connection Resiliency in EF.

构造函数重写Constructor rewrites

上方的代码示例演示应用程序所需的默认构造函数重写,以将数据依赖型路由与 Entity Framework 一起使用。The code examples above illustrate the default constructor re-writes required for your application in order to use data-dependent routing with the Entity Framework. 下表将此方法一般化到其他构造函数。The following table generalizes this approach to other constructors.

当前构造函数Current Constructor 为数据重写构造函数Rewritten Constructor for data 基构造函数Base Constructor 注释Notes
MyContext()MyContext() ElasticScaleContext(ShardMap, TKey)ElasticScaleContext(ShardMap, TKey) DbContext(DbConnection, bool)DbContext(DbConnection, bool) 该连接需要是分片映射和依赖于数据的路由键的一个函数。The connection needs to be a function of the shard map and the data-dependent routing key. 需要通过 EF 绕过自动连接创建,并改用分片映射代理该连接。You need to by-pass automatic connection creation by EF and instead use the shard map to broker the connection.
MyContext(string)MyContext(string) ElasticScaleContext(ShardMap, TKey)ElasticScaleContext(ShardMap, TKey) DbContext(DbConnection, bool)DbContext(DbConnection, bool) 该连接是分片映射和依赖于数据的路由键的一个函数。The connection is a function of the shard map and the data-dependent routing key. 在它们通过分片映射绕过验证时,固定数据库名称或连接字符串将不起作用。A fixed database name or connection string does not work as they by-pass validation by the shard map.
MyContext(DbCompiledModel)MyContext(DbCompiledModel) ElasticScaleContext(ShardMap, TKey, DbCompiledModel)ElasticScaleContext(ShardMap, TKey, DbCompiledModel) DbContext(DbConnection, DbCompiledModel, bool)DbContext(DbConnection, DbCompiledModel, bool) 会为给定分片映射和分片键创建连接,并提供模型。The connection gets created for the given shard map and sharding key with the model provided. 编译后的模型会传递到基构造函数。The compiled model is passed on to the base c'tor.
MyContext(DbConnection, bool)MyContext(DbConnection, bool) ElasticScaleContext(ShardMap, TKey, bool)ElasticScaleContext(ShardMap, TKey, bool) DbContext(DbConnection, bool)DbContext(DbConnection, bool) 该连接需要从分片映射和键推断。The connection needs to be inferred from the shard map and the key. 无法将其作为输入提供(除非该输入已经在使用分片映射和键)。It cannot be provided as an input (unless that input was already using the shard map and the key). 会传递布尔模型。The Boolean is passed on.
MyContext(string, DbCompiledModel)MyContext(string, DbCompiledModel) ElasticScaleContext(ShardMap, TKey, DbCompiledModel)ElasticScaleContext(ShardMap, TKey, DbCompiledModel) DbContext(DbConnection, DbCompiledModel, bool)DbContext(DbConnection, DbCompiledModel, bool) 该连接需要从分片映射和键推断。The connection needs to be inferred from the shard map and the key. 无法将其作为输入提供(除非该输入已在使用分片映射和键)。It cannot be provided as an input (unless that input was using the shard map and the key). 会传递编译后的模型。The compiled model is passed on.
MyContext(ObjectContext, bool)MyContext(ObjectContext, bool) ElasticScaleContext(ShardMap, TKey, ObjectContext, bool)ElasticScaleContext(ShardMap, TKey, ObjectContext, bool) DbContext(ObjectContext, bool)DbContext(ObjectContext, bool) 新的构造函数需要确保 ObjectContext 中作为输入传递的任何连接重新路由到由 Elastic Scale 管理的连接。The new constructor needs to ensure that any connection in the ObjectContext passed as an input is re-routed to a connection managed by Elastic Scale. ObjectContext 的更详细讨论不在本文档的范围内。A detailed discussion of ObjectContexts is beyond the scope of this document.
MyContext(DbConnection, DbCompiledModel, bool)MyContext(DbConnection, DbCompiledModel, bool) ElasticScaleContext(ShardMap, TKey, DbCompiledModel, bool)ElasticScaleContext(ShardMap, TKey, DbCompiledModel, bool) DbContext(DbConnection, DbCompiledModel, bool);DbContext(DbConnection, DbCompiledModel, bool); 该连接需要从分片映射和键推断。The connection needs to be inferred from the shard map and the key. 无法将连接作为输入提供(除非该输入已经在使用分片映射和键)。The connection cannot be provided as an input (unless that input was already using the shard map and the key). 模型和布尔值将传递到基类构造函数。Model and Boolean are passed on to the base class constructor.

通过 EF 迁移分片架构部署Shard schema deployment through EF migrations

自动架构管理是实体框架提供的一项便利。Automatic schema management is a convenience provided by the Entity Framework. 在使用弹性数据库工具的应用程序的上下文中,会希望保留此功能以在数据库添加到分片应用程序时,将架构自动设置为新创建的分片。In the context of applications using elastic database tools, you want to retain this capability to automatically provision the schema to newly created shards when databases are added to the sharded application. 主要用例是增加使用 EF 的分片应用程序的数据层的容量。The primary use case is to increase capacity at the data tier for sharded applications using EF. 依靠 EF 的架构管理功能可减少在 EF 上构建的分片应用程序的数据库管理工作。Relying on EF's capabilities for schema management reduces the database administration effort with a sharded application built on EF.

通过 EF 迁移的架构部署对于 未打开的连接效果最佳。Schema deployment through EF migrations works best on unopened connections. 这与依靠由弹性数据库客户端 API 提供的打开连接的数据依赖型路由方案相反。This is in contrast to the scenario for data-dependent routing that relies on the opened connection provided by the elastic database client API. 另一个区别是一致性要求:尽管确保所有数据相关的路由连接的一致性以防止并发分片映射操作是可取的,但是对于到尚未在分片映射中注册以及尚未分配为保存 shardlet 的新数据库的初始架构部署,这不是问题。Another difference is the consistency requirement: While desirable to ensure consistency for all data-dependent routing connections to protect against concurrent shard map manipulation, it is not a concern with initial schema deployment to a new database that has not yet been registered in the shard map, and not yet been allocated to hold shardlets. 因此,针对此方案,可以依靠常规数据库连接,与数据依赖型路由相反。You can therefore rely on regular database connections for this scenario, as opposed to data-dependent routing.

这会导致一种方法,在此方法中通过 EF 迁移进行的架构部署将与新数据库的注册紧密耦合,充当应用程序的分片映射中的一个分片。This leads to an approach where schema deployment through EF migrations is tightly coupled with the registration of the new database as a shard in the application's shard map. 这依靠以下先决条件:This relies on the following prerequisites:

  • 该数据库已创建。The database has already been created.
  • 该数据库为空 - 它未保存任何用户架构和用户数据。The database is empty - it holds no user schema and no user data.
  • 该数据库无法通过数据相关的路由的弹性数据库客户端 API 访问。The database cannot yet be accessed through the elastic database client APIs for data-dependent routing.

具备这些先决条件后,可以创建一个常规的未打开的 SqlConnection,以便为架构部署启动 EF 迁移。 With these prerequisites in place, you can create a regular un-opened SqlConnection to kick off EF migrations for schema deployment. 以下代码示例演示了此方法。The following code sample illustrates this approach.

// 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 迁移部署架构并将分片键的映射存储到该分片。This sample shows the method RegisterNewShard that registers the shard in the shard map, deploys the schema through EF migrations, and stores a mapping of a sharding key to the shard. 它依靠 DbContext 子类的构造函数(在本示例中为 ElasticScaleContext),此构造函数采用 SQL 连接字符串作为输入。It relies on a constructor of the DbContext subclass (ElasticScaleContext in the sample) that takes a SQL connection string as input. 此构造函数的代码很简单,如以下示例所示:The code of this constructor is straight-forward, as the following example shows:

// 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; 
} 

有人可能使用了从基类继承的构造函数版本。One might have used the version of the constructor inherited from the base class. 但是该代码需要确保在连接时使用 EF 的默认初始化程序。But the code needs to ensure that the default initializer for EF is used when connecting. 因此在调用带有连接字符串的基类构造函数前,需短暂绕行到静态方法。Hence the short detour into the static method before calling into the base class constructor with the connection string. 请注意,分片的注册应该在不同的应用域或进程中运行,以确保 EF 的初始化程序设置不冲突。Note that the registration of shards should run in a different app domain or process to ensure that the initializer settings for EF do not conflict.

限制Limitations

本文档中概述的方法存在一些限制:The approaches outlined in this document entail a couple of limitations:

  • 使用 LocalDb 的 EF 应用程序在使用弹性数据库客户端库之前,需要先迁移到常规 SQL Server 数据库。EF applications that use LocalDb first need to migrate to a regular SQL Server database before using elastic database client library. 使用弹性缩放通过分片扩大应用程序不适用于 LocalDbScaling out an application through sharding with Elastic Scale is not possible with LocalDb. 请注意,开发仍然可以使用 LocalDbNote that development can still use LocalDb.
  • 任何意味着数据库架构更改的应用程序更改需要在所有分片上通过 EF 迁移。Any changes to the application that imply database schema changes need to go through EF migrations on all shards. 本文档的示例代码不演示如何执行此操作。The sample code for this document does not demonstrate how to do this. 考虑使用带有 ConnectionString 参数的 Update-Database 循环访问所有分片,或使用 Update-Database 与 –Script 选项提取用于挂起的迁移的 T-SQL 脚本,并将 T-SQL 脚本应用到分片。Consider using Update-Database with a ConnectionString parameter to iterate over all shards; or extract the T-SQL script for the pending migration using Update-Database with the -Script option and apply the T-SQL script to your shards.
  • 给定一个请求,假设它所有的数据库处理包含在单个分片内,如该请求所提供的分片键所标识的那样。Given a request, it is assumed that all of its database processing is contained within a single shard as identified by the sharding key provided by the request. 但是,此假设并不总是有效。However, this assumption does not always hold true. 例如,当无法提供分片键时。For example, when it is not possible to make a sharding key available. 为解决此问题,客户端库提供 MultiShardQuery 类,此类可实现连接抽象以用于在多个分片上查询。To address this, the client library provides the MultiShardQuery class that implements a connection abstraction for querying over several shards. 学习结合使用 MultiShardQuery 和 EF 不在本文档的范围内。Learning to use the MultiShardQuery in combination with EF is beyond the scope of this document

结论Conclusion

通过本文档中概述的步骤,EF 应用程序可以通过重构 EF 应用程序中使用的 DbContext 子类的构造函数来使用弹性数据库客户端库的数据依赖型路由功能。 Through the steps outlined in this document, EF applications can use the elastic database client library's capability for data-dependent routing by refactoring constructors of the DbContext subclasses used in the EF application. 这将所需的更改限制到 DbContext 类已经存在的位置。This limits the changes required to those places where DbContext classes already exist. 此外,EF 应用程序可以通过将调用必要的 EF 迁移的步骤与新分片的注册和分片映射中的映射结合,继续从自动架构部署中受益。In addition, EF applications can continue to benefit from automatic schema deployment by combining the steps that invoke the necessary EF migrations with the registration of new shards and mappings in the shard map.

其他资源Additional resources

尚未使用弹性数据库工具?Not using elastic database tools yet? 请查看入门指南Check out our Getting Started Guide.