Biblioteca de cliente do Elastic Database com o Entity Framework
Aplica-se a:Banco de Dados SQL do Azure
Este documento mostra as alterações em um aplicativo do Entity Framework que são necessárias para integrar com as ferramentas do Elastic Database. O foco está em compor o gerenciamento de mapa de estilhaço e o roteamento dependente de dados com a abordagem Entity Framework Code First . O tutorial Code First - New Database para EF serve como exemplo em execução em todo este documento. O código de exemplo que acompanha este documento faz parte do conjunto de exemplos das ferramentas de banco de dados elástico nos exemplos de código do Visual Studio.
Nota
Este artigo não é aplicável ao Entity Framework Core (EF Core).
Baixando e executando o código de exemplo
Para baixar o código deste artigo:
- Visual Studio 2012 ou posterior é necessário.
- Baixe o exemplo de integração do Elastic DB Tools for Azure SQL - Entity Framework. Descompacte a amostra para um local de sua escolha.
- Inicie o Visual Studio.
- No Visual Studio, selecione Arquivo -> Abrir Projeto/Solução.
- Na caixa de diálogo Abrir projeto, navegue até o exemplo que você baixou e selecione EntityFrameworkCodeFirst.sln para abrir o exemplo.
Para executar o exemplo, você precisa criar três bancos de dados vazios no Banco de Dados SQL do Azure:
- Base de dados do Shard Map Manager
- Base de dados Shard 1
- Base de dados Shard 2
Depois de criar esses bancos de dados, preencha os espaços reservados em Programa.cs com o nome do servidor, os nomes dos bancos de dados e suas credenciais para se conectar aos bancos de dados. Crie a solução no Visual Studio. O Visual Studio baixa os pacotes NuGet necessários para a biblioteca de cliente de banco de dados elástico, o Entity Framework e o tratamento de falhas transitórias como parte do processo de compilação. Certifique-se de que a restauração de pacotes NuGet esteja habilitada para sua solução. Você pode habilitar essa configuração clicando com o botão direito do mouse no arquivo de solução no Gerenciador de Soluções do Visual Studio.
Fluxos de trabalho do Entity Framework
Os desenvolvedores do Entity Framework dependem de um dos quatro fluxos de trabalho a seguir para criar aplicativos e garantir a persistência de objetos de aplicativo:
- Code First (New Database): O desenvolvedor do EF cria o modelo no código do aplicativo e, em seguida, o EF gera o banco de dados a partir dele.
- Code First (Existing Database): O desenvolvedor permite que o EF gere o código do aplicativo para o modelo a partir de um banco de dados existente.
- Modelo primeiro: o desenvolvedor cria o modelo no designer do EF e, em seguida, o EF cria o banco de dados a partir do modelo.
- Banco de dados primeiro: o desenvolvedor usa ferramentas EF para inferir o modelo de um banco de dados existente.
Todas essas abordagens dependem da classe DbContext para gerenciar de forma transparente conexões de banco de dados e esquema de banco de dados para um aplicativo. Diferentes construtores na classe base DbContext permitem diferentes níveis de controle sobre a criação de conexão, inicialização de banco de dados e criação de esquema. Os desafios surgem principalmente do fato de que o gerenciamento de conexão de banco de dados fornecido pelo EF se cruza com os recursos de gerenciamento de conexão das interfaces de roteamento dependentes de dados fornecidas pela biblioteca cliente de banco de dados elástico.
Pressupostos das ferramentas de banco de dados elástico
Para obter definições de termos, consulte Glossário de ferramentas do Elastic Database.
Com a biblioteca cliente de banco de dados elástico, você define partições dos dados do aplicativo chamadas shardlets. Os shardlets são identificados por uma chave de fragmentação e mapeados para bancos de dados específicos. Um aplicativo pode ter quantos bancos de dados forem necessários e distribuir os shardlets para fornecer capacidade ou desempenho suficientes, de acordo com os requisitos de negócios atuais. O mapeamento de valores de chave de fragmentação para os bancos de dados é armazenado por um mapa de estilhaços fornecido pelas APIs de cliente de banco de dados elástico. Esse recurso é chamado de Gerenciamento de Mapa de Fragmentos, ou SMM, para abreviar. O mapa de estilhaços também serve como o agente de conexões de banco de dados para solicitações que carregam uma chave de fragmentação. Esse recurso é conhecido como roteamento dependente de dados.
O gerenciador de mapas de fragmentos protege os usuários contra visualizações inconsistentes em dados de shardlet que podem ocorrer quando operações simultâneas de gerenciamento de shardlet (como realocar dados de um fragmento para outro) estão acontecendo. Para fazer isso, os mapas de estilhaços gerenciados pela biblioteca do cliente intermediam as conexões de banco de dados para um aplicativo. Isso permite que a funcionalidade de mapa de estilhaços mate automaticamente uma conexão de banco de dados quando as operações de gerenciamento de estilhaços podem afetar o shardlet para o qual a conexão foi criada. Essa abordagem precisa se integrar com algumas das funcionalidades do EF, como a criação de novas conexões a partir de uma existente para verificar a existência do banco de dados. Em geral, nossa observação tem sido que os construtores DbContext padrão só funcionam de forma confiável para conexões de banco de dados fechadas que podem ser clonadas com segurança para o trabalho do EF. Em vez disso, o princípio de design do banco de dados elástico é apenas intermediar conexões abertas. Pode-se pensar que fechar uma conexão intermediada pela biblioteca do cliente antes de entregá-la ao EF DbContext pode resolver esse problema. No entanto, fechando a conexão e confiando no EF para reabri-la, renuncia-se às verificações de validação e consistência realizadas pela biblioteca. A funcionalidade de migrações no EF, no entanto, usa essas conexões para gerenciar o esquema de banco de dados subjacente de forma transparente para o aplicativo. Idealmente, você manterá e combinará todos esses recursos da biblioteca cliente de banco de dados elástico e do EF no mesmo aplicativo. A seção a seguir discute essas propriedades e requisitos com mais detalhes.
Requisitos
Ao trabalhar com a biblioteca cliente de banco de dados elástico e APIs do Entity Framework, você deseja manter as seguintes propriedades:
- Expansão: para adicionar ou remover bancos de dados da camada de dados do aplicativo fragmentado, conforme necessário para as demandas de capacidade do aplicativo. Isso significa controle sobre a criação e exclusão de bancos de dados e o uso das APIs do gerenciador de mapas de estilhaços de banco de dados elástico para gerenciar bancos de dados e mapeamentos de shardlets.
- Consistência: O aplicativo emprega fragmentação e usa os recursos de roteamento dependentes de dados da biblioteca do cliente. Para evitar corrupção ou resultados de consulta errados, as conexões são intermediadas através do gerenciador de mapas de estilhaços. Isso também mantém a validação e a consistência.
- Code First: Para manter a conveniência do paradigma code first da EF. No Code First, as classes no aplicativo são mapeadas de forma transparente para as estruturas de banco de dados subjacentes. O código do aplicativo interage com DbSets que mascaram a maioria dos aspetos envolvidos no processamento do banco de dados subjacente.
- Esquema: o Entity Framework lida com a criação inicial do esquema de banco de dados e a subsequente evolução do esquema por meio de migrações. Ao manter esses recursos, adaptar seu aplicativo é fácil à medida que os dados evoluem.
As diretrizes a seguir instruem como satisfazer esses requisitos para aplicativos Code First usando ferramentas de banco de dados elástico.
Roteamento dependente de dados usando EF DbContext
As conexões de banco de dados com o Entity Framework são normalmente gerenciadas por meio de subclasses de DbContext. Crie essas subclasses derivando de DbContext. É aqui que você define seus DbSets que implementam as coleções de objetos CLR com suporte de banco de dados para seu aplicativo. No contexto do roteamento dependente de dados, você pode identificar várias propriedades úteis que não são necessariamente válidas para outros cenários de aplicativo EF code first:
- O banco de dados já existe e foi registrado no mapa de estilhaços do banco de dados elástico.
- O esquema do aplicativo já foi implantado no banco de dados (explicado abaixo).
- As conexões de roteamento dependentes de dados para o banco de dados são intermediadas pelo mapa de estilhaços.
Para integrar DbContexts com roteamento dependente de dados para expansão:
- Crie conexões físicas de banco de dados por meio das interfaces de cliente de banco de dados elástico do gerenciador de mapas de estilhaços.
- Encapsular a conexão com a subclasse DbContext
- Passe a conexão para as classes base DbContext para garantir que todo o processamento no lado do EF também aconteça.
O exemplo de código a seguir ilustra essa abordagem. (Este código também está no projeto Visual Studio que acompanha)
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;
}
Pontos principais
Um novo construtor substitui o construtor padrão na subclasse DbContext
O novo construtor usa os argumentos necessários para o roteamento dependente de dados por meio da biblioteca cliente de banco de dados elástico:
- o mapa de estilhaços para acessar as interfaces de roteamento dependentes de dados,
- a chave de fragmentação para identificar o fragmento,
- Uma cadeia de conexão com as credenciais para a conexão de roteamento dependente de dados para o fragmento.
A chamada para o construtor de classe base faz um desvio em um método estático que executa todas as etapas necessárias para roteamento dependente de dados.
- Ele usa a chamada OpenConnectionForKey das interfaces do cliente de banco de dados elástico no mapa de estilhaços para estabelecer uma conexão aberta.
- O mapa de estilhaços cria a conexão aberta com o fragmento que contém o fragmento para a chave de fragmentação dada.
- Essa conexão aberta é passada de volta para o construtor de classe base de DbContext para indicar que essa conexão deve ser usada pelo EF em vez de permitir que o EF crie uma nova conexão automaticamente. Dessa forma, a conexão foi marcada pela API do cliente de banco de dados elástico para que ela possa garantir consistência sob operações de gerenciamento de mapa de estilhaço.
Use o novo construtor para sua subclasse DbContext em vez do construtor padrão em seu código. Segue-se um exemplo:
// 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;
…
}
O novo construtor abre a conexão com o fragmento que contém os dados para o shardlet identificado pelo valor de tenantid1. O código no bloco using permanece inalterado para acessar o DbSet para blogs que usam EF no fragmento para tenantid1. Isso altera a semântica do código no bloco de uso, de modo que todas as operações do banco de dados agora têm o escopo de um fragmento onde tenantid1 é mantido. Por exemplo, uma consulta LINQ sobre os blogs DbSet só retornaria blogs armazenados no fragmento atual, mas não os armazenados em outros fragmentos.
Tratamento de falhas transitórias
A equipe de Padrões e Práticas da Microsoft publicou o Bloco de Aplicativo de Tratamento de Falhas Transitórias. A biblioteca é usada com a biblioteca de cliente de escala elástica em combinação com o EF. No entanto, certifique-se de que qualquer exceção transitória retorne a um local onde você possa garantir que o novo construtor está sendo usado após uma falha transitória para que qualquer nova tentativa de conexão seja feita usando os construtores que você ajustou. Caso contrário, uma conexão com o estilhaço correto não é garantida e não há garantias de que a conexão seja mantida à medida que ocorrem alterações no mapa de estilhaços.
O exemplo de código a seguir ilustra como uma política de repetição SQL pode ser usada em torno dos novos construtores de subclasse DbContext :
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 no código acima é definido como um SqlDatabaseTransientErrorDetectionStrategy com uma contagem de tentativas de 10 e 5 segundos de tempo de espera entre as tentativas. Essa abordagem é semelhante à orientação para transações EF e iniciadas pelo usuário (consulte Limitações com estratégias de execução repetidas (EF6 em diante). Ambas as situações exigem que o programa de aplicativo controle o escopo ao qual a exceção transitória retorna: para reabrir a transação ou (conforme mostrado) recriar o contexto a partir do construtor adequado que usa a biblioteca cliente de banco de dados elástico.
A necessidade de controlar onde exceções transitórias nos levam de volta ao escopo também impede o uso do SqlAzureExecutionStrategy interno que vem com o EF. SqlAzureExecutionStrategy reabriria uma conexão, mas não usaria OpenConnectionForKey e, portanto, ignoraria toda a validação executada como parte da chamada OpenConnectionForKey. Em vez disso, o exemplo de código usa o DefaultExecutionStrategy interno que também vem com o EF. Ao contrário de SqlAzureExecutionStrategy, ele funciona corretamente em combinação com a política de repetição do Tratamento de Falhas Transitórias. A política de execução é definida na classe ElasticScaleDbConfiguration . Observe que decidimos não usar DefaultSqlExecutionStrategy pois ele sugere o uso de SqlAzureExecutionStrategy se ocorrerem exceções transitórias - o que levaria a um comportamento errado, conforme discutido. Para obter mais informações sobre as diferentes políticas de repetição e EF, consulte Resiliência de conexão no EF.
Construtor reescreve
Os exemplos de código acima ilustram as regravações do construtor padrão necessárias para seu aplicativo para usar o roteamento dependente de dados com o Entity Framework. A tabela a seguir generaliza essa abordagem para outros construtores.
Construtor atual | Construtor reescrito para dados | Base Construtor | Notas |
---|---|---|---|
MeuContexto() | ElasticScaleContext(ShardMap, TKey) | DbContext(DbConnection, bool) | A conexão precisa ser uma função do mapa de estilhaços e da chave de roteamento dependente de dados. Você precisa ignorar a criação automática de conexão pelo EF e, em vez disso, usar o mapa de estilhaços para intermediar a conexão. |
MyContext(string) | ElasticScaleContext(ShardMap, TKey) | DbContext(DbConnection, bool) | A conexão é uma função do mapa de estilhaços e da chave de roteamento dependente de dados. Um nome de banco de dados fixo ou uma cadeia de conexão não funciona, pois ignoram a validação pelo mapa de estilhaços. |
MyContext(DbCompiledModel) | ElasticScaleContext(ShardMap, TKey, DbCompiledModel) | DbContext(DbConnection, DbCompiledModel, bool) | A conexão é criada para o mapa de estilhaços fornecido e a chave de fragmentação com o modelo fornecido. O modelo compilado é passado para o construtor base. |
MyContext(DbConnection, bool) | ElasticScaleContext(ShardMap, TKey, bool) | DbContext(DbConnection, bool) | A conexão precisa ser inferida a partir do mapa de estilhaços e da chave. Ele não pode ser fornecido como uma entrada (a menos que essa entrada já estivesse usando o mapa de estilhaços e a chave). O booleano é transmitido. |
MyContext(string, DbCompiledModel) | ElasticScaleContext(ShardMap, TKey, DbCompiledModel) | DbContext(DbConnection, DbCompiledModel, bool) | A conexão precisa ser inferida a partir do mapa de estilhaços e da chave. Ele não pode ser fornecido como uma entrada (a menos que essa entrada estivesse usando o mapa de estilhaços e a chave). O modelo compilado é transmitido. |
MyContext(ObjectContext, bool) | ElasticScaleContext(ShardMap, TKey, ObjectContext, bool) | DbContext(ObjectContext, bool) | O novo construtor precisa garantir que qualquer conexão no ObjectContext passada como uma entrada seja redirecionada para uma conexão gerenciada pelo Elastic Scale. Uma discussão detalhada de ObjectContexts está além do escopo deste documento. |
MyContext(DbConnection, DbCompiledModel, bool) | ElasticScaleContext(ShardMap, TKey, DbCompiledModel, bool) | DbContext(DbConnection, DbCompiledModel, bool); | A conexão precisa ser inferida a partir do mapa de estilhaços e da chave. A conexão não pode ser fornecida como uma entrada (a menos que essa entrada já estivesse usando o mapa de estilhaços e a chave). Model e Boolean são passados para o construtor de classe base. |
Implantação de esquema de estilhaço por meio de migrações EF
O gerenciamento automático de esquemas é uma conveniência fornecida pelo Entity Framework. No contexto de aplicativos que usam ferramentas de banco de dados elástico, você deseja manter esse recurso para provisionar automaticamente o esquema para fragmentos recém-criados quando os bancos de dados são adicionados ao aplicativo fragmentado. O principal caso de uso é aumentar a capacidade na camada de dados para aplicativos fragmentados usando EF. Confiar nos recursos do EF para gerenciamento de esquema reduz o esforço de administração do banco de dados com um aplicativo fragmentado criado no EF.
A implantação de esquema por meio de migrações EF funciona melhor em conexões não abertas. Isso contrasta com o cenário de roteamento dependente de dados que depende da conexão aberta fornecida pela API do cliente de banco de dados elástico. Outra diferença é o requisito de consistência: embora desejável para garantir a consistência de todas as conexões de roteamento dependentes de dados para proteger contra manipulação simultânea de mapas de estilhaços, não é uma preocupação com a implantação inicial do esquema em um novo banco de dados que ainda não foi registrado no mapa de estilhaços e ainda não foi alocado para armazenar shardlets. Portanto, você pode confiar em conexões de banco de dados regulares para esse cenário, em vez de roteamento dependente de dados.
Isso leva a uma abordagem em que a implantação do esquema por meio de migrações EF é fortemente acoplada ao registro do novo banco de dados como um fragmento no mapa de estilhaços do aplicativo. Isso se baseia nos seguintes pré-requisitos:
- A base de dados já foi criada.
- O banco de dados está vazio - ele não contém nenhum esquema de usuário e nenhum dado de usuário.
- O banco de dados ainda não pode ser acessado por meio das APIs do cliente de banco de dados elástico para roteamento dependente de dados.
Com esses pré-requisitos em vigor, você pode criar um SqlConnection regular não aberto para iniciar migrações EF para implantação de esquema. O exemplo de código a seguir ilustra essa abordagem.
// 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);
}
Este exemplo mostra o método RegisterNewShard que registra o fragmento no mapa de estilhaços, implanta o esquema por meio de migrações EF e armazena um mapeamento de uma chave de fragmentação para o fragmento. Ele depende de um construtor da subclasse DbContext (ElasticScaleContext no exemplo) que usa uma cadeia de conexão SQL como entrada. O código deste construtor é direto, como mostra o exemplo a seguir:
// 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;
}
Pode-se ter usado a versão do construtor herdada da classe base. Mas o código precisa garantir que o inicializador padrão do EF seja usado ao se conectar. Daí o pequeno desvio para o método estático antes de chamar o construtor de classe base com a cadeia de conexão. Observe que o registro de fragmentos deve ser executado em um domínio ou processo de aplicativo diferente para garantir que as configurações do inicializador para EF não entrem em conflito.
Limitações
As abordagens delineadas neste documento implicam algumas limitações:
- Os aplicativos EF que usam LocalDb primeiro precisam migrar para um banco de dados SQL Server regular antes de usar a biblioteca cliente de banco de dados elástico. Não é possível dimensionar um aplicativo por meio de fragmentação com o Elastic Scale com o LocalDb. Observe que o desenvolvimento ainda pode usar LocalDb.
- Quaisquer alterações no aplicativo que impliquem alterações no esquema do banco de dados precisam passar por migrações do EF em todos os fragmentos. O código de exemplo para este documento não demonstra como fazer isso. Considere o uso de Update-Database com um parâmetro ConnectionString para iterar em todos os fragmentos; ou extraia o script T-SQL para a migração pendente usando Update-Database com a opção -Script e aplique o script T-SQL aos seus fragmentos.
- Dada uma solicitação, presume-se que todo o seu processamento de banco de dados está contido em um único fragmento, conforme identificado pela chave de fragmentação fornecida pela solicitação. No entanto, esta suposição nem sempre é verdadeira. Por exemplo, quando não é possível disponibilizar uma chave de fragmentação. Para resolver isso, a biblioteca de cliente fornece a classe MultiShardQuery que implementa uma abstração de conexão para consultar vários fragmentos. Aprender a usar o MultiShardQuery em combinação com o EF está além do escopo deste documento
Conclusão
Por meio das etapas descritas neste documento, os aplicativos EF podem usar o recurso da biblioteca cliente de banco de dados elástico para roteamento dependente de dados refatoração construtores das subclasses DbContext usadas no aplicativo EF. Isso limita as alterações necessárias para os locais onde as classes DbContext já existem. Além disso, os aplicativos EF podem continuar a se beneficiar da implantação automática de esquema combinando as etapas que invocam as migrações EF necessárias com o registro de novos fragmentos e mapeamentos no mapa de estilhaços.
Recursos adicionais
Ainda não está usando ferramentas de banco de dados elástico? Consulte o nosso Guia de Introdução. Para dúvidas, entre em contato conosco na página de perguntas e respostas da Microsoft para o Banco de dados SQL e para solicitações de recursos, adicione novas ideias ou vote em ideias existentes no fórum de comentários do Banco de dados SQL.