Entity Framework + SQL Server 2008 + MySQL 5.0

(Português) - (English)

(To be translated...)

Sempre que falamos em Entity Framework, um dos benefícios que ressaltamos é a possibilidade de se trabalhar com um modelo conceitual (CSDL) e através do mapeamento (MSL) para o modelo físico (SSDL) conseguiríamos abstrair, por exemplo, a complexidade de um modelo E-R. Se conseguimos criar essa abstração, então também podemos trabalhar com diferentes bancos de dados mapeados para um mesmo modelo de entidades, certo? Sim, agora só precisamos saber como fazemos isso.

Para chegar ao resultado final desse artigo, utilizei os seguintes recursos:

* Visual Studio 2008 SP1
* SQL Server 2008 RTM
* MySQL 5.0 (instalei o pacote mysql-essential-5.0.67-win32 - Wizard super intuitivo, moleza)
* MySQL WorkBench (mysql-workbench-oss-5.0.24-win32)
* Trial do MySQL Provider da DevArts - antiga Core Lab (https://www.devart.com/)

Gostaria de ressaltar que esse artigo descreve a primeira abordagem que veio em minha cabeça, e (admito) é um pouco manual e trabalhosa, então pode ser que haja alguma maneira mais fácil, que espero poder compartilhar um dia. Chega de papo, eu quero ver ação...

Primeiramente precisamos criar um banco de dados no MySQL, que chamei de NWindFake e somente possui as tabelas Produto e Categoria, que seguem aproximadamente o modelo do que temos nas tabelas Products e Categories no Northwind. Para criar tudo certinho, usei o MySQL Workbench para gerar o script (veja figura 01) e a interface de linha de comando do MySQL, assim criei o banco NWindFake e mandei rodar o script que contém a criação das tabelas e inserção dos registros, como mostra a figura 02. O script para criação dessas tabelas e inserção dos registros está anexado a esse artigo.

Com os bancos de dados criados e funcionando corretamente, vamos aos projetos. Crie um novo projeto console usando o Visual Studio 2008 e adicione mais dois projetos do tipo class library.

Um desses projetos será sua camada de acesso a dados, adicione um modelo de entidades apontando para o Northwind no SQL Server 2008, incluindo somente a tabela Products no seu modelo (vamos ser minimalistas nesse artigo). Importante: Mantenha os nomes agnósticos da fonte de dados e anote os nomes que você escolheu, no meu caso, chamei o edmx de NWindSimple, a entrada no App.Config (que será o contexto) de NWindEntities e o namespace de NWindModel. Tirei da entidade algumas propriedades que não me interessavam e o resultado pode ser visto na figura 03.

Uma vez que sua camada de acesso a dados está pronta, no projeto console adicionamos referências para o projeto DataAccess e assembly System.Data.Entity. Lembre também de adicionar um arquivo de configuração com a string de conexão do entity framework, gerada no projeto de acesso a dados. Agora podemos codificar alguma besteirinha usando o LINQ, que mostra sete registros com os produtos mais caros.

static void Main(string[] args)
{
NWindEntities contexto = InstanciaContexto();

            var consulta = from p in contexto.Products
where p.UnitPrice > 50
orderby p.UnitPrice descending
select new { p.ProductName, p.UnitPrice };

            foreach (var item in consulta)
{
Console.WriteLine("\nNome: {0} \nPreco {1}",
item.ProductName, item.UnitPrice.ToString());
}

            Console.ReadLine();
}

public static NWindEntities InstanciaContexto()
{
return new NWindEntities();
}

Tudo funcionando? Agora vamos ao MySQL...

No outro projeto que você havia adicionado, crie um novo modelo de entidades apontando para o NWindFake que está no MySQL (Vide a figura 04 para ver a interface do wizard que configura o acesso ao banco de dados) e, por facilidade, use os mesmos nomes que anotou anteriormente. Selecione a tabela Produto e você verá uma entidade simples, mas diferente do que foi criado para o SQL Server. Agora precisamos ter um modelo conceitual idêntico ao gerado anteriormente e refazer o mapeamento para o modelo físico. Podemos fazer isso através da interface gráfica, apagando o que foi gerado, copiando e colando, mas que graça teria?

Abra os dois modelos (SQL Server e MySQL) utilizando um editor XML. Para o xml do MySQL: comente toda a região conceitual (CSDL) e na região de mapeamento (C-S mapping), retire o elemento <EntitySetMapping Name="produto">, deixando um "vazio" no mapeamento - veja abaixo - que será preenchido através da interface gráfica (pode ser direto no XML, mas chega, né?).

<!-- C-S mapping content -->
<edmx:Mappings>
<Mapping Space="C-S" xmlns="urn:schemas-microsoft-com:windows:storage:mapping:CS">
<EntityContainerMapping StorageEntityContainer="NWindModelStoreContainer" CdmEntityContainer="NWindEntities">

        </EntityContainerMapping>
</Mapping>
</edmx:Mappings>

Agora precisamos garantir que o modelo conceitual é o mesmo, certo? Então copie o CSDL do XML gerado para o SQL Server e cole no lugar onde estava o CSDL do MySQL. Após isso, você pode fechar os XMLs e abrir o MySQL no designer do EF. Se o modelo não abrir é porque você fez alguma coisa errada. Lembra que eu pedi que fossem utilizados os mesmos nomes entre os modelos? Se você olhar o CSDL vai ver que existem referências para o Namespace e Container, então para não precisarmos fazer a substituição destes quando formos reutilizar os XMLs, usamos o mesmo nome. Veja um fragmento do que eu estou falando:

<Schema Namespace="NWindModel" Alias="Self" xmlns=" https://schemas.microsoft.com/ado/2006/04/edm" >
<EntityContainer Name="NWindEntities">

Nota: o nome do arquivo não influencia aqui, só quis deixar tudo uniforme.

Nesse momento temos um modelo conceitual, um físico e nenhum mapeamento. Use o editor de mapeamentos para fechar as lacunas que estão faltando, no fim você deve ver o que a figura 05 mostra.

Agora que os dois modelos estão prontos e possuem o mesmo modelo conceitual, precisamos de uma maneira de substituir o modelo físico e o de armazenamento, de acordo com a fonte de dados que estamos utilizando. Isso é feito através da string de conexão do entity framework, então se analisarmos o que foi gerado anteriormente, veremos em negrito a informação de onde estão os metadados:

<add name="NWindEntities" connectionString="metadata=res://*/NWindSimple.csdl|res://*/NWindSimple.ssdl|res://*/NWindSimple.msl;provider=System.Data.SqlClient;provider connection string=&quot;Data Source=DEStudio_Server;Initial Catalog=Northwind;Persist Security Info=True;User ID=sa;Password=.sql2008.;MultipleActiveResultSets=True&quot;" providerName="System.Data.EntityClient" />

Na versão RTM do Entity Framework, o CSDL, MSL e SSDL são embutidos no arquivo de recurso, o que atrapalha um pouco nossa vida. Para mudar isso, clique em alguma área vazia do seu modelo de entidades e altere a propriedade "Metadata Artifact Processing" para "Copy to Output Directory". Faça isso para os dois modelos, recompile a solução, e olhe o diretório de compilação, onde verá arquivos com extensões SSDL, MSL e CSDL.

Alguns dos próximos passos podem ser automatizados através do controle de builds, mas para facilitar minha vida, vamos fazer manualmente...

1 - No projeto de acesso a dados do SQL Server, copie os três novos arquivos gerados (CSDL, MSL e SSDL) para o "bin\debug" do projeto console.
2 - No projeto de acesso a dados do MySQL, renomeie o MSL e SSDL para NWindSimple_MySQL. Copie esses dois arquivos para o "bin\debug" do projeto console.
3 - No projeto console, adicione mais um elemento ao arquivo de configuração (copie e cole do projeto MySQL) alterando o nome do elemento e dos arquivos SSDL e MSL, para refletirem o que foi feito no passo 02.
4 - A configuração original indicava que os metadados estavam como recursos embutidos no projeto, então retire todos os seis "res://*/", isso indicará que o EF deve procurar os arquivos no diretório onde está o executável da aplicação. No fim, tudo deve ficar semelhante ao XML abaixo:

<configuration>
<connectionStrings>
<add name="NWindEntities" connectionString="metadata=NWindSimple.csdl|NWindSimple.ssdl|NWindSimple.msl;provider=System.Data.SqlClient;provider connection string=&quot;Data Source=DEStudio_Server;Initial Catalog=Northwind;Persist Security Info=True;User ID=sa;Password=blablabla;MultipleActiveResultSets=True&quot;" providerName="System.Data.EntityClient" />
<add name="NWindEntities_MySQL" connectionString="metadata=NWindSimple.csdl |NWindSimple_MySQL.ssdl|NWindSimple_MySQL.msl;provider=CoreLab.MySql;provider connection string=&quot;User Id=root;Password=blablabla;Host=localhost;Database=nwindfake;Persist Security Info=True&quot;" providerName="System.Data.EntityClient" />
</connectionStrings>
</configuration>

Nesse momento, se você executar seu projeto, deve ver os mesmos sete produtos que haviam aparecido anteriormente. Isso significa que seu arquivo de configuração está funcionando corretamente para o SQL Server (como não mudamos muita coisa, era de se esperar). Agora, altere o método InstanciaContexto para que ele crie o contexto utilizando a outra string de conexão (MySQL).

public static NWindEntities InstanciaContexto()
{
//return new NWindEntities();
return new NWindEntities("Name=NWindEntities_MySQL");
}

Ao re-executar o projeto, você deve ver os mesmos sete produtos com os nomes precedidos por "MySQL_" (tomei o cuidado de manipular os nomes para garantir que eu estava acessando a base correta). A figura 06 mostra o resultado...

PRONTO! Agora eu tenho um projeto que acessa tanto o SQL Server 2008, quanto o MySQL 5.0, alterando uma linha de código! Se você quer ver o resultado de tudo isso, coloquei a solução completa em anexo a esse post.

Conclusão

Passada a emoção de ver a coisa funcionando, acho que vale a pena analisar a solução criada.

1 - Criei um método InstanciaContexto de propósito, pois é ali que você vai codificar a inteligência para selecionar qual conexão vai utilizar, lendo isso provavelmente de um arquivo de configuração.
2 - Inicialmente tentei criar os dois modelos dentro do mesmo projeto, alterando o nome dos arquivos e da configuração, onde eu conseguiria manter os dois EDMx em paralelo. O problema aconteceu quando o CSDL foi reutilizado, pois as classes geradas a partir das entidades colidiram. Eu poderia separar por namespaces diferentes, mas resolvi deixar isso para um próximo artigo.
3 - Não gosto de referenciar diretamente o objeto contexto na minha aplicação. Em outro projeto eu usei o refactoring "Extract Interface" para gerar um arquivo INwindEntities.cs e implementei a interface em um outro arquivo, através da classe parcial do contexto. Dessa forma, quando as classes são geradas novamente pelo designer, não perdemos a implementação da interface. Toda vez que o conceitual mudar, o processo de extrair a interface deve ser refeito.
4 - Se possível, eu gostaria de indicar para o VS que não gerasse as classes para alguns modelos edmx, pois isso resolveria meu problema de conflito. Vou pesquisar mais sobre o assunto.

Eu acho que ainda temos que evoluir mais para facilitar a criação de projetos "multi-bancos", de forma que seja uma brincadeira de criança. Atualmente vejo alguns pontos que precisam ser melhorados e sei que a manutenção disso ainda trará um pouco de dor de cabeça, mas acredito que esse trabalho será muito menor do que uma empresa implementar todos os mecanismos necessários para abstrair a complexidade dos banco de dados (seja ele qual for) e ainda se dar ao luxo de utilizar o LINQ.

Voltarei com mais assuntos relacionados...

image

(Figure 01)

image

(Figure 02)

image

(Figure 03)

image

(Figure 04)

image

(Figure 05)

image

(Figure 06)

[]s
Luciano Caixeta Moreira
luciano.moreira@microsoft.com
===============================================
This post is provided "AS IS" and confers no right
===============================================

20080905_EF_SQLServer_MySQL.zip