Pontos de dados

Coisas boas do Code First no Entity Framework 6

Julie Lerman

Baixar o código de exemplo

Julie Lerman
No meu artigo de dezembro de 2013, “Entity Framework 6: A edição Ninja” (msdn.microsoft.com/magazine/dn532202), descrevi muitos dos novos recursos do Entity Framework 6 (EF6). Porém, não consegui me aprofundar em todos os recursos, então, neste mês vou detalhar algumas das melhorias do EF6 específicas para o Code First. Dois dos recursos sobre os quais falarei são relevantes para os mapeamentos do Code First, e os outros estão relacionados às migrações do Code First.

Carregue muitos mapeamentos da API Fluent de uma vez

Há duas maneiras de especificar mapeamentos do Fluent (de suas classes ao seu banco de dados) para um modelo. Uma é diretamente no método OnModel­Creating da classe DbContext, assim:

modelBuilder.Entity<Casino>()
  .Property(c=>c.Name).IsRequired().HasMaxLength(200);
modelBuilder.Entity<PokerTable>()
  .Property(c => c.SerialNo).HasColumnName("SerialNumber");

Quando você tem um muitos mapeamentos, pode organizá-los por tipo em classes EntityTypeConfiguration individuais que, posteriormente, você adicionará ao construtor de modelos usando um código como este:

modelBuilder.Configurations.Add(new CasinoConfiguration());
modelBuilder.Configurations.Add(new PokerTableConfiguration());

No entanto, se você tiver muitos mapeamentos para muitas entidades, poderá acabar com muitos métodos modelBuilder.Configurations.Add repetitivos em OnModelCreating. Para eliminar esse transtorno, agora você pode carregar todos EntityTypeConfigurations de determinado assembly com um único método. Aqui uso o novo método AddFromAssembly para carregar as configurações que são especificadas no assembly de execução para o aplicativo em execução:

modelBuilder.Configurations
  .AddFromAssembly(Assembly.GetExecutingAssembly())

Um recurso interessante deste método é que ele não é limitado pelo escopo das configurações que irá carregar. As classes EntityTypeConfiguration personalizadas podem até ser marcadas como privadas que o método irá encontrá-las. Além disso, AddFromAssembly também compreende hierarquias de herança em EntityTypeConfigurations.

AddFromAssembly é uma de uma série de contribuições da comunidade de Unai Zorrilla. Confira a postagem no blog de Zorrilla, “EF6: Setting Configurations Automatically”, em bit.ly/16OBuJ5 para obter mais detalhes — e deixe uma nota de agradecimento a ele enquanto estiver na página.

Defina seu próprio esquema padrão

Em minha coluna Pontos de dados de março de 2013, “Brincando com o EF6 Alpha” (msdn.microsoft.com/magazine/jj991973), falei um pouco sobre o suporte a esquemas para o Code First. Uma novo recurso é o mapeamento que você pode configurar em OnModelCreating: HasDefaultSchema. Isso permite que você especifique o esquema de banco de dados para todas as tabelas para as quais o contexto é mapeado, em vez de usar o padrão de dbo do EF. No artigo “Entity Framework 6: A edição Ninja”, executem um SQL bruto na discussão sobre DbTransactions:

("Update Casino.Casinos set rating= " + (int) Casino.Rating)

Você deve ter notado o esquema Casino que especifiquei no SQL. O motivo pelo qual tenho um esquema chamado Casino é porque o especifiquei no método OnModelCreating de meu DbContext (CasinoSlotsModel):

modelBuilder.HasDefaultSchema("Casino");

Também falei sobre o novo suporte do EF6 para migrações do Code First que estão sendo realizadas com um banco de dados que tem esquemas diferentes. Como isso não mudou desde o EF6 Alpha, deixarei você ler sobre isso na minha coluna anterior.

Scripts de migrações para reconstruir o banco de dados de qualquer ponto

Um dos novos recursos para migrações do Code First listados nas especificações (e em cada artigo que simplesmente reitera as especificações) são os "scripts de migrações idempotentes". Agora, você pode ser alguém com graduação comp sci ou talvez seja um DBA. Mas não tenho nenhum desses títulos e tive de procurar o significado de "idempotente". De acordo com o Wikipedia, que faz referência a um engenheiro da IBM (bit.ly/9MIrRK): "Em ciências da computação, o termo "idempotente" é usado... para descrever uma operação que produzirá os mesmos resultados se executada uma vez ou várias vezes." Também tive de averiguar como pronunciá-lo. É i-dem-po-ten-te.

No mundo do banco de dados, idempotente pode ser usado para descrever um script SQL que sempre tem o mesmo impacto em um banco de dados, independentemente do seu estado. Com as migrações do Code First, antes de executar a migração, esse script verificará se essa migração já foi executada. Esse recurso é específico do parâmetro -script de Update-Database.

O EF sempre forneceu a capacidade de criar scripts que passam por todas as etapas da migração de um ponto de partida específico (origem) e, opcionalmente, a um ponto final explícito (destino). Este comando NuGet evoca esse comportamento:

Update-Database -Script
  –SourceMigration:NameOfStartMigration
  –TargetMigration:NameOfEndMigrationThatIsntLatest

A novidade é que o script gerado é muito mais inteligente agora, quando você chama o comando de uma maneira específica:

Update-Database -Script -SourceMigration $InitialDatabase

Isso também parece funcionar se você substituir $InitialDatabase por 0, mas não há garantias de que essa alternativa seja aceita em versões futuras.

Em resposta, o script começa com a migração inicial e continua até a última migração. É por isso que a sintaxe não fornece explicitamente os nomes das migrações de origem ou destino.

Mas com este comando específico, o EF6 adiciona lógica ao script que verifica quais as migrações já foram aplicadas antes de executar o SQL para uma migração em particular. Veja um exemplo de código que você verá no script:

    IF @CurrentMigration < '201310311821192_AddedSomeNewPropertyToCasino'
    BEGIN
      ALTER TABLE [Casino].[Casinos] ADD [AgainSomeNewProperty] [nvarchar](4000)
      INSERT [Casino].[__MigrationHistory]([MigrationId], [ContextKey], [Model], [ProductVersion])
      VALUES (N'201310311821192_AddedSomeNewPropertyToCasino',
        N'CasinoModel.Migrations.Configuration', HugeBinaryValue , 
        N'6.1.0-alpha1-21011')
    END

O código verifica na tabela ­_MigrationHistory se o script AddedSomeNewPropertyToCasino já foi executado no banco de dados. Em caso positivo, o SQL dessa migração não será executado. Antes do EF6, o script apenas executaria o SQL sem verificar se ele já foi executado.

Tabelas de histórico de migração favoráveis para fornecedores

O EF6 permite personalizar como a tabela _MigrationHistory é definida usando um recurso chamado Tabela Personalizável de Histórico de Migrações. Isso é importante se você estiver usando provedores de dados de terceiros que possuem requisitos diferentes dos padrões. A Figura 1 mostra o esquema padrão da tabela.

Este é um exemplo de como isso pode ser útil. No CodePlex, um desenvolvedor observou que como cada char nos dois PKs poderia ter mais de 1 byte, ele estava recebendo um erro dizendo que o comprimento da chave composta criada com MigrationId e ContextKey excedia o comprimento de chave permitido para uma tabela MySQL: 767 bytes (bit.ly/18rw1BX). Para resolver o problema, a equipe MySQL está usando o HistoryContext internamente para alterar os comprimentos das duas colunas de chave ao trabalhar na versão EF6 do MySQL Connector para ADO.NET (bit.ly/7uYw2a).

Observe na Figura 1 que a tabela __MigrationHistory recebe o mesmo esquema que defini para o contexto usando HasSchema: Casino. Você pode ter convenções que dizem que tabelas que não são de dados devem ser usadas por um esquema com permissões limitadas, então, é recomendável mudar o nome do esquema dessa tabela. Você pode fazer isso com o HistoryContext, por exemplo, para especificar que o esquema "admin" deve ser usado.

Default Schema for the __MigrationHistory Table
Figura 1 Esquema padrão para a tabela __MigrationHistory

HistoryContext é derivado de DbContext, então, o código deve ser um pouco familiar se você já trabalhou com DbContext e Code First antes. A Figura 2 mostra uma classe HistoryContext que defini para especificar o esquema admin.

Figura 2 HistoryContext personalizado para redefinir a tabela __MigrationHistory

public class CustomHistoryContext : HistoryContext
{
  public CustomHistoryContext
   (DbConnection dbConnection, string defaultSchema)
     : base(dbConnection, defaultSchema)
  {
  }
  protected override void OnModelCreating(DbModelBuilder modelBuilder)
  {
    base.OnModelCreating(modelBuilder);
    modelBuilder.Entity<HistoryRow>()
      .ToTable("__MigrationHistory", "admin");
  }
}

Você também pode usar chamadas de API familiares, como Property().HasColumnType, HasMaxLength ou HasColumnName. Por exemplo, se você precisar alterar o comprimento do ContextKey, faça isto:

modelBuilder.Entity<HistoryRow>()
  .Property(h => h.ContextKey).HasMaxLength(255);

Se você leu o artigo do mês passado, já deve estar familiarizado com o EF6 DbConfiguration. Isso é o que você usa para deixar seu modelo ciente do arquivo CustomHistory­Context. Em seu construtor personalizado do DbConfiguration, você precisa especificar o HistoryContext a ser usado. Aqui eu defino o contexto para o provedor do SQL Server usar CustomHistoryContext:

SetHistoryContext(
  SqlProviderServices.ProviderInvariantName,
     (connection, defaultSchema) =>
  new CustomHistoryContext(connection,
     defaultSchema));

A funcionalidade de inicialização e migrações de bancos de dados verá este contexto adicional e construirá o SQL de acordo. A tabela na Figura 3 foi criada usando o HistoryContext personalizado para alterar o nome do esquema da tabela __MigrationHistory para admin. (Não incluí o código de amostra para alterar o comprimento da coluna.)

Customized __MigrationHistory Table
Figura 3 Tabela __MigrationHistory personalizada

HistoryContext é um recurso poderoso, mas precisa ser usado com cuidado. Com sorte, o provedor de banco de dados que você está usando já terá usado para especificar uma tabela __MigrationHistory que é relevante para o banco de dados de destino, e você não precisa sequer pensar nisso. Caso contrário, recomendo verificar o documento do MSDN sobre esse recurso e seguir sua orientação (bit.ly/16eK2pD).

Crie operações de migração personalizadas

Se você já usou migrações antes – não automaticamente, mas criando e executando explicitamente migrações na janela Package Manager Console – você pode ter explorado os arquivos de migração criados por add-migration. Nesse caso, você pode ter descoberto que as migrações do Code First têm uma API fortemente tipada para descrever cada mudança a fazer no esquema de banco de dados: System.Data.Entity.Migrations.DbMigration.

A Figura 4 mostra um exemplo de aplicação do método Create­Table que define uma série de atributos.

Figura 4 Método DbMigrations.CreateTable

CreateTable(
  Casino.SlotMachines",
  c => new
    {
      Id = c.Int(nullable: false, identity: true),
      SlotMachineType = c.Int(nullable: false),
      SerialNumber = c.String(maxLength: 4000),
      HotelId = c.Int(nullable: false),
      DateInService = c.DateTime(nullable: false),
      HasQuietMode = c.Boolean(nullable: false),
      LastMaintenance = c.DateTime(nullable: false),
      Casino_Id = c.Int(),
    })
  .PrimaryKey(t => t.Id)
  .ForeignKey("Casino.Casinos", t => t.Casino_Id)
  .Index(t => t.Casino_Id);

Em seguida, os provedores convertem essas chamadas de API em SQL específico de banco de dados.

Existem métodos para criar tabelas e índices, criar ou alterar propriedades, soltar objetos e muito mais. É uma API bastante rica, como você pode ver dentre as possibilidades listadas na Figura 5 – que incluem a capacidade de simplesmente executar alguns SQL diretamente. Mas, em alguns casos, pode não ser rica o suficiente para suas necessidades. Por exemplo, não há método para criar exibições de banco de dados, especificar permissões ou muitas outras operações.

DbMigrations Database Schema Operations
Figura 5 Operações de esquema de banco de dados DbMigrations

Mais uma vez, a comunidade tem a solução. No EF6, agora você pode criar operações de migrações personalizadas para poder chamar personalizando as classes de migração geradas por add-migration. Isso se deve a outro desenvolvedor da comunidade CodePlex, Iñaki Elcoro, conhecido como iceclow.

Para criar sua própria operação, você deve executar algumas etapas. Mostrarei a essência de cada etapa. Você pode ver o código completo e como as etapas são organizadas no download deste artigo.

  • Defina a operação. Aqui defini uma CreateView­Operation, como listada na Figura 6.
  • Crie um método de extensão para apontar para a operação. Isso simplifica a chamada de DbMigration:
public static void CreateView(this DbMigration migration,
  string viewName, string viewqueryString)
{
  ((IDbMigration) migration)
    .AddOperation(new CreateViewOperation(viewName,
       viewqueryString));
}
  • Defina o SQL para a operação no método Generate de uma classe SqlServerMigrationSqlGenerator personalizada, como mostrado na Figura 7.
  • Diga para a classe DbConfiguration usar a classe SqlServerMigrationSqlGenerator personalizada:
SetMigrationSqlGenerator("System.Data.SqlClient",
  () => new CustomSqlServerMigrationSqlGenerator());

Figura 6 Uma operação de migração para criar uma exibição de banco de dados

public class CreateViewOperation : MigrationOperation
{
  public CreateViewOperation(string viewName, string viewQueryString)
    : base(null)
  {
    ViewName = viewName;
    ViewString = viewQueryString;
  }
  public string ViewName { get; private set; }
  public string ViewString { get; private set; }
  public override bool IsDestructiveChange
  {
    get { return false; }
  }
}

Figura 7 Classe SqlServerMigrationSqlGenerator personalizada

public class CustomSqlServerMigrationSqlGenerator
  : SqlServerMigrationSqlGenerator
{
  protected override void Generate(MigrationOperation migrationOperation)
  {
    var operation = migrationOperation as CreateViewOperation;
    if (operation != null)
    {
      using (IndentedTextWriter writer = Writer())
      {
        writer.WriteLine("CREATE VIEW {0} AS {1} ; ",
                          operation.ViewName,
                          operation.ViewString);
        Statement(writer);
      }
    }
  }
}

Com tudo isso providenciado, agora você pode usar a nova operação em um arquivo de migração e Update-Database saberá o que fazer com ele. A Figura 8 mostra a operação CreateView em uso e fornece um lembrete de que você também precisa criar uma operação para remover a exibição, que seria chamada pelo método Down se você precisasse simplificar essa migração.

Figura 8 Usando a nova operação CreateView

public partial class AddView : DbMigration
  {
    public override void Up()
    {
      this.CreateView("dbo.CasinosWithOver100SlotMachines",
                      @"SELECT  *
                        FROM    Casino.Casinos
                        WHERE  Id IN  (SELECT   CasinoId AS Id
                        FROM     Casino.SlotMachines
                        GROUP BY CasinoId
                        HAVING COUNT(CasinoId)>=100)");
    }
    public override void Down()
    {
      this.RemoveView("dbo.CasinosWithOver100SlotMachines");
    }
  }

Depois de chamar Update-Database, você pode ver a nova exibição em meu banco de dados na Figura 9.

The Newly Created View Generated by Update-Database
Figura 9 A exibição recém-criada gerada por Update-Database

O Code First continua a evoluir

Com os principais recursos do Code First implantados, a Microsoft e os desenvolvedores da comunidade aproveitaram a oportunidade de começar a fazer melhorias no EF6, com recursos que agora se beneficiam da maior flexibilidade. Mas isso não para com o lançamento do EF6. Se você filtrar os itens de trabalho do CodePlex para versões posteriores à 6.0.1 (em bit.ly/1dA0LZf), poderá ver que ainda mais melhorias estão sendo adicionadas às versões futuras do EF6 e do Code First. Esses itens estão em vários estados. Talvez você encontrará aquele com o qual gostaria de trabalhar.

Julie Lerman é MVP da Microsoft, mentora e consultora do .NET, que reside nas colinas de Vermont. Você pode encontrá-la fazendo apresentações sobre acesso a dados e outros tópicos do Microsoft .NET em grupos de usuários e conferências em todo o mundo. Seu blog está em thedatafarm.com/blog e é autora do livro “Programming Entity Framework” (2010), além das edições Code First (2011) e DbContext (2012), todos da O’Reilly Media. Siga Julie no Twitter em twitter.com/julielerman e confira seus cursos da Pluralsight em juliel.me/PS-Videos.

Agradecemos ao seguinte especialista técnico pela revisão deste artigo: Rowan Miller (Microsoft)