3. Estudo de caso com o SQL Server Express 2012
3.1. Introduction
A maioria dos exemplos encontrados na internet para o Entity Framework são exemplos com o SQL Server. Isso é bastante normal. É provável que esse seja o SGBD mais difundido no mundo das empresas. Vamos seguir essa tendência. Os exemplos serão, posteriormente, ampliados para todos os bancos de dados mencionados no parágrafo 1.2.
3.2. Instalação das ferramentas
Não descreveremos a instalação das ferramentas. De fato, isso exigiria uma quantidade enorme de capturas de tela que se tornariam obsoletas rapidamente. Essa é uma tarefa (nem sempre fácil, é verdade) que deixamos a cargo do leitor.
Precisamos instalar as seguintes ferramentas:
- o SGBD SQL Server Express 2012: [http://www.microsoft.com/fr-fr/download/details.aspx?id=29062]. Baixe a versão “With Tools”, que inclui, junto com o SGBD, uma ferramenta de administração:
Após instalar o SGBD, iniciamos o programa:
![]() |
![]() |
- [1]: no Menu Iniciar, inicie o “Gerenciador de configuração do servidor SQL”;
- [2]: nesse gerenciador, inicie o servidor;
- [3]: ele foi iniciado.
Agora, iniciamos a ferramenta de administração do SQL Server:
![]() |
- [1]: no Menu Iniciar, inicie o “SQL Server Management Studio”;
- [2]: a ferramenta de administração.
Vamos nos conectar ao servidor:
![]() |
- no [1], abrimos o explorador de objetos;
- em [2], informamos os parâmetros da conexão:
- [3]: o servidor (local) (atenção aos parênteses necessários) refere-se ao servidor instalado na máquina,
- [4]: escolhe-se a autenticação do Windows. É necessário ser administrador do próprio computador para que essa conexão seja bem-sucedida,
- [5]: efetua-se a conexão;
![]() |
- [6]: estamos conectados;
- [7]: deseja-se alterar algumas propriedades do servidor;
![]() |
- [8]: solicita-se que haja dois modos de autenticação:
- autenticação do Windows, como acabou de ser utilizada. Um usuário do Windows com os direitos adequados pode, então, conectar-se,
- autenticação no servidor SQL. O usuário deve fazer parte dos usuários registrados no SGBD;
Feito isso, é possível validar as propriedades do servidor;
- [9]: editam-se as propriedades do usuário (administrador do sistema);
![]() |
- no [10], define-se uma senha para ele. No restante deste documento, essa senha é sqlserver2012;
![]() |
- em [10], concede-se a ele permissão para se conectar;
- em [11], a conexão é ativada. Com isso, o assistente pode ser validado;
- em [12], desconectamo-nos do servidor.
Agora, nos reconectamos com o login sa/sqlserver2012:
![]() |
- em [1], reconectamos;
- em [2], na autenticação SQL Server;
- em [3], o usuário é sa;
- em [4], a senha dele é sqlserver2012;
- em [5], realiza-se a conexão;
![]() |
- em [6], estamos conectados.
Agora vamos criar um banco de dados de demonstração:
![]() |
- em [1], criamos uma nova BD;
- em [2], ela se chamará demo;
- em [3], confirmamos;
![]() |
- no [4], o banco de dados é criado;
- em [5], cria-se uma nova tabela no banco de dados demo;
![]() |
![]() |
![]() |
![]() |
- em [6], define-se uma tabela com duas colunas: ID e NOM;
- em [7], define-se a coluna [ID] como chave primária;
- em [8], a chave primária é representada por uma chave;
- em [9], a tabela é salva;
- em [10], atribui-se um nome a ela;
- em [11], para que a tabela apareça no banco de dados [demo], é preciso atualizar o banco de dados;
- em [12], a tabela [PERSONNES] foi criada com sucesso.
Por enquanto, já sabemos o suficiente sobre como usar a ferramenta de administração do SQL Server.
3.3. O servidor integrado (localdb)\v11.0
O VS Express 2012 vem com um servidor SQL integrado. Supõe-se aqui que o VS Express 2012 tenha sido instalado no [http://www.microsoft.com/visualstudio/fra/downloads]. Iniciamos o VS 2012 no [1]:
![]() |
Iniciamos a ferramenta de administração do SQL Server 2012 [2] e fazemos login no [3].
![]() |
- no [4], conectamos-nos ao servidor (localdb)\v11.0;
- em [5], com autenticação do Windows;
- no [6], após a conexão bem-sucedida, são exibidos os bancos de dados do servidor. Assim como anteriormente, seria possível criar um novo banco de dados.
Não utilizaremos esse servidor embutido no VS 2012.
3.4. Criação do banco de dados a partir das entidades
O Entity Framework 5 Code First permite criar um banco de dados a partir de entidades. É isso que veremos agora. Com o VS Express 2012, criamos um primeiro projeto de console em C#:
![]() |
![]() |
- em [1], a definição do projeto;
- em [2], o projeto criado.
Todos os nossos projetos precisarão d , o DLL do Entity Framework 5. Vamos adicioná-lo:
![]() |
- em [1], a ferramenta NuGet permite baixar dependências;
![]() |
- em [2], baixamos a dependência do Entity Framework;
- em [3], a referência foi adicionada ao projeto.
É possível obter mais informações verificando as propriedades da referência adicionada:
![]() |
- em [1], a versão do DLL. É necessária a versão 5;
- em [2], sua localização no sistema de arquivos: <solução>\packages\EntityFramework.5.0.0\lib\net45\EntityFramework.dll, onde <solução> é a pasta da solução VS. Todos os pacotes adicionados pelo NuGet serão colocados na pasta <solução>/packages;
- no [3], foi criado um arquivo [packages.config]. Seu conteúdo é o seguinte:
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="EntityFramework" version="5.0.0" targetFramework="net45" />
</packages>
Ele lista os pacotes importados pelo NuGet.
Voltemos ao projeto VS e criemos uma pasta [Models] no projeto:
![]() |
- em [1], adição de uma pasta ao projeto;
- em [2], que se chamará [Models].
Manteremos esse hábito daqui em diante de colocar a definição de nossas entidades na pasta [Models].
Para construir nossas entidades, vamos nos valer da definição do banco de dados MySQL 5 utilizada no projeto NHibernate. Vale lembrar a função das entidades EF:
![]() |
As entidades devem refletir as tabelas do banco de dados. A camada de acesso aos dados utiliza essas entidades em vez de trabalhar diretamente com as tabelas. Vamos começar pela tabela [MEDECINS]:
3.4.1. A entidade [Medecin]
Ela contém informações sobre os médicos gerenciados pelo aplicativo [RdvMedecins].
![]() | ![]() |
- ID: número que identifica o médico — chave primária da tabela
- VERSION: número que identifica a versão da linha na tabela. Esse número é incrementado em 1 sempre que uma alteração é feita na linha.
- NOM: o sobrenome do médico
- PRENOM: seu nome
- TITRE: seu título (Srta., Sra., Sr.)
Poderíamos partir da seguinte classe [Medecin]:
using System;
[Table("MEDECINS", Schema = "dbo")]
namespace RdvMedecins.Entites
{
public class Medecin
{
// dados
public int Id { get; set; }
public string Titre { get; set; }
public string Nom { get; set; }
public string Prenom { get; set; }
}
- linha 3: a classe [Medecin] está associada à tabela [MEDECINS] do banco de dados. Esta tabela estará em um esquema chamado “dbo”.
Colocamos essa classe em um arquivo [Entites.cs] [1]. É lá que colocaremos todas as nossas entidades.
![]() |
Ainda na pasta [Models], criamos o seguinte arquivo [Context.cs]:
using System.Data.Entity;
using RdvMedecins.Entites;
namespace RdvMedecins.Models
{
// o contexto
public class RdvMedecinsContext : DbContext
{
// os médicos
public DbSet<Medecin> Medecins { get; set; }
}
// inicialização do banco de dados
public class RdvMedecinsInitializer : DropCreateDatabaseAlways<RdvMedecinsContext>
{
}
}
- linha 8: a classe [RdvMedecinsContext] representará o contexto de persistência, c.-à-d. o conjunto de entidades gerenciadas pela ORM. Ela deve derivar da classe [System.Data.Entity.DbContext];
- linha 11: o campo [Medecins] representará as entidades do tipo [Medecin] do contexto de persistência. É do tipo DbSet<Médico>. Geralmente, haverá tantos [DbSet] quanto tabelas no banco de dados, um por tabela;
- linha 15: define-se uma classe [RdvMedecinsInitializer] para inicializar o banco de dados criado. Aqui, ela deriva da classe [DropCreateDataBaseAlways] que, como o próprio nome indica, exclui o banco de dados caso ele já exista e, em seguida, o recria. Isso é útil na fase de desenvolvimento da BD. O parâmetro da classe [DropCreateDataBaseAlways] é o tipo de contexto de persistência associado ao banco de dados. É possível utilizar outras classes-pai além da [DropCreateDataBaseAlways] para a classe de inicialização:
- [DropCreateDatabaseIfModelChanges]: recria o banco de dados se as entidades tiverem sido alteradas,
- [CreateDatabaseIfNotExists]: cria o banco de dados caso ele não exista;
Resta-nos criar um programa principal. Será o seguinte: [CreateDB_01.cs]:
using System;
using System.Data.Entity;
using RdvMedecins.Models;
namespace RdvMedecins_01
{
class CreateDB_01
{
static void Main(string[] args)
{
// criando o banco de dados
Database.SetInitializer(new RdvMedecinsInitializer());
using (var context = new RdvMedecinsContext())
{
context.Database.Initialize(false);
}
}
}
}
- linha 12: [System.Data.Entity.DataBase] é uma classe que oferece métodos estáticos para gerenciar o banco de dados associado a um contexto de persistência. O método estático [SetInitializer] permite especificar a classe de inicialização do banco de dados. Isso não inicia a inicialização;
- linha 13: para trabalhar com um contexto de persistência, é preciso instanciá-lo. É isso que se faz aqui. Utiliza-se uma cláusula
usingpara que o contexto seja fechado automaticamente ao sair da cláusula. Portanto, na linha 17, o contexto é fechado; - linha 15: iniciamos explicitamente a geração do banco de dados associado ao contexto de persistência [RdvMedecinsContext]. O parâmetro false indica que essa operação não deve ser realizada se já tiver sido feita para esse contexto. Aqui, poderíamos muito bem ter colocado true.
Ao trabalhar com um banco de dados, os parâmetros de conexão geralmente são registrados no arquivo [App.config]. Observemos que, por enquanto, eles não estão lá:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
<!-- Para obter mais informações sobre a configuração do Entity Framework, acesse http://go.microsoft.com/fwlink/?LinkID=237468 -->
<section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
</configSections>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
<entityFramework>
<defaultConnectionFactory type="System.Data.Entity.Infrastructure.SqlConnectionFactory, EntityFramework" />
</entityFramework>
</configuration>
Os elementos acima foram gravados no arquivo [App.config] quando adicionamos a dependência do Entity Framework às referências do projeto.
Vamos executar o projeto (Ctrl-F5) após iniciar o SQL Server Express (isso é importante):
![]() | ![]() |
A execução deve ser concluída sem erros. Agora, vamos abrir a ferramenta de administração do SQL Server e atualizar a exibição:
![]() |
Percebemos que foi criada uma base de dados com o nome completo da classe [RdvMedecinsContext] e que ela contém uma tabela [dbo.MEDECINS] (esse é o nome que lhe foi atribuído) com colunas que reproduzem os nomes dos campos da entidade [Medecin]. Se o código foi executado corretamente e o banco de dados acima não aparecer, é preciso verificar o servidor embutido (localdb)\v11.0 (consulte a página 19). Com o VS 2012 Pro, esse servidor é utilizado caso o servidor SQL não esteja ativo no momento da execução do código. Com o VS 2012 Express, não.
Vamos examinar a estrutura da tabela [MEDECINS]:
- ela contém os nomes dos campos da entidade [Medecin];
- a coluna [Id] é a chave primária. Essa é uma convenção do EF: se a entidade E possui um campo Id ou Eid (MedecinId), então essa coluna é a chave primária na tabela associada;
- os tipos das colunas da tabela são os mesmos dos campos da entidade;
- para as colunas Título, Sobrenome e Nome, foi utilizado o tipo [nvarchar(max)]. Seria possível ser mais preciso: 5 caracteres para o título, 30 para o sobrenome e o nome;
- as colunas Título, Sobrenome e Nome podem ter o valor NULL. Vamos alterar isso.
Vejamos as propriedades da chave primária [Id]:
![]() |
Em [1], vemos que a chave primária é do tipo [Identité], o que significa que seu valor é gerado automaticamente pelo SQL Server. Adotaremos essa estratégia para todos os SGBD.
Vamos reduzir a dependência das convenções de EF utilizando anotações. O código da entidade em [Entites.cs] passa a ser o seguinte:
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace RdvMedecins.Entites
{
[Table("MEDECINS", Schema = "dbo")]
public class Medecin
{
// dados
[Key]
[Column("ID")]
public int Id { get; set; }
[Required]
[MaxLength(5)]
[Column("TITRE")]
public string Titre { get; set; }
[Required]
[MaxLength(30)]
[Column("NOM")]
public string Nom { get; set; }
[Required]
[MaxLength(30)]
[Column("PRENOM")]
public string Prenom { get; set; }
[Required]
[Column("VERSION")]
public int Version { get; set; }
}
}
- linhas 2 e 3: as anotações estão nos espaços de nomes [System.ComponentModel.DataAnnotations] (Key, Required, MaxLength) e [System.ComponentModel.DataAnnotations.Schema] (Column). Outras anotações podem ser encontradas nos espaços de nomes URL e [http://msdn.microsoft.com/en-us/data/gg193958.aspx];
- linha 11: [Key] designa a chave primária;
- linha 12: [Column] define o nome da coluna correspondente ao campo;
- linha 14: [Required] indica que o campo é obrigatório (SQL, NOT, NULL);
- linha 15: [MaxLength] define o tamanho máximo da sequência de caracteres, e [MinLength], seu tamanho mínimo;
Vamos executar o projeto com essa nova definição da entidade [Medecin]. O banco de dados criado fica então da seguinte forma:
![]() |
- as colunas têm os nomes que lhes foram atribuídos;
- a anotação [Required] foi convertida em SQL, NOT e NULL;
- a anotação [MaxLength(N)] foi convertida para o tipo SQL nvarchar(N).
No aplicativo NHibernate, a coluna [VERSION] servia para impedir acessos simultâneos a uma mesma linha de uma tabela. O princípio é o seguinte:
- um processo P1 lê uma linha L da tabela [MEDECINS] no momento T1. A linha possui a versão V1;
- um processo P2 lê a mesma linha L da tabela [MEDECINS] no momento T2. A linha possui a versão V1 porque o processo P1 ainda não validou sua modificação;
- o processo P1 valida sua modificação na linha L. A versão da linha L passa então para V2=V1+1;
- o processo P2 valida sua modificação na linha L. O ORM, então, lança uma exceção, pois o processo P2 possui uma versão V1 da linha L diferente da versão V2 encontrada no banco de dados.
Isso é chamado de gerenciamento otimista de acessos concorrentes. Com o EF 5, um campo que desempenha essa função deve ter um dos dois atributos: [Timestamp] ou [ConcurrencyCheck]. O SQL Server possui o tipo [timestamp]. O valor de uma coluna com esse tipo é gerado automaticamente pelo SQL Server sempre que houver uma inserção ou modificação de uma linha. Essa coluna pode, então, ser usada para gerenciar a concorrência de acesso. Retomando o exemplo anterior, o processo P2 encontrará um timestamp diferente daquele que leu, pois, nesse intervalo, a modificação feita pelo processo P1 o terá alterado.
Nossa entidade [Medecin] evolui da seguinte forma:
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace RdvMedecins.Entites
{
[Table("MEDECINS", Schema = "dbo")]
public class Medecin
{
// dados
[Key]
[Column("ID")]
public int Id { get; set; }
[Required]
[MaxLength(5)]
[Column("TITRE")]
public string Titre { get; set; }
[Required]
[MaxLength(30)]
[Column("NOM")]
public string Nom { get; set; }
[Required]
[MaxLength(30)]
[Column("PRENOM")]
public string Prenom { get; set; }
[Column("TIMESTAMP")]
[Timestamp]
public byte[] Timestamp { get; set; }
}
}
- linhas 26-28: a nova coluna com o atributo [Timestamp] da linha 27. O tipo do campo deve ser byte[] (linha 28). O nome do campo pode ser qualquer um. Não se atribui a ele o atributo [Required], pois não é o aplicativo que fornecerá esse valor, mas o próprio SGBD.
Se executarmos o projeto com essa nova entidade, o banco de dados será atualizado da seguinte forma:
![]() |
Resta-nos resolver um último ponto. O contexto de persistência “sabe” que uma entidade deve ser inserida no banco de dados porque, nesse momento, sua chave primária é igual a null. É a inserção no banco de dados que atribuirá um valor à chave primária. Nesse caso, o tipo int atribuído à chave primária [Id] não é adequado, pois esse tipo não aceita o valor null. Assim, atribui-se a ela o tipo int?, que aceita os valores *int e o ponteiro *null. A entidade [Medecin] utilizada será, portanto, a seguinte:
public class Medecin
{
// dados
[Key]
[Column("ID")]
public int? Id { get; set; }
...
Resta-nos ver como representar em uma entidade o conceito de chave estrangeira entre tabelas.
3.4.2. A entidade [Creneau]
A tabela [CRENEAUX] lista os horários em que os RV são possíveis:
![]() |
![]() |
- ID: número que identifica o intervalo horário — chave primária da tabela
- VERSION: número que identifica a versão da linha na tabela. Esse número é incrementado em 1 sempre que uma modificação é feita na linha.
- ID_MEDECIN: número que identifica o médico ao qual esse horário pertence – chave estrangeira na coluna MEDECINS (ID).
- HDEBUT: hora de início do horário
- MDEBUT: minutos de início do horário
- HFIN: hora de término do horário
- MFIN: minutos de término do intervalo
A segunda linha da tabela [CRENEAUX] (ver [1] acima) indica, por exemplo, que o horário nº 2 começa às 8h20 e termina às 8h40 e pertence à médica nº 1 (Sra. Marie PELISSIER).
Com essas informações, podemos definir a entidade [Creneau] da seguinte forma em [Entites.cs]:
[Table("CRENEAUX", Schema = "dbo")]
public class Creneau
{
// dados
[Key]
[Column("ID")]
public int? Id { get; set; }
[Required]
[Column("HDEBUT")]
public int Hdebut { get; set; }
[Required]
[Column("MDEBUT")]
public int Mdebut { get; set; }
[Required]
[Column("HFIN")]
public int Hfin { get; set; }
[Required]
[Column("MFIN")]
public int Mfin { get; set; }
[Required]
public virtual Medecin Medecin { get; set; }
[Column("TIMESTAMP")]
[Timestamp]
public byte[] Timestamp { get; set; }
}
A única novidade está nas linhas 20-21. O fato de a tabela [CRENEAUX] possuir uma chave estrangeira na tabela [MEDECINS] é refletido na entidade [Creneau] pela presença de uma referência à entidade [Medecin], linha 21. O nome do campo não importa; apenas o tipo é relevante. A propriedade deve ser declarada como virtual com a palavra-chave virtual. De fato, EF precisa redefinir todas as propriedades chamadas de “navegacionais”, ou seja, aquelas que correspondem a uma chave estrangeira e que permitem passar de uma tabela para outra.
Para testar a nova entidade, precisamos fazer algumas modificações em [Context.cs]:
using System.Data.Entity;
using RdvMedecins.Entites;
namespace RdvMedecins.Models
{
// o contexto
public class RdvMedecinsContext : DbContext
{
// as entidades
public DbSet<Medecin> Medecins { get; set; }
public DbSet<Creneau> Creneaux { get; set; }
}
// inicialização do banco de dados
public class RdvMedecinsInitializer : DropCreateDatabaseIfModelChanges<RdvMedecinsContext>
{
}
}
A linha 12 reflete o fato de que o contexto tem mais uma entidade para gerenciar. Ao executarmos o projeto, obtemos o seguinte novo banco de dados:
![]() |
A tabela [CRENEAUX] foi criada corretamente, e a novidade é a presença das chaves estrangeiras [1] e [2]. Seu nome foi gerado a partir do nome do campo correspondente na entidade (Médico), com o sufixo “_Id”. Para conhecer as propriedades dessa chave estrangeira, tentamos alterá-la para [3].
![]() |
A captura de tela acima mostra que [Medecin_Id] é a chave estrangeira da tabela [CRENEAUX] e que ela faz referência à chave primária [ID] da tabela [MEDECINS].
Se criarmos as entidades para um banco de dados existente, a coluna da chave estrangeira não se chamará necessariamente [Medecin_Id]. Para as outras colunas, vimos que a anotação [Column] resolvia esse problema. Estranhamente, isso é mais complicado para uma chave estrangeira. É preciso proceder da seguinte maneira:
public class Creneau
{
// dados
...
[Required]
[Column("MEDECIN_ID")]
public int MedecinId { get; set; }
[Required]
[ForeignKey("MedecinId")]
public virtual Medecin Medecin { get; set; }
...
}
- linhas 5-7: cria-se um campo do tipo da chave estrangeira (int). Com o atributo [Column], especifica-se o nome da coluna que será a chave estrangeira na tabela associada à entidade;
- linha 9: adiciona-se a anotação [ForeignKey] ao campo do tipo [Medecin]. O argumento dessa anotação é o nome do campo (não da coluna) associado à coluna-chave estrangeira da tabela.
A execução do projeto cria, desta vez, a seguinte tabela:
![]() |
Acima, a coluna-chave estrangeira possui, de fato, o nome que lhe foi atribuído. É importante observar que os campos:
[Required]
[Column("MEDECIN_ID")]
public int MedecinId { get; set; }
[Required]
[ForeignKey("MedecinId")]
public virtual Medecin Medecin { get; set; }
geraram apenas uma única coluna, a coluna [MEDECIN_ID]. No entanto, a presença do campo [MedecinId] é importante. Ao ler uma linha da tabela [CRENEAUX], ela receberá o valor da coluna [MEDECIN_ID], ou seja, o valor da chave estrangeira na tabela [MEDECINS]. Isso costuma ser útil.
O campo [Medecin] acima reflete a relação “muitos para um” que vincula a entidade [Creneau] à entidade [Medecin]. Vários objetos [Creneau] estão ligados a um mesmo [Medecin]. A relação inversa, em que um objeto [Medecin] está associado a vários objetos [Creneau], pode ser modelada por meio de um campo adicional na entidade [Medecin]:
public class Medecin
{
// dados
[Key]
[Column("ID")]
public int? Id { get; set; }
...
public ICollection<Creneau> Creneaux { get; set; }
[Column("TIMESTAMP")]
[Timestamp]
public byte[] Timestamp { get; set; }
Na linha 8, foi adicionado o campo [Creneaux], que é uma coleção de objetos [Creneau]. Esse campo nos dará acesso a todos os horários disponíveis do médico.
Ao executar o projeto novamente, percebe-se que a tabela [MEDECINS] não sofreu alterações:
![]() |
Nenhuma coluna foi adicionada. A relação de chave estrangeira existente entre a tabela [CRENEAUX] e a tabela [MEDECINS] é suficiente para que a tabela EF consiga gerar os campos relacionados a ela:
public class Medecin
{
...
public ICollection<Creneau> Creneaux { get; set; }
...
}
public class Creneau
{
...
[Required]
[Column("MEDECIN_ID")]
public int MedecinId { get; set; }
[Required]
[ForeignKey("MedecinId")]
public virtual Medecin Medecin { get; set; }
...
}
Já sabemos o essencial. Podemos concluir com a criação das outras duas entidades.
3.4.3. As entidades [Client] e [Rv]
Com o que aprendemos, podemos escrever as entidades [Client] e [Rv]. A entidade [Client] contém informações sobre os clientes gerenciados pelo aplicativo [RdvMedecins].
![]() | ![]() |
- ID: número que identifica o cliente — chave primária da tabela
- VERSION: número que identifica a versão da linha na tabela. Esse número é incrementado em 1 sempre que uma modificação é feita na linha.
- NOM: o nome do cliente
- PRENOM: seu nome
- TITRE: seu título (Srta., Sra., Sr.)
A entidade [Client] poderia ser a seguinte:
[Table("CLIENTS", Schema = "dbo")]
public class Client
{
// dados
[Key]
[Column("ID")]
public int? Id { get; set; }
[Required]
[MaxLength(5)]
[Column("TITRE")]
public string Titre { get; set; }
[Required]
[MaxLength(30)]
[Column("NOM")]
public string Nom { get; set; }
[Required]
[MaxLength(30)]
[Column("PRENOM")]
public string Prenom { get; set; }
// os Rvs do cliente
public ICollection<Rv> Rvs { get; set; }
[Column("TIMESTAMP")]
[Timestamp]
public byte[] Timestamp { get; set; }
}
A classe [Client] é praticamente idêntica à classe [Medecin]. Elas poderiam ser derivadas de uma mesma classe pai. A novidade está na linha 21. Ela reflete o fato de que um cliente pode ter vários compromissos e decorre da presença de uma chave estrangeira da tabela [RVS] para a tabela [CLIENTS].
A entidade [Rv] representa um compromisso:
![]() |
- ID: número que identifica o RV de forma exclusiva – chave primária
- JOUR: dia do RV
- ID_CRENEAU: horário do RV – chave estrangeira na coluna [ID] da tabela [CRENEAUX] – define tanto o horário quanto o médico responsável.
- ID_CLIENT: número do cliente para o qual a reserva foi feita – chave estrangeira na coluna [ID] da tabela [CLIENTS]
A entidade [Rv] poderia ser a seguinte:
[Table("MEDECINS", Schema = "dbo")]
public class Rv
{
// dados
[Key]
[Column("ID")]
public int? Id { get; set; }
[Required]
[Column("JOUR")]
public DateTime Jour { get; set; }
[Column("CLIENT_ID")]
public int ClientId { get; set; }
[ForeignKey("ClientId")]
[Required]
public virtual Client Client { get; set; }
[Column("CRENEAU_ID")]
public int CreneauId { get; set; }
[ForeignKey("CreneauId")]
[Required]
public virtual Creneau Creneau { get; set; }
[Column("TIMESTAMP")]
[Timestamp]
public byte[] Timestamp { get; set; }
}
- linhas 5-7: chave primária;
- linhas 8-10: data do compromisso;
- linhas 11-12: a chave estrangeira da tabela [RVS] para a tabela [CLIENTS];
- linhas 13-15: o cliente que tem a consulta;
- linhas 16-17: a chave estrangeira da tabela [RVS] para a tabela [CRENEAUX];
- linhas 18-20: o horário da consulta;
- linhas 21-23: o campo de gerenciamento de acessos simultâneos.
Na linha 17, observa-se uma relação muitos-para-um: um intervalo de horário pode corresponder a vários compromissos (não no mesmo dia). A relação inversa pode ser refletida na entidade [Creneau]:
public class Creneau
{
// as Rvs do horário
public ICollection<Rv> Rvs { get; set; }
...
}
Linha 4: o conjunto de compromissos agendados nesse intervalo horário.
Ao executar o projeto, o banco de dados gerado é o seguinte:
![]() |
As tabelas [MEDECINS] e [CRENEAUX] não sofreram alterações. As tabelas [CLIENTS] e [RVS] são as seguintes:
![]() | ![]() |
Isso era o que se esperava. Ainda precisamos resolver alguns detalhes:
- gerenciar o nome do banco de dados. Aqui, ele foi gerado pelo EF;
- preencher o banco de dados com dados.
3.4.4. Definir o nome do banco de dados
Para definir o nome do banco de dados gerado pelo EF, usaremos uma cadeia de conexão definida no [App.config]. Esse arquivo de configuração fica da seguinte forma:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
<!-- Para obter mais informações sobre a configuração do Entity Framework, acesse http://go.microsoft.com/fwlink/?LinkID=237468 -->
<section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
</configSections>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
<entityFramework>
<defaultConnectionFactory type="System.Data.Entity.Infrastructure.SqlConnectionFactory, EntityFramework" />
</entityFramework>
<!-- cadeia de conexão com o banco de dados -->
<connectionStrings>
<add name="RdvMedecinsContext"
connectionString="Data Source=localhost;Initial Catalog=rdvmedecins-ef;User Id=sa;Password=sqlserver2012;"
providerName="System.Data.SqlClient" />
</connectionStrings>
<!-- o provedor de fábrica -->
<system.data>
<DbProviderFactories>
<add name="SqlClient Data Provider"
invariant="System.Data.SqlClient"
description=".Net Framework Data Provider for SqlServer"
type="System.Data.SqlClient.SqlClientFactory, System.Data,
Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
/>
</DbProviderFactories>
</system.data>
</configuration>
- linhas 15-19: a string de conexão com o banco de dados;
- linha 16: o atributo [name] retoma o nome da classe [RdvMedecinsContext] utilizada para o contexto de persistência. É importante lembrar disso. Essa restrição pode ser contornada no construtor do contexto:
// construtor
public RdvMedecinsContext()
: base("monContexte")
{
}
Nesse caso, poderemos ter name= "monContexte". É isso que teremos no restante do documento.
- linha 17: a string de conexão. [Data Source]: o nome do servidor no qual se encontra o SGBD, [Initial Catalog]: o nome do banco de dados, ou seja, neste caso, [rdvmedecins-ef]; [User Id]: o proprietário da conexão; [Password]: sua senha. O leitor deverá adaptar essa sequência ao seu ambiente;
- linhas 21-29: definem um [DbProviderFactory]. Não sei o que é isso. A julgar pelo nome, poderia ser uma classe que permite gerar a camada [ADO.NET] que separa EF de SGBD:
![]() |
Na verdade, essas linhas são desnecessárias para o SQL Server, mas tive que adicioná-las para os outros SGBD. Por isso, estou colocando-as aqui apenas para registro. Elas não atrapalham. O único ponto importante é a versão da linha 27. É a do DLL e do [System.Data], presentes nas referências do projeto:
![]() |
Pronto. Estamos prontos. Executamos o projeto e obtemos a base [rdvmedecins-ef] a seguir:
![]() |
Essa será nossa base definitiva. Agora só precisamos inserir os dados nela.
3.4.5. Preenchimento do banco de dados
A classe de inicialização do banco de dados pode ser usada para inserir dados nele:
public class RdvMedecinsInitializer : DropCreateDatabaseIfModelChanges<RdvMedecinsContext>
{
// inicialização da base
public class RdvMedecinsInitializer : DropCreateDatabaseAlways<RdvMedecinsContext>
{
protected override void Seed(RdvMedecinsContext context)
{
base.Seed(context);
// inicializando o banco de dados
// os clientes
Client[] clients ={
new Client { Titre = "Mr", Nom = "Martin", Prenom = "Jules" },
new Client { Titre = "Mme", Nom = "German", Prenom = "Christine" },
new Client { Titre = "Mr", Nom = "Jacquard", Prenom = "Jules" },
new Client { Titre = "Melle", Nom = "Bistrou", Prenom = "Brigitte" }
};
foreach (Client client in clients)
{
context.Clients.Add(client);
}
// os médicos
Medecin[] medecins ={
new Medecin { Titre = "Mme", Nom = "Pelissier", Prenom = "Marie" },
new Medecin { Titre = "Mr", Nom = "Bromard", Prenom = "Jacques" },
new Medecin { Titre = "Mr", Nom = "Jandot", Prenom = "Philippe" },
new Medecin { Titre = "Melle", Nom = "Jacquemot", Prenom = "Justine" }
};
foreach (Medecin medecin in medecins)
{
context.Medecins.Add(medecin);
}
// os horários disponíveis
Creneau[] creneaux ={
new Creneau{ Hdebut=8,Mdebut=0,Hfin=8,Mfin=20,Medecin=medecins[0]},
new Creneau{ Hdebut=8,Mdebut=20,Hfin=8,Mfin=40,Medecin=medecins[0]},
new Creneau{ Hdebut=8,Mdebut=40,Hfin=9,Mfin=0,Medecin=medecins[0]},
new Creneau{ Hdebut=9,Mdebut=0,Hfin=9,Mfin=20,Medecin=medecins[0]},
new Creneau{ Hdebut=9,Mdebut=20,Hfin=9,Mfin=40,Medecin=medecins[0]},
new Creneau{ Hdebut=9,Mdebut=40,Hfin=10,Mfin=0,Medecin=medecins[0]},
new Creneau{ Hdebut=10,Mdebut=0,Hfin=10,Mfin=20,Medecin=medecins[0]},
new Creneau{ Hdebut=10,Mdebut=20,Hfin=10,Mfin=40,Medecin=medecins[0]},
new Creneau{ Hdebut=10,Mdebut=40,Hfin=11,Mfin=0,Medecin=medecins[0]},
new Creneau{ Hdebut=11,Mdebut=0,Hfin=11,Mfin=20,Medecin=medecins[0]},
new Creneau{ Hdebut=11,Mdebut=20,Hfin=11,Mfin=40,Medecin=medecins[0]},
new Creneau{ Hdebut=11,Mdebut=40,Hfin=12,Mfin=0,Medecin=medecins[0]},
new Creneau{ Hdebut=14,Mdebut=0,Hfin=14,Mfin=20,Medecin=medecins[0]},
new Creneau{ Hdebut=14,Mdebut=20,Hfin=14,Mfin=40,Medecin=medecins[0]},
new Creneau{ Hdebut=14,Mdebut=40,Hfin=15,Mfin=0,Medecin=medecins[0]},
new Creneau{ Hdebut=15,Mdebut=0,Hfin=15,Mfin=20,Medecin=medecins[0]},
new Creneau{ Hdebut=15,Mdebut=20,Hfin=15,Mfin=40,Medecin=medecins[0]},
new Creneau{ Hdebut=15,Mdebut=40,Hfin=16,Mfin=0,Medecin=medecins[0]},
new Creneau{ Hdebut=16,Mdebut=0,Hfin=16,Mfin=20,Medecin=medecins[0]},
new Creneau{ Hdebut=16,Mdebut=20,Hfin=16,Mfin=40,Medecin=medecins[0]},
new Creneau{ Hdebut=16,Mdebut=40,Hfin=17,Mfin=0,Medecin=medecins[0]},
new Creneau{ Hdebut=17,Mdebut=0,Hfin=17,Mfin=20,Medecin=medecins[0]},
new Creneau{ Hdebut=17,Mdebut=20,Hfin=17,Mfin=40,Medecin=medecins[0]},
new Creneau{ Hdebut=17,Mdebut=40,Hfin=18,Mfin=0,Medecin=medecins[0]},
new Creneau{ Hdebut=8,Mdebut=0,Hfin=8,Mfin=20,Medecin=medecins[1]},
new Creneau{ Hdebut=8,Mdebut=20,Hfin=8,Mfin=40,Medecin=medecins[1]},
new Creneau{ Hdebut=8,Mdebut=40,Hfin=9,Mfin=0,Medecin=medecins[1]},
new Creneau{ Hdebut=9,Mdebut=0,Hfin=9,Mfin=20,Medecin=medecins[1]},
new Creneau{ Hdebut=9,Mdebut=20,Hfin=9,Mfin=40,Medecin=medecins[1]},
new Creneau{ Hdebut=9,Mdebut=40,Hfin=10,Mfin=0,Medecin=medecins[1]},
new Creneau{ Hdebut=10,Mdebut=0,Hfin=10,Mfin=20,Medecin=medecins[1]},
new Creneau{ Hdebut=10,Mdebut=20,Hfin=10,Mfin=40,Medecin=medecins[1]},
new Creneau{ Hdebut=10,Mdebut=40,Hfin=11,Mfin=0,Medecin=medecins[1]},
new Creneau{ Hdebut=11,Mdebut=0,Hfin=11,Mfin=20,Medecin=medecins[1]},
new Creneau{ Hdebut=11,Mdebut=20,Hfin=11,Mfin=40,Medecin=medecins[1]},
new Creneau{ Hdebut=11,Mdebut=40,Hfin=12,Mfin=0,Medecin=medecins[1]},
};
foreach (Creneau creneau in creneaux)
{
context.Creneaux.Add(creneau);
}
// as consultas
context.Rvs.Add(new Rv { Jour = new System.DateTime(2012, 10, 8), Client = clients[0], Creneau = creneaux[0] });
}
}
}
- linha 6: a inicialização ocorre no método [Seed]. Esse método existe na classe pai. Aqui, ele é redefinido. O argumento é o contexto de persistência [RdvMedecinsContext] do aplicativo;
- linha 8: o argumento é passado para a classe pai; é provável que esta abra o contexto de persistência que lhe foi passado, pois essa abertura não é mais necessária posteriormente;
- linhas 11-16: criação de 4 clientes;
- linhas 17-20: estes são adicionados ao contexto de persistência, mais precisamente aos médicos desse contexto. Observe-se o método [Add], que permite isso. É preciso lembrar aqui a definição do contexto:
public class RdvMedecinsContext : DbContext
{
// as entidades
public DbSet<Medecin> Medecins { get; set; }
public DbSet<Creneau> Creneaux { get; set; }
public DbSet<Client> Clients { get; set; }
public DbSet<Rv> Rvs { get; set; }
...
Diz-se também que os clientes foram vinculados ao contexto, ou seja, agora são gerenciados pelo EF. Antes, eles não estavam vinculados. Eles existiam como objetos, mas não eram gerenciados pelo EF;
- linhas 21-27: criação de 4 médicos;
- linhas 28-31: eles são inseridos no contexto de persistência;
- linhas 33-70: criação de horários disponíveis. Linhas 34-57, para o médico medecins[0]; linhas 58-69, para o médico medecins[1]. Os demais médicos não possuem horários disponíveis;
- linhas 71-74: esses horários são inseridos no contexto de persistência;
- linha 76: criação de um agendamento para o primeiro cliente com o primeiro horário disponível e sua inserção no contexto de persistência.
Ao executar o projeto, obtém-se a base a seguir:
![]() | ![]() |
Acima, vemos a tabela [CLIENTS] preenchida.
3.4.6. Modificação das entidades
Atualmente, as classes [Medecin] e [Client] são praticamente idênticas. Na verdade, se removemos os campos adicionados para o gerenciamento da persistência com EF 5, elas são idênticas. Vamos fazê-las derivar de uma classe [Personne]. Essas duas entidades passam a ser as seguintes:
// uma pessoa
public abstract class Personne
{
// data
[Key]
[Column("ID")]
public int? Id { get; set; }
[Required]
[MaxLength(5)]
[Column("TITRE")]
public string Titre { get; set; }
[Required]
[MaxLength(30)]
[Column("NOM")]
public string Nom { get; set; }
[Required]
[MaxLength(30)]
[Column("PRENOM")]
public string Prenom { get; set; }
[Column("TIMESTAMP")]
[Timestamp]
public byte[] Timestamp { get; set; }
// assinatura
public override string ToString()
{
return String.Format("[{0},{1},{2},{3},{4}]", Id, Titre, Prenom, Nom, dump(Timestamp));
}
// assinatura curta
public string ShortIdentity()
{
...
}
// utilitário
private string dump(byte[] timestamp)
{
...
}
}
[Table("MEDECINS", Schema = "dbo")]
public class Medecin : Personne
{
// horários de atendimento do médico
public ICollection<Creneau> Creneaux { get; set; }
// assinatura
public override string ToString()
{
return String.Format("Medecin {0}", base.ToString());
}
}
[Table("CLIENTS", Schema = "dbo")]
public class Client : Personne
{
// as consultas do cliente
public ICollection<Rv> Rvs { get; set; }
// assinatura
public override string ToString()
{
return String.Format("Client {0}", base.ToString());
}
}
Ao executar o projeto, obtém-se a mesma base. O EF 5 mapeou as classes mais baixas da herança, cada uma em uma tabela. Na verdade, o EF 5 possui diferentes estratégias de geração de tabelas para representar a herança de entidades. Não as apresentaremos aqui. Pode-se ler, por exemplo, “ Entity Framework Code First Inheritance: Table Per Hierarchy and Table Per Type”, no URL [http://www.codeproject.com/Articles/393228/Entity-Framework-Code-First-Inheritance-Table-Per].
A partir de agora, utilizaremos essa versão das entidades.
3.4.7. Adicionar restrições ao banco de dados
Ainda temos um detalhe a resolver. A tabela [RVS] de compromissos é a seguinte:
![]() |
Essa tabela deve ter uma restrição de exclusividade: para um determinado dia, um horário de atendimento de um médico só pode ser reservado uma vez para uma consulta. Em termos de tabela, isso significa que o par (JOUR,CRENEAU_ID) deve ser único. Não sei se essa restrição pode ser definida diretamente no código, seja nas entidades ou no contexto. É provável que sim, mas não verifiquei. Vamos adotar outra abordagem. Usaremos um cliente de administração do SQL Server para adicionar essa restrição.
Com o “SQL Server Management Studio”, não encontrei uma maneira simples de adicionar essa restrição, a não ser executando o comando SQL, que a cria:
![]() |
- no [1], cria-se uma consulta SQL para o banco de dados [rdvmedecins-ef];
- em [2], a consulta SQL que cria a restrição de unicidade;
- em [3], a execução dessa consulta criou um novo índice na tabela [RVS].
Existem outras ferramentas de administração do SQL Server. Vamos utilizar aqui a ferramenta EMS SQL Manager for SQL Server Freeware [http://www.sqlmanager.net/fr/products/mssql/manager/download]. Após a instalação, vamos iniciá-la:
![]() |
- no [1], salvamos um banco de dados;
- no [2], conectamo-nos ao servidor (local);
- em [3], com autenticação no servidor SQL;
- em [4], com a identidade “sa”;
- em [5], e a senha sqlserver2012;
- em [6], passa-se para a próxima etapa;
![]() |
- em [7], selecione o banco de dados [rdvmedecins-ef];
- em [8], conclui-se o assistente;
- em [9], o banco de dados aparece na árvore de bancos de dados. Conectamo-nos a ele em [10];
- em [11], a conexão é estabelecida.
“SQL Manager Lite para SQL Server” permite criar a restrição de exclusividade na tabela [RVS].
![]() |
- no [1], vemos a restrição de unicidade que criamos anteriormente;
- em [2], a removemos;
- em [3], o índice correspondente a essa restrição de unicidade desapareceu.
Recriamos a restrição excluída:
![]() |
- em [1], criamos um novo índice para a tabela [RVS];
- em [2], atribuímos um nome a ele;
- em [3], trata-se de uma restrição de unicidade;
- em [4], nas colunas JOUR e CRENEAU_ID;
A aba DDL nos fornece o código SQL que será executado:
![]() |
- em [6], compila-se a ordem SQL;
![]() |
- em [7], confirme;
- em [8], o novo índice apareceu.
A interface oferecida pelo “SQL Manager Lite for SQL server” é semelhante à oferecida pelo “SQL Server Management Studio”. É possível encontrar interfaces semelhantes para o SGBD Oracle, PostgreSQL, Firebird e MySQL. Portanto, continuaremos a partir de agora com essa família de ferramentas de administração do SGBD.
Para acessar as informações de uma tabela, basta clicar duas vezes nela:
![]() |
As informações sobre a tabela selecionada estão disponíveis em abas. Acima, vemos a aba [Fields] da tabela [CLIENTS]. A aba [Data] exibe o conteúdo da tabela:

3.4.8. A base definitiva
Temos nossa base definitiva. Exportamos seu script SQL para podermos regenerá-la, se necessário.
![]() |
- em [1], início do assistente;
- em [2], o servidor;
- em [3], o banco de dados que será exportado;
![]() |
- em [4], especifique o nome do arquivo onde o script SQL será salvo;
- em [5], especifique a codificação;
- em [6], especifique o que você deseja extrair (tabelas, restrições, dados);
![]() |
- no [7], você pode ajustar o script que será gerado;
- em [8], conclua o assistente.
O script foi gerado e carregado no editor de scripts. Você pode consultar o código SQL gerado. Vamos reconstruir o banco de dados a partir desse script.
![]() |
- em [1], a base de dados é excluída;
- nos scripts [2] e [3], recriamos o banco de dados;
![]() |
- em [4], realiza-se a autenticação;
- em [5], executa-se o script SQL para a criação do banco de dados;
![]() |
- no [6], registra-se no “SQL Manager”;
- no [7], conecta-se ao banco de dados recém-criado;
![]() |
- em [8], o banco de dados ainda não possui tabelas;
- em [9a], abre-se um editor de script SQL;
![]() |
- em [9b], abre-se o script SQL criado anteriormente;
- em [10], executa-se o script;
![]() |
- em [11], as tabelas foram criadas;
- no [12], elas são preenchidas;
![]() |
- em [14], encontramos a restrição de unicidade que havíamos criado para a tabela [RVS].
A partir de agora, vamos trabalhar com esse banco de dados existente. Se ele for destruído ou danificado, sabemos como regenerá-lo.
3.5. Utilização do banco de dados com o Entity Framework
Vamos:
- adicionar, excluir e modificar elementos do banco de dados;
- consultar o banco de dados com LINQ to Entities;
- gerenciar acessos simultâneos a um mesmo elemento do banco de dados;
- compreender os conceitos de Lazy Loading / Eager Loading;
- descobrir que a atualização do banco de dados pelo contexto de persistência ocorre em uma transação.
3.5.1. Exclusão de elementos do contexto de persistência
Temos um banco de dados preenchido. Vamos esvaziá-lo. Criamos uma nova classe [Erase.cs] no projeto atual [1]:
![]() |
A classe [Erase] é a seguinte:
using RdvMedecins.Models;
namespace RdvMedecins_01
{
class Erase
{
static void Main(string[] args)
{
using (var context = new RdvMedecinsContext())
{
// limpar o banco de dados atual
// clientes
foreach (var client in context.Clients)
{
context.Clients.Remove(client);
}
// os médicos
foreach (var medecin in context.Medecins)
{
context.Medecins.Remove(medecin);
}
// salvando o contexto de persistência
context.SaveChanges();
}
}
}
}
- linha 9: as operações em um contexto de persistência são sempre realizadas em uma cláusula [using]. Isso garante que, ao sair do [using], o contexto tenha sido fechado;
- linha 13: percorre-se o contexto dos clientes [context.Clients]. Todos os clientes do banco de dados serão inseridos no contexto de persistência;
- linha 15: para cada um deles, executa-se a operação [Remove], que os remove do contexto. Na verdade, eles ainda estão no contexto, mas em um estado “excluído”;
- linhas 18-21: faz-se o mesmo para os médicos;
- linha 23: salvamos o contexto de persistência no banco de dados.
Ao salvar o contexto no banco de dados, as entidades do contexto que:
- possuem uma chave primária nula são submetidas a uma operação SQL INSERT;
- estão no estado “excluído” são submetidas a uma operação SQL DELETE;
- estão no estado “modificado” são submetidos a uma operação SQL UPDATE;
Como veremos mais adiante, essas operações SQL são realizadas dentro de uma transação. Se uma delas falhar, tudo o que foi feito anteriormente será revertido.
Vamos definir o programa [Erase] como o novo objeto de inicialização do projeto [1] e, em seguida, executar o projeto.
![]() |
Vamos verificar o banco de dados. Verificaremos que todas as tabelas estão vazias ([2]). Isso é surpreendente, pois solicitamos apenas a exclusão dos médicos e dos clientes. Foi por meio das chaves estrangeiras que as outras tabelas foram esvaziadas em cascata.
A definição da chave estrangeira da tabela [CRENEAUX] para a tabela [MEDECINS] foi definida da seguinte forma pelo provedor de EF 5:
![]() |
- na [1], seleciona-se a tabela [CRENEAUX];
- em [2], seleciona-se a aba de chaves estrangeiras;
- em [3], edita-se a única chave estrangeira;
![]() |
- em [4], na aba DDL, a definição SQL da restrição de chave estrangeira;
- Na tabela [5], a cláusula ON DELETE CASCADE faz com que a exclusão de um médico resulte na exclusão dos horários a ele associados.
As restrições de chave estrangeira da tabela [RVS] são definidas de forma análoga:
- linhas 1-6: a exclusão de um cliente também excluirá os horários de consulta a ele associados;
- linhas 1-6: excluir um horário também excluirá todos os compromissos a ele associados.
3.5.2. Adicionando elementos ao contexto de persistência
Agora que esvaziamos o banco de dados, vamos preenchê-lo novamente. Adicionamos ao projeto o programa [Fill.cs] [1].
![]() |
O programa [Fill.cs] é o seguinte:
using RdvMedecins.Entites;
using RdvMedecins.Models;
namespace RdvMedecins_01
{
class Fill
{
static void Main(string[] args)
{
using (var context = new RdvMedecinsContext())
{
// esvaziar o banco de dados atual
foreach (var client in context.Clients)
{
context.Clients.Remove(client);
}
foreach (var medecin in context.Medecins)
{
context.Medecins.Remove(medecin);
}
// reinicializa-se
// os clientes
Client[] clients ={
new Client { Titre = "Mr", Nom = "Martin", Prenom = "Jules" },
new Client { Titre = "Mme", Nom = "German", Prenom = "Christine" },
new Client { Titre = "Mr", Nom = "Jacquard", Prenom = "Jules" },
new Client { Titre = "Melle", Nom = "Bistrou", Prenom = "Brigitte" }
};
foreach (Client client in clients)
{
context.Clients.Add(client);
}
// os médicos
Medecin[] medecins ={
new Medecin { Titre = "Mme", Nom = "Pelissier", Prenom = "Marie" },
new Medecin { Titre = "Mr", Nom = "Bromard", Prenom = "Jacques" },
new Medecin { Titre = "Mr", Nom = "Jandot", Prenom = "Philippe" },
new Medecin { Titre = "Melle", Nom = "Jacquemot", Prenom = "Justine" }
};
foreach (Medecin medecin in medecins)
{
context.Medecins.Add(medecin);
}
// os horários
Creneau[] creneaux ={
new Creneau{ Hdebut=8,Mdebut=0,Hfin=8,Mfin=20,Medecin=medecins[0]},
new Creneau{ Hdebut=8,Mdebut=20,Hfin=8,Mfin=40,Medecin=medecins[0]},
new Creneau{ Hdebut=8,Mdebut=40,Hfin=9,Mfin=0,Medecin=medecins[0]},
new Creneau{ Hdebut=9,Mdebut=0,Hfin=9,Mfin=20,Medecin=medecins[0]},
new Creneau{ Hdebut=9,Mdebut=20,Hfin=9,Mfin=40,Medecin=medecins[0]},
new Creneau{ Hdebut=9,Mdebut=40,Hfin=10,Mfin=0,Medecin=medecins[0]},
new Creneau{ Hdebut=10,Mdebut=0,Hfin=10,Mfin=20,Medecin=medecins[0]},
new Creneau{ Hdebut=10,Mdebut=20,Hfin=10,Mfin=40,Medecin=medecins[0]},
new Creneau{ Hdebut=10,Mdebut=40,Hfin=11,Mfin=0,Medecin=medecins[0]},
new Creneau{ Hdebut=11,Mdebut=0,Hfin=11,Mfin=20,Medecin=medecins[0]},
new Creneau{ Hdebut=11,Mdebut=20,Hfin=11,Mfin=40,Medecin=medecins[0]},
new Creneau{ Hdebut=11,Mdebut=40,Hfin=12,Mfin=0,Medecin=medecins[0]},
new Creneau{ Hdebut=14,Mdebut=0,Hfin=14,Mfin=20,Medecin=medecins[0]},
new Creneau{ Hdebut=14,Mdebut=20,Hfin=14,Mfin=40,Medecin=medecins[0]},
new Creneau{ Hdebut=14,Mdebut=40,Hfin=15,Mfin=0,Medecin=medecins[0]},
new Creneau{ Hdebut=15,Mdebut=0,Hfin=15,Mfin=20,Medecin=medecins[0]},
new Creneau{ Hdebut=15,Mdebut=20,Hfin=15,Mfin=40,Medecin=medecins[0]},
new Creneau{ Hdebut=15,Mdebut=40,Hfin=16,Mfin=0,Medecin=medecins[0]},
new Creneau{ Hdebut=16,Mdebut=0,Hfin=16,Mfin=20,Medecin=medecins[0]},
new Creneau{ Hdebut=16,Mdebut=20,Hfin=16,Mfin=40,Medecin=medecins[0]},
new Creneau{ Hdebut=16,Mdebut=40,Hfin=17,Mfin=0,Medecin=medecins[0]},
new Creneau{ Hdebut=17,Mdebut=0,Hfin=17,Mfin=20,Medecin=medecins[0]},
new Creneau{ Hdebut=17,Mdebut=20,Hfin=17,Mfin=40,Medecin=medecins[0]},
new Creneau{ Hdebut=17,Mdebut=40,Hfin=18,Mfin=0,Medecin=medecins[0]},
new Creneau{ Hdebut=8,Mdebut=0,Hfin=8,Mfin=20,Medecin=medecins[1]},
new Creneau{ Hdebut=8,Mdebut=20,Hfin=8,Mfin=40,Medecin=medecins[1]},
new Creneau{ Hdebut=8,Mdebut=40,Hfin=9,Mfin=0,Medecin=medecins[1]},
new Creneau{ Hdebut=9,Mdebut=0,Hfin=9,Mfin=20,Medecin=medecins[1]},
new Creneau{ Hdebut=9,Mdebut=20,Hfin=9,Mfin=40,Medecin=medecins[1]},
new Creneau{ Hdebut=9,Mdebut=40,Hfin=10,Mfin=0,Medecin=medecins[1]},
new Creneau{ Hdebut=10,Mdebut=0,Hfin=10,Mfin=20,Medecin=medecins[1]},
new Creneau{ Hdebut=10,Mdebut=20,Hfin=10,Mfin=40,Medecin=medecins[1]},
new Creneau{ Hdebut=10,Mdebut=40,Hfin=11,Mfin=0,Medecin=medecins[1]},
new Creneau{ Hdebut=11,Mdebut=0,Hfin=11,Mfin=20,Medecin=medecins[1]},
new Creneau{ Hdebut=11,Mdebut=20,Hfin=11,Mfin=40,Medecin=medecins[1]},
new Creneau{ Hdebut=11,Mdebut=40,Hfin=12,Mfin=0,Medecin=medecins[1]},
};
foreach (Creneau creneau in creneaux)
{
context.Creneaux.Add(creneau);
}
// os compromissos
context.Rvs.Add(new Rv { Jour = new System.DateTime(2012, 10, 8), Client = clients[0], Creneau = creneaux[0] });
// salvamos o contexto de persistência
context.SaveChanges();
}
}
}
}
- linha 10: abre-se o contexto de persistência;
- linhas 13-20: as linhas das tabelas [CLIENTS] e [MEDECINS] são inseridas no contexto e, em seguida, removidas dele. Acabamos de ver que isso esvaziava totalmente o banco de dados;
- linhas 22-88: são adicionados elementos ao contexto de persistência. Todos eles têm sua chave primária definida como null. Portanto, serão inseridos no banco de dados;
- linha 90: as alterações realizadas no contexto são sincronizadas com o banco de dados. Este será submetido a uma série de operações SQL DELETE, seguida por uma série de operações SQL INSERT;
Definimos o programa [Fill] como o novo objeto de inicialização do projeto [1] e, em seguida, executamos este último.
![]() |
Verifica-se no [2] que as tabelas foram preenchidas.
3.5.3. Exibição do conteúdo do banco de dados
Agora vamos exibir o conteúdo do banco de dados por meio da consulta LINQ to Entity. LINQ (Linguagem INtegrated Query) surgiu com o framework .NET 3.5 em 2007. Ele surge como uma extensão das linguagens .NET e c.a.d, às quais está integrado, e sua sintaxe é verificada pelo compilador. Ele permite consultar diferentes coleções com uma sintaxe que apresenta semelhanças com a linguagem SQL (Structured Query Language) de consulta a bancos de dados. Existem diferentes versões do LINQ:
- LINQ to Object, para consultar coleções em memória;
- LINQ to XML, para consultar o XML;
- LINQ para Entity, para consultar bancos de dados;
Para existir, o LINQ se baseia em diversas extensões feitas às linguagens .NET. Essas extensões podem ser utilizadas fora do LINQ. Não vamos apresentá-las, mas apenas indicar duas referências onde o leitor encontrará uma descrição detalhada do LINQ:
- LINQ in Action, de Fabrice Marguerie, Steve Eichert e Jim Wooley, publicado pela Manning;
- “LINQ pocket reference”, de Joseph e Ben Albahari, pela editora O’Reilly.
Li o primeiro e achei excelente. Não li o segundo, mas li, dos mesmos autores, “C# 3.0 in a nutshell” quando o LINQ foi lançado. Achei esse livro muito acima da média dos livros que costumo ler. Parece que os outros livros desses dois autores têm o mesmo nível. Além disso, vamos usar o LINQPad, uma ferramenta de aprendizado do LINQ escrita por Joseph Albahari.
Vamos exibir as entidades presentes no banco de dados. Para isso, adicionaremos às respectivas classes dois métodos de exibição. Comecemos pela entidade [Medecin]:
// um médico
public class Medecin
{
// data
[Key]
[Column("ID")]
public int? Id { get; set; }
[Required]
[MaxLength(5)]
[Column("TITRE")]
public string Titre { get; set; }
[Required]
[MaxLength(30)]
[Column("NOM")]
public string Nom { get; set; }
[Required]
[MaxLength(30)]
[Column("PRENOM")]
public string Prenom { get; set; }
// os horários disponíveis do médico
public ICollection<Creneau> Creneaux { get; set; }
[Column("TIMESTAMP")]
[Timestamp]
public byte[] Timestamp { get; set; }
// assinatura
public override string ToString()
{
return String.Format("Medecin[{0},{1},{2},{3},{4}]", Id, Titre, Prenom, Nom, dump(Timestamp));
}
// assinatura abreviada
public string ShortIdentity()
{
return ToString();
}
// utilitário
private string dump(byte[] timestamp){
string str = "";
foreach (byte b in timestamp)
{
str += b;
}
return str;
}
}
- linhas 27-30: o método ToString da classe. Observe que ele não exibe a coleção da linha 21;
- linhas 32-37: o método ShortIdentity, que faz a mesma coisa.
Precisamos explicar aqui os conceitos de Lazy e Eager Loading para avaliar o impacto dos dois métodos anteriores. Vimos que uma entidade pode ter dependências em relação a outra entidade. Essas dependências são de dois tipos:
- de um para vários, como acima, onde um médico está vinculado a vários horários;
- de vários para um, como na entidade [Creneau] abaixo, em que vários horários estão vinculados ao mesmo médico;
public class Creneau
{
// dados
...
[Required]
[Column("MEDECIN_ID")]
public int MedecinId { get; set; }
[Required]
[ForeignKey("MedecinId")]
public virtual Medecin Medecin { get; set; }
...
}
Quando as dependências são carregadas ao mesmo tempo que as entidades às quais estão vinculadas, fala-se em Eager Loading. Caso contrário, fala-se de Lazy Loading: as dependências só são carregadas quando são referenciadas pela primeira vez. Por padrão, o EF 5 utiliza o Lazy Loading: as dependências não são carregadas ao mesmo tempo que a entidade.
Vejamos nosso método [ToString] acima:
// horários de atendimento do médico
public ICollection<Creneau> Creneaux { get; set; }
// assinatura
public override string ToString()
{
return String.Format("Medecin[{0},{1},{2},{3},{4}]", Id, Titre, Prenom, Nom, dump(Timestamp));
}
// assinatura abreviada
public string ShortIdentity()
{
return ToString();
}
O método [ToString] não exibe a dependência [Creneaux] da linha 2. Se o tivesse feito, teria forçado o carregamento de todos os horários do médico antes de sua execução. Foi para evitar esse carregamento dispendioso que a dependência não foi incluída na assinatura da entidade. De modo geral, incluiremos duas assinaturas em cada entidade:
- um método ToString que exibirá a entidade e suas eventuais dependências, uma por uma. Conforme explicado acima, isso provocará o carregamento da dependência;
- um método ShortIdentity que não fará referência a nenhuma dependência. Portanto, não haverá carregamento de dependências;
Os métodos de exibição das demais entidades serão os seguintes:
A entidade [Client]:
public class Client
{
// dados
...
// as consultas do cliente
public ICollection<Rv> Rvs { get; set; }
// assinatura
public override string ToString()
{
return String.Format("Client[{0},{1},{2},{3},{4}]", Id, Titre, Prenom, Nom, dump(Timestamp));
}
// assinatura curta
public string ShortIdentity()
{
return ToString();
}
}
- linhas 9-12: o método [ToString] não exibe a dependência da linha 6;
A entidade [Creneau]:
public class Creneau
{
...
[Required]
[Column("MEDECIN_ID")]
public int MedecinId { get; set; }
[Required]
[ForeignKey("MedecinId")]
public virtual Medecin Medecin { get; set; }
// os horários do horário reservado
public ICollection<Rv> Rvs { get; set; }
// assinatura
public override string ToString()
{
return String.Format("Creneau[{0},{1},{2},{3},{4}, {5}]", Id, Hdebut, Mdebut, Hfin, Mfin, Medecin, dump(Timestamp));
}
// assinatura curta
public string ShortIdentity()
{
return String.Format("Creneau[{0},{1},{2},{3},{4}, {5}, {6}]", Id, Hdebut, Mdebut, Hfin, Mfin, Timestamp, MedecinId, dump(Timestamp));
}
}
- linha 16: o método [ToString] faz referência à dependência da linha 9. Isso forçará seu carregamento;
- linha 11: a dependência [Rvs] não é referenciada. Ela não será carregada;
- linhas 21-22: o método [ShortIdentity] não faz mais referência à referência [Medecin] da linha 9. Portanto, esta não será carregada.
A entidade [Rv]:
public class Rv
{
// dados
...
[Column("CLIENT_ID")]
public int ClientId { get; set; }
[ForeignKey("ClientId")]
[Required]
public virtual Client Client { get; set; }
[Column("CRENEAU_ID")]
public int CreneauId { get; set; }
[ForeignKey("CreneauId")]
[Required]
public virtual Creneau Creneau { get; set; }
// assinatura
public override string ToString()
{
return String.Format("Rv[{0},{1},{2},{3},{4}]", Id, Jour, Client, Creneau, dump(Timestamp));
}
// assinatura curta
public string ShortIdentity()
{
return String.Format("Rv[{0},{1},{2},{3},{4}]", Id, Jour, ClientId, CreneauId, dump(Timestamp));
}
}
- linhas 17-20: o método [ToString] faz referência às dependências das linhas 9 e 14. Isso forçará o carregamento delas;
- linhas 17-20: o método [ShortIdentity] evita isso e, portanto, as dependências não serão carregadas.
Concluindo, devemos prestar atenção aos métodos [ToString] das entidades. Se não prestarmos atenção a isso, exibir uma tabela pode carregar metade do banco de dados caso a tabela tenha muitas dependências.
Dito isso, escrevemos o novo código [Dump.cs] a seguir:
using RdvMedecins.Entites;
using RdvMedecins.Models;
using System;
using System.Linq;
namespace RdvMedecins_01
{
class Dump
{
static void Main(string[] args)
{
// dump do banco de dados
using (var context = new RdvMedecinsContext())
{
// clientes
Console.WriteLine("Clients--------------------------------------");
var clients = from client in context.Clients select client;
foreach (Client client in clients)
{
Console.WriteLine(client);
}
// os médicos
Console.WriteLine("Médecins--------------------------------------");
var medecins = from medecin in context.Medecins select medecin;
foreach (Medecin medecin in medecins)
{
Console.WriteLine(medecin);
}
// horários disponíveis
Console.WriteLine("Créneaux horaires--------------------------------------");
var creneaux = from creneau in context.Creneaux select creneau;
foreach (Creneau creneau in creneaux)
{
Console.WriteLine(creneau);
}
// as consultas
Console.WriteLine("Rendez-vous--------------------------------------");
var rvs = from rv in context.Rvs select rv;
foreach (Rv rv in rvs)
{
Console.WriteLine(rv);
}
}
}
}
}
Vamos explicar as linhas 17 a 21, que exibem as entidades [Client]. A explicação fornecida se aplica às demais entidades.
// clientes
Console.WriteLine("Clients--------------------------------------");
var clients = from client in context.Clients select client;
foreach (Client client in clients)
{
Console.WriteLine(client);
}
- linha 3: a palavra-chave var foi introduzida no C# 3.0. Ela permite evitar a indicação do tipo específico de uma variável. O compilador, então, deduz esse tipo a partir do tipo da expressão atribuída à variável;
- linha 3: a expressão atribuída à variável clients é uma consulta LINQ to Entity. Nela, reconhecem-se palavras-chave da linguagem SQL incorporadas no LINQ. A sintaxe utilizada aqui é a seguinte:
from variable in DbSet select variable
Uma sintaxe mais geral de LINQ é
from variable in collection select variable
A coleção será percorrida e, para cada elemento dela, a variável será avaliada. Isso só ocorre quando a variável [clients] da linha 3 for enumerada pelo for / each das linhas 4 a 7. Enquanto isso não ocorrer, a variável [clients] é apenas uma consulta não avaliada;
- linha 4: a consulta [clients] é iterada. Isso forçará a avaliação da consulta. As linhas da tabela [CLIENTS] serão trazidas, uma a uma, para o contexto de persistência;
- linha 6: o método [ToString] da entidade [Client] é utilizado para a exibição. Não há carregamento de dependências;
Vamos passar para as próximas linhas do código:
- linhas 24-28: as linhas da tabela [MEDECINS] são inseridas no contexto de persistência e exibidas. Não há carregamento de dependências;
- linhas 31-35: as linhas da tabela [CRENEAUX] são inseridas no contexto de persistência e exibidas. Vimos que o método [ToString] dessa entidade exibia a dependência [Medecin]. No entanto, essa dependência já está carregada. Portanto, não haverá um novo carregamento;
- linhas 38-42: as linhas da tabela [RVS] são inseridas no contexto de persistência e exibidas. Vimos que o método [ToString] dessa entidade exibia as dependências [Client] e [Creneau]. No entanto, essas já estão carregadas. Portanto, não haverá novos carregamentos.
Observe-se que a ordem de exibição não é neutra. Se quiséssemos exibir primeiro as entidades [Rv], o método [ToString] dessa entidade teria provocado o carregamento das entidades [Client] e [Creneau] vinculadas a esses compromissos. As demais não teriam sido carregadas. Elas teriam sido carregadas posteriormente, em outra exibição. Isso afeta o desempenho. O código anterior precisa de quatro comandos SQL para exibir todas as entidades. Suponhamos agora que se utilize primeiro a tabela [RVS] de compromissos. É necessária uma primeira consulta SQL para a tabela [RVS]. Em seguida, o método [ToString] da entidade [Rv] provocará o possível carregamento das entidades [Client] e [Creneau] associadas. É necessária uma consulta SQL para cada uma delas. Supondo que haja N2 clientes e N3 horários e que todas essas entidades estejam referenciadas na tabela [RVS], a exibição dessa tabela exigirá 1 + N2 + N3 consultas SQL. Portanto, o desempenho é inferior ao da versão analisada. Para exibir a tabela [RVS] com suas dependências, seria necessária uma junção entre tabelas. É possível realizá-la com a tabela LINQ. Voltaremos a esse assunto com um exemplo. Por enquanto, vamos lembrar que devemos prestar atenção às consultas SQL subjacentes ao nosso código LINQ.
Configuramos o projeto para executar esses novos códigos [1] e [2] e, em seguida, os executamos:
![]() |
A exibição no console é a seguinte:
3.5.4. Aprendizado do LINQ com o LINQPad
Utilizamos acima as consultas LINQ to Entity para exibir o conteúdo das tabelas do banco de dados. Joseph Albahari escreveu um programa para aprender as diferentes formas de LINQ. Apresentamos-no agora.
O LINQPad está disponível no URL seguinte ao [http://www.linqpad.net/]. Após a instalação, executamos o [1]:
![]() |
O iniciante LINQ poderá se familiarizar com os exemplos da aba [Samples] [2], que apresentam inúmeros exemplos. Vamos selecionar o exemplo [3], que será exibido em outra janela [4]. O código completo do exemplo é o seguinte:
// Agora, uma expressão de consulta simples do LINQ-to-objects (observe que não há ponto-e-vírgula):
from word in "The quick brown fox jumps over the lazy dog".Split()
orderby word.Length
select word
// Fique à vontade para editar isso... (ninguém está olhando!) Você será solicitado a salvar quaisquer
// alterações em um arquivo separado.
//
// Dica: Você pode executar parte de uma consulta selecionando-a e, em seguida, pressionando F5.
As linhas 3 a 5 são um exemplo de consulta LINQ to Object. A consulta LINQ segue a sintaxe:
from variable in collection orderby élément1 select élément2
- A variável designa o elemento atual da coleção. No nosso exemplo, essa coleção é a lista de palavras resultantes da string dividida;
- a coleção é ordenada de acordo com o parâmetro élément1 de
</span>**<span style="color: #000000">orderby**. No nosso exemplo, a coleção de palavras será ordenada de acordo com seu comprimento; - a palavra-chave
selectindica o que queremos extrair do elemento atual</span>*<span style="color: #000000">variable*da coleção. No nosso exemplo, será a palavra.
Vamos executar essa consulta LINQ:
![]() |
- em [1]: uma expressão LINQ é executada por [F5] ou por meio do botão de execução;
- em [2]: a exibição. As palavras são exibidas na ordem de seu comprimento. Este exemplo simples mostra o poder do LINQ;
- no [3], é possível baixar outros exemplos, principalmente os do livro “LINQ in action” [4];
![]() |
- em [5], escolhemos um exemplo do livro;
string[] words = { "hello", "wonderful", "linq", "beautiful", "world" };
// Agrupe as palavras por comprimento
var groups =
from word in words
orderby word ascending
group word by word.Length into lengthGroups
orderby lengthGroups.Key descending
select new { Length = lengthGroups.Key, Words = lengthGroups };
// Imprimir cada grupo
foreach (var group in groups)
{
Console.WriteLine("Words of length " + group.Length);
foreach (string word in group.Words)
Console.WriteLine(" " + word);
}
- linha 4: uma nova consulta LINQ com novas palavras-chave;
- linha 5: a coleção solicitada é a tabela de palavras da linha 1;
- linha 6: a coleção é classificada em ordem alfabética das palavras;
- linha 7: a coleção é agrupada (palavra-chave into) em uma nova coleção lengthGroups. lengthGroups.Key representa o fator de agrupamento (palavra-chave by), neste caso, o comprimento das palavras. lengthGroups reúne as palavras com o mesmo fator de agrupamento, portanto, o mesmo comprimento;
- linha 8: a coleção lengthGroups é ordenada por chave de agrupamento em ordem decrescente, ou seja, neste caso, pelo tamanho decrescente das palavras;
- linha 9: a partir dessa coleção, são gerados novos objetos (classes anônimas) com dois campos:
- Length: o comprimento das palavras,
- Words: as palavras com esse comprimento;
Aqui, percebe-se claramente a utilidade da palavra-chave var da linha 4. Como utilizamos uma classe anônima na linha 9, não é possível especificar o tipo da variável </span>*<span style="color: #000000">groups. O compilador, por sua vez, atribuirá um nome interno à classe anônima e, com base nele, tipificará a variável groups. Ele será capaz, então, de determinar se a variável groups *está sendo utilizada corretamente
- linha 12: processamento da consulta da linha 4. Só nesse momento ela é avaliada. Vale lembrar que sua execução produzirá uma coleção de objetos, especificados na linha 9;
- linha 14: exibe-se a propriedade Length do elemento atual, ou seja, o comprimento das palavras;
- linhas 15-17: exibe-se cada elemento da coleção da propriedade Words, ou seja, o conjunto de palavras com o comprimento exibido anteriormente.
Ao executarmos essa consulta, obtemos o seguinte resultado em LINQPad:
![]() |
Agora que vimos alguns exemplos de consultas [LINQ to Object], vamos examinar as consultas [LINQ to Entity] que nos permitirão consultar bancos de dados. Primeiramente, vamos nos conectar ao banco de dados SQL Server que criamos e preenchemos:
![]() |
- em [1], adicionamos uma conexão a um banco de dados;
- em [2], os meios de acesso à fonte de dados. Para acessar o banco de dados SQL Server, utilizaremos [LINQPad Driver];
- no [3], também é possível recuperar um contexto de persistência [DbContext] definido em um arquivo .exe ou .dll assembly (opção 3). Infelizmente, até o momento (8 de outubro de 2012), o Entity Framework 5 não é compatível;
- no [4], é possível baixar drivers para outros SGBD além do SQL Server;
- no [5], baixaremos o driver para os SGBD, MySQL e Oracle;
![]() |
- no [6], o driver baixado;
- em [7], conectamos a um servidor SQL;
![]() |
- em [8], o banco de dados está no servidor de nomes (local);
- em [9], conectamo-nos com a autenticação sa / sqlserver2012;
- em [10], no banco de dados [rdvmedecins-ef] que criamos;
- em [11], é possível testar a conexão;
- no [12], encerra-se o assistente;
- em [13], a conexão aparece em LINQPad.
As entidades foram criadas a partir da tabela [rdvmedecins-ef]. São as seguintes:
![]() |
- em [1], [CLIENTS] representa o conjunto de entidades [Client]. Cada entidade possui:
- as propriedades (ID, TITRE, NOM, PRENOM, TIMESTAMP),
- uma relação de 1 para vários [CLIENTRVS];
- em que [2] e [CRENEAUXes] representam o conjunto das entidades [Creneau]. Cada entidade possui:
- as propriedades (ID, HDEBUT, MDEBUT, HFIN, MFIN, MEDECIN_ID, TIMESTAMP),
- uma relação de 1 para vários [CRENEAURVS],
- uma relação de muitos para 1 [MEDECIN];
- em [3], a entidade [MEDECINS] representa o conjunto das entidades [Medecin]. Cada entidade possui:
- as propriedades (ID, TITRE, NOM, PRENOM, TIMESTAMP),
- uma relação de 1 para vários [MEDECINCRENEAUXes];
- em [4], a entidade [RVS] representa o conjunto das entidades [Rv]. Cada entidade possui:
- as propriedades (ID, JOUR, CLIET_ID, CRENEAU_ID, TIMESTAMP),
- uma relação de muitos para um com [CLIENT],
- uma relação muitos-para-um [CRENEAU].
Observe que os nomes das propriedades acima são diferentes dos nomes que usamos até agora. Isso não importa. Queremos apenas aprender os princípios básicos de consulta em bancos de dados.
Vamos ver como podemos consultar essa base de entidades. Por exemplo, queremos a lista de médicos ordenada por seus TITRE e NOM:
![]() |
- em [1], criamos uma nova consulta;
- em [2], o texto da consulta;
![]() |
- em [3], o resultado da consulta;
- em [4], a mesma consulta com expressões lambda. Uma consulta com expressões lambda é menos legível do que uma consulta de texto, e talvez seja preferível evitá-las. No entanto, às vezes elas são indispensáveis, pois permitem certas coisas que as consultas de texto não permitem. Uma expressão lambda designa uma função com um parâmetro de entrada a e um parâmetro de saída b, na forma a=>b. O método OrderBy acima aceita uma função lambda como único parâmetro. Essa função fornece a ele o parâmetro segundo o qual uma coleção deve ser ordenada. Assim, MEDECINS.OrderBy(m=>m.TITRE) é a lista de médicos ordenada pelos títulos. É preciso interpretar a instrução como um pipeline sobre uma coleção. A coleção de médicos é fornecida como entrada para o método OrderBy. Este irá processar as entidades [Medecin] uma a uma. Na expressão lambda m=>m.TITRE, m representa a entrada da função lambda. É possível nomeá-la como se desejar. Aqui, a entrada da função lambda será uma entidade [Medecin]. A função m=>m.TITRE pode ser interpretada da seguinte forma: se eu chamar m de minha entrada (uma entidade [Medecin]), então minha saída será m.TITRE, ou seja, o título do médico. MEDECINS.OrderBy(m=>m.TITRE) é, por sua vez, uma coleção: a coleção de médicos ordenada por títulos. Essa nova coleção pode alimentar outro método; no exemplo, o método ThenBy. Esse método funciona com o mesmo princípio. Ele serve para indicar parâmetros adicionais para a ordenação da coleção.
Ler o código lambda equivalente ao código de texto que costumamos digitar é uma boa maneira de aprender;
![]() |
- em [5], a ordem SQL emitida no banco de dados. Mais uma vez, devemos ler esse código com atenção. Ele permite avaliar o custo real de uma consulta LINQ.
A seguir, apresentamos alguns exemplos de consultas LINQ. Em cada caso, mostramos os resultados exibidos e os códigos lambda e SQL equivalentes. Para compreender essas consultas, é preciso lembrar as relações “muitos para um” que conectam as entidades entre si. É por meio delas que navegamos de uma entidade para outra. Elas são chamadas de propriedades de navegação.
![]() |
// os clientes cujo título é “Sr.”, classificados em ordem decrescente de nomes
Resultados:
![]() |
LINQ | |
Lambda | |
SQL | |
// todos os horários disponíveis com o médico correspondente
Resultados (parciais):
![]() |
LINQ | |
Lambda | ![]() |
SQL | |
// todas as consultas com o cliente e o médico associados
Resultados:
![]() |
LINQ | |
Lambda | ![]() |
SQL | |
// médicos sem consultas agendadas
Resultados:
![]() |
LINQ | |
Lambda | ![]() |
SQL | |
Não existe uma consulta LINQ para essa solicitação. É preciso utilizar expressões lambda. Esta é interpretada da seguinte forma: pego a coleção de médicos (MEDECINS) e retenho (Where) apenas os médicos (m) para os quais não consigo encontrar, na coleção de consultas (RVS), uma consulta (rv) com esse médico (m).
// horários disponíveis da Sra. Pélissier
Resultados (parciais):
![]() |
LINQ | |
Lambda | ![]() |
SQL | |
// Número de consultas da Sra. Pélissier em 08/10/2012
Resultados:
![]() |
LINQ | |
Lambda | |
SQL | |
// lista de clientes que marcaram consulta com a Sra. Pélissier em 08/10/2012
Resultados:
![]() |
LINQ | |
Lambda | ![]() |
SQL | |
// número de horários por médico
Resultados:
![]() |
LINQ | |
Lambda | ![]() |
SQL | |
3.5.5. Modificação de uma entidade vinculada ao contexto de persistência
Vimos as seguintes operações no contexto de persistência:
- adicionar um elemento ao contexto ([dbContext].[DbSet].Add);
- remover um elemento do contexto ([dbContext].[DbSet].Remove);
- consultar um contexto com consultas LINQ.
Quando se deseja sincronizar o contexto com o banco de dados, escreve-se [dbContext].SaveChanges().
![]() | ![]() |
O código [ModifyAttachedEntity] ilustra a modificação de uma entidade vinculada ao contexto:
using System;
using System.Data;
using System.Linq;
using RdvMedecins.Entites;
using RdvMedecins.Models;
namespace RdvMedecins_01
{
class ModifyAttachedEntity
{
static void Main(string[] args)
{
Client client1, client2, client3;
// 1º contexto
using (var context = new RdvMedecinsContext())
{
// Esvaziar o banco de dados atual
foreach (var client in context.Clients)
{
context.Clients.Remove(client);
}
foreach (var medecin in context.Medecins)
{
context.Medecins.Remove(medecin);
}
// adicionar um cliente
client1 = new Client { Nom = "xx", Prenom = "xx", Titre = "xx" };
context.Clients.Add(client1);
// acompanhamento
Console.WriteLine("client1--avant");
Console.WriteLine(client1);
// salvar contexto
context.SaveChanges();
// acompanhamento
Console.WriteLine("client1--après");
Console.WriteLine(client1);
}
// 2º contexto
using (var context = new RdvMedecinsContext())
{
// recuperando o cliente1 no cliente2
client2 = context.Clients.Find(client1.Id);
// acompanhamento
Console.WriteLine("client2");
Console.WriteLine(client2);
// modificação do cliente2
client2.Nom = "yy";
// salvar contexto
context.SaveChanges();
}
// 3º contexto
using (var context = new RdvMedecinsContext())
{
// recuperando o cliente2 no cliente3
client3 = context.Clients.Find(client2.Id);
// acompanhamento
Console.WriteLine("client3");
Console.WriteLine(client3);
}
}
}
}
- linha 15: abertura do contexto do aplicativo;
- linhas 18-25: o contexto é esvaziado. Mais precisamente, todas as entidades são trazidas do banco de dados para o contexto e, em seguida, passam para o estado “excluído”. Observe-se que, nesta fase, o banco de dados não sofreu alterações. Enquanto o contexto não estiver sincronizado com o banco de dados, este não sofre alterações. Lembramos que basta excluir as entidades [Medecin] e [Client] para esvaziar o banco de dados por meio do mecanismo de exclusões em cascata;
- linhas 27-28: um novo cliente é adicionado ao banco de dados;
- linhas 30-31: ele é exibido antes de ser salvo no banco de dados;
- linha 33: sincroniza-se o contexto com o banco de dados. As entidades marcadas como “excluídas” serão submetidas a uma operação SQL DELETE, a entidade adicionada será submetida a uma operação SQL INSERT;
- linhas 35-36: exibe-se o cliente após a sincronização com o banco de dados;
O resultado obtido no console é o seguinte:
Observe os seguintes pontos:
- antes da sincronização com o banco de dados, o cliente não possui nem chave primária, nem timestamp,
- após a sincronização, ele os possui. Vale lembrar aqui que a chave primária foi configurada para ser gerada pelo SQL Server. Da mesma forma, esse SGBD gera automaticamente o timestamp;
- linha 37: o contexto de persistência é fechado. As entidades que ele continha tornam-se “desvinculadas”. Elas existem como objetos, mas não como entidades vinculadas a um contexto de persistência;
- linha 39: inicia-se um novo contexto vazio;
- linha 42: o cliente é recuperado diretamente do banco de dados por meio de sua chave primária. Ele é então inserido no contexto. Se não for encontrado, o método Find retorna o ponteiro null;
- linhas 48-49: ele é exibido;
Isso resulta no seguinte:
- linha 47: ele é modificado;
- linha 49: sincroniza-se o contexto com o banco de dados. EF detectará que alguns elementos do contexto foram modificados desde que foram inseridos nele. Para esses elementos, ele irá gerar ordens SQL e UPDATE no banco de dados. Portanto, neste caso, a sincronização consistirá em uma única ordem UPDATE;
- linha 50: o segundo contexto é fechado. A entidade client2, que estava vinculada ao contexto, passa a ser desvinculada dele;
- linha 52: abre-se um terceiro contexto vazio;
- linha 55: traz-se de volta o único cliente do banco de dados. Queremos verificar se a modificação feita nele no contexto anterior foi refletida no banco de dados;
- linhas 57-58: exibe-se o cliente. Isso resulta no seguinte:
O nome do cliente foi efetivamente alterado no banco de dados. É interessante notar que seu timestamp foi atualizado.
- linha 59: fecha-se o contexto. A propósito, observe-se que, ao contrário das duas vezes anteriores, não foi necessário sincronizar previamente o contexto com o banco de dados (SaveChanges), pois o contexto não havia sido alterado.
3.5.6. Gerenciamento de entidades destacadas
Voltemos à arquitetura em camadas de um aplicativo como o do estudo de caso:
![]() |
A camada [DAO] utiliza as camadas ORM e EF5 para acessar os dados. Temos os blocos básicos dessa camada. Cada método abrirá um contexto de persistência, realizará nele as operações necessárias (inserção, modificação, exclusão, consulta) e, em seguida, o fechará. As entidades gerenciadas pela camada [DAO] serão encaminhadas até a camada web ASP.NET. Nessa camada, elas estão fora do contexto de persistência e, portanto, desassociadas. Na camada web, um usuário pode modificar essas entidades (adição, modificação, exclusão). Quando retornam à camada [DAO], elas ainda estão desanexadas. No entanto, a camada [DAO] precisará refletir as alterações feitas pelo usuário no banco de dados. Portanto, ela precisará trabalhar com entidades desanexadas. Vejamos os três casos possíveis:
Adicionar uma entidade desanexada
Esse é o caso normal para uma adição. Basta adicionar (Add) a entidade destacada ao contexto, certificando-se de que ela tenha uma chave primária igual a null.
Modificar uma entidade destacada
Pode-se usar o código a seguir:
- o método [DbContext].Entry(entidade-separada) colocará a entidade no contexto;
- o status dessa entidade é definido como “modificado” para que ela seja objeto de uma ordem SQL UPDATE.
Excluir uma entidade destacada
É possível utilizar o código a seguir:
- linha 1: insere-se no contexto a entidade com a mesma chave primária que a entidade destacada;
- linha 2: exclui-se a entidade:
Observe-se que isso requer, como base, um SELECT seguido por um DELETE, enquanto normalmente basta apenas o DELETE. Também é possível seguir o exemplo da modificação de uma entidade separada e escrever:
Como não consegui implementar logs nas operações SQL realizadas no banco de dados, não sei se um método é mais recomendável do que o outro.
Aqui está um exemplo:
![]() | ![]() |
O código do programa [ModifyDetachedEntities] é o seguinte:
using System;
using System.Data;
using RdvMedecins.Entites;
using RdvMedecins.Models;
namespace RdvMedecins_01
{
class ModifyDetachedEntities
{
static void Main(string[] args)
{
Client client1;
// esvaziamos o banco de dados atual
Erase();
// adiciona-se um cliente
using (var context = new RdvMedecinsContext())
{
// criação de cliente
client1 = new Client { Titre = "x", Nom = "x", Prenom = "x" };
// adição do cliente ao contexto
context.Clients.Add(client1);
// salvando o contexto
context.SaveChanges();
}
// exibição do banco de dados
Dump("1-----------------------------");
// cliente1 não está no contexto — ele é modificado
client1.Nom = "y";
// novo contexto
using (var context = new RdvMedecinsContext())
{
// aqui, temos um contexto vazio
// colocamos o cliente1 no contexto em um estado modificado
context.Entry(client1).State = EntityState.Modified;
// salvamos o contexto
context.SaveChanges();
}
// exibição básica
Dump("2-----------------------------");
// exclusão de entidade fora do contexto
using (var context = new RdvMedecinsContext())
{
// aqui, temos um novo contexto vazio
// colocamos o cliente1 no contexto em um estado excluído
context.Entry(client1).State = EntityState.Deleted;
// salvamos o contexto
context.SaveChanges();
}
// exibição do banco de dados
Dump("3-----------------------------");
}
static void Erase()
{
// esvazia a base
using (var context = new RdvMedecinsContext())
{
foreach (var client in context.Clients)
{
context.Clients.Remove(client);
}
foreach (var medecin in context.Medecins)
{
context.Medecins.Remove(medecin);
}
// salva-se o contexto
context.SaveChanges();
}
}
static void Dump(string str)
{
Console.WriteLine(str);
// exibe a base
using (var context = new RdvMedecinsContext())
{
foreach (var rv in context.Rvs)
{
Console.WriteLine(rv);
}
foreach (var creneau in context.Creneaux)
{
Console.WriteLine(creneau);
}
foreach (var client in context.Clients)
{
Console.WriteLine(client);
}
foreach (var medecin in context.Medecins)
{
Console.WriteLine(medecin);
}
}
}
}
}
- linha 15: o banco de dados é apagado;
- linhas 17-25: um cliente é adicionado ao banco de dados;
- linha 27: exibe o conteúdo do banco de dados;
- após a linha 25, o contexto de persistência não existe mais. Portanto, não há mais entidades vinculadas. A entidade client1 passou para o estado “desvinculado”;
- linha 29: altera-se o nome da entidade desanexada;
- linha 31: abre-se um novo contexto vazio;
- linha 35: a entidade desanexada client1 é colocada no contexto no estado “modificado”;
- linha 37: o contexto é sincronizado com o banco de dados;
- linha 38: ele é fechado;
- linha 40: o banco de dados é exibido;
O nome do cliente foi alterado corretamente no banco de dados. Observe que o timestamp foi atualizado;
- linha 42: abertura de um novo contexto vazio;
- linha 46: a entidade destacada client1 é inserida no contexto no estado “excluída”;
- linha 48: o contexto é sincronizado com o banco de dados;
- linha 49: ele é fechado;
- linha 51: o banco de dados é exibido;
A entidade foi efetivamente excluída do banco de dados.
Agora, veremos os dois modos de carregamento das dependências de uma entidade: Lazy e Eager Loading.
3.5.7. Carregamento Lazy e Eager
Vamos retomar o esquema de dependências “muitos para um” de uma de nossas quatro entidades:
![]() |
Acima, a entidade [Creneau] possui uma propriedade de navegação [Creneau.Medecin] direcionada à entidade [Medecin]. Isso é chamado de dependência. Vimos que também existem dependências de um para vários. O princípio que será explicado se aplica igualmente a elas.
Por padrão, EF 5 está no modo Lazy Loading: quando traz uma entidade do banco de dados para o contexto de persistência, não traz suas dependências. Estas serão carregadas quando forem utilizadas pela primeira vez. Essa é uma medida de bom senso. Se não fosse assim, trazer os compromissos para o contexto implicaria, de acordo com as dependências acima:
- as entidades [Creneau] vinculadas aos compromissos;
- as entidades [Medecin] relacionadas a esses horários;
- as entidades [Clients] vinculadas aos compromissos.
Às vezes, porém, precisamos de uma entidade e de suas dependências. Vamos ilustrar os dois modos de carregamento.
![]() | ![]() |
O código de [LazyEagerLoading] é o seguinte:
using RdvMedecins.Entites;
using RdvMedecins.Models;
using System;
using System.Linq;
namespace RdvMedecins_01
{
class LazyEagerLoading
{
// as entidades
static Medecin[] medecins;
static Client[] clients;
static Creneau[] creneaux;
static void Main(string[] args)
{
// inicializa a base
InitBase();
Console.WriteLine("Initialisation terminée");
// carregamento antecipado
Creneau creneau;
int idCreneau = (int)creneaux[0].Id;
using (var context = new RdvMedecinsContext())
{
// intervalo n.º 0
creneau = context.Creneaux.Include("Medecin").Single<Creneau>(c => c.Id == idCreneau);
Console.WriteLine(creneau.ShortIdentity());
}
// exibição de dependências
try
{
Console.WriteLine("Médecin={0}", creneau.Medecin);
}
catch (Exception e)
{
Console.WriteLine("L'erreur 1 suivante s'est produite : {0}", e);
}
// carregamento diferido – modo padrão
using (var context = new RdvMedecinsContext())
{
// intervalo n.º 0
creneau = context.Creneaux.Single<Creneau>(c => c.Id == idCreneau);
Console.WriteLine(creneau.ShortIdentity());
}
// exibição de dependência
try
{
Console.WriteLine("Médecin={0}", creneau.Medecin);
}
catch (Exception e)
{
Console.WriteLine("L'erreur 2 suivante s'est produite : {0}", e);
}
}
static void InitBase()
{
// inicialização do banco de dados
using (var context = new RdvMedecinsContext())
{
// esvaziando o banco de dados atual
...
// inicializando o banco de dados
// os clientes
clients = new Client[] {
new Client { Titre = "Mr", Nom = "Martin", Prenom = "Jules" },
new Client { Titre = "Mme", Nom = "German", Prenom = "Christine" },
new Client { Titre = "Mr", Nom = "Jacquard", Prenom = "Jules" },
new Client { Titre = "Melle", Nom = "Bistrou", Prenom = "Brigitte" }
};
...
// os compromissos
context.Rvs.Add(new Rv { Jour = new System.DateTime(2012, 10, 8), Client = clients[0], Creneau = creneaux[0] });
// salva-se o contexto de persistência
context.SaveChanges();
}
}
}
}
- linha 18: partimos de uma base conhecida, a que foi utilizada até agora. Após essa operação, as tabelas das linhas 11 a 13 são preenchidas com entidades isoladas;
- linhas 21-22: analisa-se o primeiro horário e o médico associado;
- linha 23: novo contexto;
- linha 26: insere-se o horário no contexto com sua dependência (eager loading). Como esse não é o modo padrão, é necessário solicitar explicitamente essa dependência. O método Include permite fazer isso. Seu parâmetro é o nome da dependência na entidade trazida para o contexto. A consulta que traz a entidade para o contexto utiliza expressões lambda. O método Single permite especificar uma condição para retornar uma única entidade. Aqui, busca-se no banco de dados a entidade [Creneau], que possui a chave primária do horário nº 0;
- linha 27: exibe-se a entidade recuperada. Vale lembrar os dois métodos de gravação utilizados nas entidades:
// assinatura
public override string ToString()
{
return String.Format("Creneau[{0},{1},{2},{3},{4}, {5},{6}]", Id, Hdebut, Mdebut, Hfin, Mfin, Medecin, dump(Timestamp));
}
// assinatura curta
public string ShortIdentity()
{
return String.Format("Creneau[{0},{1},{2},{3},{4}, {5}, {6}]", Id, Hdebut, Mdebut, Hfin, Mfin, MedecinId, dump(Timestamp));
}
- linhas 2-5: o método [ToString] exibe a dependência [Medecin]. Se esta ainda não estiver no contexto, será pesquisada no banco de dados para ser inserida nele;
- linhas 8-11: o método [ShortIdentity] não exibe a dependência [Medecin]. Portanto, ela não será pesquisada no banco de dados se não estiver no contexto;
Nesta fase, a exibição no console é a seguinte:
- linha 28: o contexto é fechado;
- linhas 30-37: tenta-se gravar a dependência [Medecin] da entidade. Vale lembrar o funcionamento do Lazy Loading: uma dependência é carregada na primeira vez em que é utilizada, caso não esteja presente. Aqui, normalmente ela está presente. A exibição é a seguinte:
- linhas 39-44: em um novo contexto, o intervalo nº 0 é novamente buscado no banco de dados e trazido para o contexto. Aqui, a dependência [Medecin] não é solicitada explicitamente. Portanto, ela não será carregada (Lazy Loading);
- linha 43: a exibição da identidade curta do intervalo é a seguinte:
Aqui, é importante usar ShortIdentity em vez de ToString para exibir a entidade. Se for utilizado ToString, a dependência [Medecin] será exibida e, para isso, será buscada no banco de dados. No entanto, não é isso que queremos.
- linha 44: o contexto é fechado;
- linhas 46-53: tenta-se exibir a dependência da entidade. É importante fazer isso fora do contexto; caso contrário, ela será pesquisada no banco de dados e encontrada. Aqui, estamos fora do contexto. A entidade [Creneau] está desanexada e sua dependência [Medecin] está ausente (Lazy Loading). O que vai acontecer? A exibição na tela é a seguinte:
EF detectou que a dependência [Medecin] estava ausente. Tentou carregá-la, mas, como o contexto estava fechado, essa operação não foi mais possível. Vamos registrar essa exceção [System.ObjectDisposedException], pois ela é característica do carregamento de uma dependência fora de um contexto aberto.
Agora, vamos examinar a concorrência de acesso às entidades.
3.5.8. Concorrência de acesso às entidades
Voltemos à definição da entidade [Client]:
public class Client
{
// dados
[Key]
[Column("ID")]
public int? Id { get; set; }
[Required]
[MaxLength(5)]
[Column("TITRE")]
public string Titre { get; set; }
[Required]
[MaxLength(30)]
[Column("NOM")]
public string Nom { get; set; }
[Required]
[MaxLength(30)]
[Column("PRENOM")]
public string Prenom { get; set; }
// os Rvs do cliente
public ICollection<Rv> Rvs { get; set; }
[Column("TIMESTAMP")]
[Timestamp]
public byte[] Timestamp { get; set; }
// assinatura
...
}
Vamos nos concentrar no campo [Timestamp] da linha 23. Sabemos que seu valor é gerado pelo SGBD. Também mencionamos que a anotação [Timestamp] da linha 22 fazia com que EF 5 utilizasse o campo anotado para gerenciar a concorrência de acesso às entidades. Vamos relembrar o que é o gerenciamento de concorrência de acesso:
- um processo P1 lê uma linha L da tabela [MEDECINS] no momento T1. A linha possui o timestamp TS1;
- um processo P2 lê a mesma linha L da tabela [MEDECINS] no momento T2. A linha possui os valores timestamp e TS1 porque o processo P1 ainda não validou sua modificação;
- o processo P1 valida sua modificação na linha L. O timestamp da linha L passa então para TS2;
- o processo P2 valida sua modificação na linha L. OORM lança, então, uma exceção, pois o processo P2 possui um timestamp TS1 da linha L diferente do timestamp TS2 encontrado no banco de dados.
Isso é chamado de gerenciamento otimista de acessos concorrentes. Com EF 5, um campo que desempenha essa função deve ter um dos dois atributos: [Timestamp] ou [ConcurrencyCheck]. O servidor SQL possui um tipo [timestamp]. O valor de uma coluna com esse tipo é gerado automaticamente pelo servidor SQL a cada inserção ou modificação de uma linha. Essa coluna pode, então, ser utilizada para gerenciar a concorrência de acesso.
Vamos ilustrar essa concorrência de acesso com duas threads que modificarão, ao mesmo tempo, uma mesma entidade [Client] no banco de dados. O projeto evolui da seguinte forma:
![]() | ![]() |
O código do programa [AccèsConcurrents] é o seguinte:
using System;
using System.Data;
using System.Linq;
using System.Threading;
using RdvMedecins.Entites;
using RdvMedecins.Models;
namespace RdvMedecins_01
{
// objeto trocado com os threads
class Data
{
public int Duree { get; set; }
public string Nom { get; set; }
public Client Client { get; set; }
}
// programa de teste
class AccèsConcurrents
{
static void Main(string[] args)
{
Client client1;
using (var context = new RdvMedecinsContext())
{
// thread principal
Thread.CurrentThread.Name = "main";
// esvazia-se o banco de dados atual
foreach (var client in context.Clients)
{
context.Clients.Remove(client);
}
foreach (var medecin in context.Medecins)
{
context.Medecins.Remove(medecin);
}
// adicionando um cliente
client1 = new Client { Nom = "xx", Prenom = "xx", Titre = "xx" };
context.Clients.Add(client1);
// acompanhamento
Console.WriteLine("{0} client1--avant sauvegarde du contexte", Thread.CurrentThread.Name);
Console.WriteLine(client1.ShortIdentity());
// salvamento
context.SaveChanges();
// acompanhamento
Console.WriteLine("{0} client1--après sauvegarde du contexte", Thread.CurrentThread.Name);
Console.WriteLine(client1.ShortIdentity());
}
// vamos modificar o cliente1 com duas threads
// thread t1
Thread t1 = new Thread(Modifie);
t1.Name = "t1";
t1.Start(new Data { Duree = 5000, Nom = "yy", Client = client1 });
// thread t2
Thread t2 = new Thread(Modifie);
t2.Name = "t2";
t2.Start(new Data { Duree = 5000, Nom = "zz", Client = client1 });
// aguardamos o término das duas threads
Console.WriteLine("Thread {0} -- début attente fin des deux threads", Thread.CurrentThread.Name);
t1.Join();
t2.Join();
Console.WriteLine("Thread {0} -- fin attente fin des deux threads", Thread.CurrentThread.Name);
// exibimos a modificação — apenas uma deve ter sido bem-sucedida
using (var context = new RdvMedecinsContext())
{
// recuperamos o cliente1 no cliente2
Client client2 = context.Clients.Find(client1.Id);
Console.WriteLine("Thread {0} client2", Thread.CurrentThread.Name);
Console.WriteLine("Thread {0} {1}", Thread.CurrentThread.Name, client2.ShortIdentity());
}
}
// thread
static void Modifie(object infos)
{
...
}
- linha 26: inicia-se um contexto vazio;
- linha 29: atribui-se um nome à thread atual para diferenciá-la das duas threads que serão criadas posteriormente;
- linhas 31-38: as entidades [Medecin] e [Client] são colocadas no estado “excluído”;
- linhas 40-41: insere-se um cliente no contexto;
- linhas 43-44: ele é exibido antes da sincronização do contexto;
- linha 46: sincronização do contexto com o banco de dados: as entidades no estado “excluído” serão excluídas do banco de dados. A entidade [Client] colocada no contexto será inserida no banco de dados. Esse será o único elemento do banco de dados;
- linhas 47-49: exibe-se o cliente após a sincronização do contexto. Nesta fase, as exibições na tela são as seguintes:
Observe-se que, após a sincronização do contexto, o cliente possui uma chave primária e um timestamp;
- linha 50: o contexto é fechado;
- linha 53: um thread t1 é associado ao método [Modifie] da linha 84. Isso significa que, quando for iniciado, ele executará o método [Modifie];
- linha 54: atribui-se um nome ao thread t1;
- linha 55: o thread t1 é iniciado. São passados parâmetros a ele na forma de uma estrutura [Data] definida nas linhas 12 a 17:
- Duração: o thread será interrompido Durée segundos antes de concluir sua execução,
- Cliente: uma referência ao cliente a ser atualizado no banco de dados,
- Nome: nome a ser atribuído a esse cliente;
- linhas 57-59: o mesmo procedimento com um segundo thread. No final, dois threads tentarão alterar, no banco de dados, o nome do mesmo cliente;
- linhas 60-63: após iniciar os dois threads, o thread principal aguarda o término de sua execução;
- linha 62: espera pelo término do thread t1;
- linha 63: espera pela conclusão do thread t2;
- linha 64: não sabemos em que ordem as duas threads serão concluídas. O que é certo é que, na linha 64, elas já estão concluídas;
- linhas 66-72: em um novo contexto, busca-se o cliente no banco de dados para verificar em que estado ele se encontra.
Vamos ver agora o que fazem as duas threads t1 e t2. Elas executam o seguinte método [Modifie]:
static void Modifie(object infos)
{
// recuperando o parâmetro
Data data = (Data)infos;
try
{
using (var context = new RdvMedecinsContext())
{
Console.WriteLine("Début Thread {0}", Thread.CurrentThread.Name);
// recuperando o cliente1 no cliente2
Client client2 = context.Clients.Find(data.Client.Id);
Console.WriteLine("Thread {0} client2", Thread.CurrentThread.Name);
Console.WriteLine("Thread {0} {1}", Thread.CurrentThread.Name, client2.ShortIdentity());
// modifica-se o cliente2
client2.Nom = data.Nom;
// espera-se um pouco
Thread.Sleep(data.Duree);
// salvamos as alterações
context.SaveChanges();
}
}
catch (Exception e)
{
// exceção
Console.WriteLine("Thread {0} {1}", Thread.CurrentThread.Name, e);
}
// fim do thread
Console.WriteLine("Fin Thread {0}", Thread.CurrentThread.Name);
}
- linha 4: recuperam-se os parâmetros da thread (Duração, Nome, Cliente);
- linha 7: novo contexto;
- linha 11: o cliente é inserido no contexto;
- linhas 12-13: acompanhamento para verificar o estado do cliente;
- linha 15: altera-se o nome do cliente;
- linha 17: o thread é interrompido por Duree milissegundos. Isso tem um efeito interessante. O thread libera o processador que o executava, dando lugar a outro thread. No nosso exemplo, temos três threads: main, t1 e t2. O thread main está parado, aguardando o término dos threads t1 e t2. Supondo que a thread t1 tenha obtido o processador primeiro, ela agora o cede à thread t2. Isso fará com que a thread t2 leia exatamente a mesma coisa que a thread t1: o mesmo cliente com o mesmo timestamp;
- linha 19: o contexto está sincronizado com o banco de dados. Suponhamos novamente que a thread t1 retorne primeiro. Ela salvará o cliente com o nome “yy”. Ela poderá fazer isso porque possui o mesmo timestamp que está no banco de dados. Devido a essa atualização, o SGBD modificará o timestamp. Quando a thread t2 acordar por sua vez, ela terá um cliente com um timestamp diferente daquele que está agora no banco de dados. Sua atualização será recusada.
As mensagens exibidas na tela são as seguintes:
- linha 4: o cliente no banco de dados;
- linha 9: o cliente conforme lido pela thread t2;
- linha 11: o cliente conforme lido pelo thread t1. Os dois threads, portanto, leram a mesma coisa;
- linha 12: o thread t2 termina primeiro. Portanto, ele conseguiu fazer sua atualização. O nome deve ter mudado para “zz”;
- linha 13: o thread t1 lança uma exceção do tipo [System.Data.OptimisticConcurrencyException]. EF detectou que não possuía o timestamp correto;
- linha 21: a thread t1 também é encerrada;
- linha 22: o thread principal encerrou sua espera;
- linha 24: o thread principal exibe o cliente no banco de dados. De fato, foi o thread t2 que venceu. O nome é “zz”. Observe-se que o timestamp mudou.
Agora, vamos examinar outro aspecto: a transação que controla a sincronização do contexto de persistência com o banco de dados.
3.5.9. Sincronização em uma transação
A tabela [CRENEAUX] possui uma restrição de unicidade que adicionamos manualmente (ver parágrafo 2.2.4, página 12):
Vamos proceder da seguinte maneira: vamos adicionar, ao mesmo tempo, duas consultas para o mesmo médico, no mesmo dia e no mesmo horário. Vamos ver o que acontece.
O projeto evolui da seguinte forma:
![]() | ![]() |
O código do programa [SynchronisationTransaction] é o seguinte:
using System;
using System.Linq;
using RdvMedecins.Entites;
using RdvMedecins.Models;
namespace RdvMedecins_01
{
// programa de teste
class SynchronisationTransaction
{
static void Main(string[] args)
{
using (var context = new RdvMedecinsContext())
{
// esvaziamos o banco de dados atual
foreach (var client in context.Clients)
{
context.Clients.Remove(client);
}
foreach (var medecin in context.Medecins)
{
context.Medecins.Remove(medecin);
}
context.SaveChanges();
}
// criando um cliente
Client client1 = new Client { Nom = "xx", Prenom = "xx", Titre = "xx" };
// criando um médico
Medecin medecin1 = new Medecin { Nom = "xx", Prenom = "xx", Titre = "xx" };
// cria-se um horário para esse médico
Creneau creneau1 = new Creneau { Hdebut = 8, Mdebut = 20, Hfin = 8, Mfin = 40, Medecin = medecin1 };
// criar duas consultas para esse médico e esse cliente, no mesmo dia, no mesmo horário
Rv rv1 = new Rv { Client = client1, Creneau = creneau1, Jour = new DateTime(2012, 10, 18) };
Rv rv2 = new Rv { Client = client1, Creneau = creneau1, Jour = new DateTime(2012, 10, 18) };
try
{
// colocamos todos esses dados no contexto de persistência
using (var context = new RdvMedecinsContext())
{
context.Clients.Add(client1);
context.Creneaux.Add(creneau1);
context.Medecins.Add(medecin1);
context.Rvs.Add(rv1);
context.Rvs.Add(rv2);
// salva-se o contexto — deve ocorrer uma exceção
// pois o BD subjacente tem uma restrição de exclusividade que impede
// que haja dois RDV no mesmo dia, no mesmo intervalo
context.SaveChanges();
}
}
catch (Exception e)
{
Console.WriteLine("Erreur : {0}", e);
}
// se o salvamento ocorrer em uma transação, então nada deve ter sido inserido no banco de dados
// devido à exceção anterior — verifica-se
using (var context = new RdvMedecinsContext())
{
// os clientes
Console.WriteLine("Clients--------------------------------------");
var clients = from client in context.Clients select client;
foreach (Client client in clients)
{
Console.WriteLine(client);
}
// os médicos
Console.WriteLine("Médecins--------------------------------------");
var medecins = from medecin in context.Medecins select medecin;
foreach (Medecin medecin in medecins)
{
Console.WriteLine(medecin);
}
// os horários disponíveis
Console.WriteLine("Créneaux horaires--------------------------------------");
var creneaux = from creneau in context.Creneaux select creneau;
foreach (Creneau creneau in creneaux)
{
Console.WriteLine(creneau);
}
// as consultas
Console.WriteLine("Rendez-vous--------------------------------------");
var rvs = from rv in context.Rvs select rv;
foreach (Rv rv in rvs)
{
Console.WriteLine(rv);
}
}
}
}
}
- linhas 15-27: utiliza-se um contexto de persistência para esvaziar o banco de dados;
- linha 30: criação de um objeto [Client];
- linha 32: criação de um objeto [Medecin];
- linha 34: criação de um objeto [Creneau];
- linha 36: criação de um objeto [Rv];
- linha 37: criação de um segundo objeto [Rv] idêntico ao anterior;
- linha 41: abertura de um novo contexto;
- linhas 43-47: os objetos criados anteriormente são vinculados ao novo contexto. Observe aqui que, levando em conta as dependências, poderíamos ter minimizado o número de operações Add. Mas a operação EF otimizará as ordens SQL e INSERT a serem emitidas para o banco de dados;
- linha 51: o contexto é sincronizado com o banco de dados. Conforme indicado no comentário, a inserção de um dos dois compromissos deve falhar devido à restrição de unicidade na tabela [RVS]. Mas, além disso, se a sincronização ocorrer dentro de uma transação, tudo deve ser revertido. Portanto, nenhuma inserção deve ocorrer. O banco de dados deve permanecer vazio;
- linha 53: o contexto é fechado;
- linhas 61-90: exibição do conteúdo do banco de dados. Ele deve estar vazio.
A exibição na tela é a seguinte:
- linha 1: exceção devido à violação da restrição de unicidade na tabela [RVS];
- linhas 9-12: o banco de dados está vazio. A sincronização do contexto com o banco de dados ocorreu, portanto, em uma transação.
Certamente haveria outros aspectos a serem explorados na tabela EF 5. Mas sabemos o suficiente para retomar nosso estudo sobre uma arquitetura multicamadas. O leitor encontrará, no início deste documento, referências a artigos e livros que lhe permitirão aprofundar seus conhecimentos sobre a tabela EF 5.
3.6. Estudo de uma arquitetura multicamadas baseada em EF 5
Voltamos ao nosso estudo de caso descrito no parágrafo 2. Trata-se de um aplicativo web ASP.NET estruturado da seguinte forma:
![]() |
Começaremos construindo a camada [DAO] de acesso aos dados. Essa camada se baseará em EF5.
3.6.1. O novo projeto
Criamos um novo projeto de console VS 2012 [RdvMedecins-SqlServer-02] na solução atual [1]:
![]() |
Adicionamos quatro pastas [2], nas quais vamos distribuir nossos códigos. A pasta [Entites] é uma cópia da pasta [Entites] do projeto anterior. Após essa cópia, surgem erros devido ao fato de não termos as referências corretas. Precisamos adicionar uma referência ao Entity Framework 5. Para isso, seguiremos o método explicado no parágrafo 3.4, página 21. A lista de referências fica da seguinte forma: [3]:
![]() |
Nesta fase, o projeto não deve mais apresentar erros de compilação. Do projeto anterior, copiamos também o arquivo [App.config], que configura a conexão com o banco de dados:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
<!-- Para obter mais informações sobre a configuração do Entity Framework, acesse http://go.microsoft.com/fwlink/?LinkID=237468 -->
<section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
</configSections>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
<entityFramework>
<defaultConnectionFactory type="System.Data.Entity.Infrastructure.SqlConnectionFactory, EntityFramework" />
</entityFramework>
<!-- cadeia de conexão com o banco de dados -->
<connectionStrings>
<add name="monContexte"
connectionString="Data Source=localhost;Initial Catalog=rdvmedecins-ef;User Id=sa;Password=sqlserver2012;"
providerName="System.Data.SqlClient" />
</connectionStrings>
<!-- o provedor de fábrica -->
<system.data>
<DbProviderFactories>
<add name="SqlClient Data Provider"
invariant="System.Data.SqlClient"
description=".Net Framework Data Provider for SqlServer"
type="System.Data.SqlClient.SqlClientFactory, System.Data,
Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
/>
</DbProviderFactories>
</system.data>
</configuration>
3.6.2. A classe Exception
Vamos utilizar uma classe de exceção específica do projeto. É essa que será gerada pela camada [DAO]:
![]() |
A camada [DAO] interceptará todas as exceções que forem encaminhadas até ela e as encapsulará em uma exceção do tipo [RdvMedecinsException]. Essa exceção será a seguinte:
using System;
namespace RdvMedecins.Exceptions
{
public class RdvMedecinsException : Exception
{
// propriedades
public int Code { get; set; }
// construtores
public RdvMedecinsException()
: base()
{
}
public RdvMedecinsException(string message)
: base(message)
{
}
public RdvMedecinsException(int code, string message)
: base(message)
{
Code = code;
}
public RdvMedecinsException(int code, string message, Exception ex)
: base(message, ex)
{
Code = code;
}
// identidade
public override string ToString()
{
if (InnerException == null)
{
return string.Format("RdvMedecinsException[{0},{1}]", Code, base.Message);
}
else
{
return string.Format("RdvMedecinsException[{0},{1},{2}]", Code, base.Message, base.InnerException.Message);
}
}
}
}
- linha 5: a classe deriva da classe [Exception];
- linha 9: ela adiciona um código de erro à sua classe base;
- linhas 12-32: os diferentes construtores incorporam a presença do campo [Code].
O projeto evolui da seguinte forma:
![]() |
3.6.3. A camada [DAO]
![]() |
A camada [DAO] oferece uma interface para a camada [ASP.NET]. Para identificar esta última, é preciso consultar as páginas da web do aplicativo:
![]() |
- em [1] acima, a lista suspensa foi preenchida com a lista de médicos. A camada [DAO] fornecerá essa lista;
- em [2], a camada [DAO] fornecerá;
- a lista de consultas de um médico para determinado dia,
- a lista de horários disponíveis de um médico,
- informações complementares sobre o médico selecionado;
![]() |
- em [3], a lista suspensa de clientes será fornecida pela camada [DAO];
![]() |
- em [4], o usuário confirma uma consulta. A camada [DAO] deve ser capaz de adicioná-la ao banco de dados. Ela também deve fornecer informações adicionais sobre o cliente selecionado;
![]() |
- em [5], o usuário exclui um compromisso. A camada [DAO] deve permitir isso.
Com essas informações, a interface [IDao] da camada [DAO] poderia ser a seguinte:
using System;
using System.Collections.Generic;
using RdvMedecins.Entites;
namespace RdvMedecins.Dao
{
public interface IDao
{
// lista de clientes
List<Client> GetAllClients();
// lista de médicos
List<Medecin> GetAllMedecins();
// lista de horários disponíveis de um médico
List<Creneau> GetCreneauxMedecin(int idMedecin);
// lista de RV de um determinado médico, em um determinado dia
List<Rv> GetRvMedecinJour(int idMedecin, DateTime jour);
// adicionar um RV
int AjouterRv(DateTime jour, int idCreneau, int idClient);
// excluir um RV
void SupprimerRv(int idRv);
// localizar uma entidade T por meio de sua chave primária
T Find<T>(int id) where T : class;
}
}
Os métodos das linhas 10 a 20 decorrem da análise que acabamos de realizar. O método da linha 22 existe para contornar o fato de estarmos trabalhando com Lazy Loading. Se, na camada [ASP.NET], for necessária uma dependência de uma entidade, ela será buscada no banco de dados por meio desse método.
A implementação [Dao] dessa interface será a seguinte:
using System;
using System.Collections.Generic;
using System.Linq;
using RdvMedecins.Entites;
using RdvMedecins.Exceptions;
using RdvMedecins.Models;
namespace RdvMedecins.Dao
{
public class Dao : IDao
{
//lista de clientes
public List<Client> GetAllClients()
{
// lista de clientes
List<Client> clients = null;
try
{
// abrir contexto de persistência
using (var context = new RdvMedecinsContext())
{
// lista de clientes
clients = context.Clients.ToList();
}
}
catch (Exception ex)
{
throw new RdvMedecinsException(1, "GetAllClients", ex);
}
// retornamos o resultado
return clients;
}
// lista de médicos
public List<Medecin> GetAllMedecins()
{
// lista de médicos
List<Medecin> medecins = null;
try
{
// abertura do contexto de persistência
using (var context = new RdvMedecinsContext())
{
// lista de médicos
medecins = context.Medecins.ToList();
}
}
catch (Exception ex)
{
throw new RdvMedecinsException(2, "GetAllMedecins", ex);
}
// retorna o resultado
return medecins;
}
// lista de horários disponíveis de um determinado médico
public List<Creneau> GetCreneauxMedecin(int idMedecin)
{
...
}
// lista dos RV de um médico para um determinado dia
public List<Rv> GetRvMedecinJour(int idMedecin, DateTime jour)
{
...
}
// adicionar um RV
public int AjouterRv(DateTime jour, int idCreneau, int idClient)
{
...
}
// excluir um RV
public void SupprimerRv(int idRv)
{
...
}
// localizar um cliente
public Client FindClient(int id)
{
...
}
// encontrar um horário disponível
public Creneau FindCreneau(int id)
{
...
}
// encontrar um médico
public Medecin FindMedecin(int id)
{
....
}
// marcar uma consulta
public Rv FindRv(int id){
...
}
}
}
Vamos explicar o método [GetAllClients], que deve retornar a lista de todos os clientes:
- linhas 18-31: a busca pelos clientes é feita em um try/catch. O mesmo se aplica a todos os métodos a seguir;
- linha 21: abertura de um novo contexto;
- linha 24: as entidades [Client] são carregadas no contexto e colocadas em uma lista.
O método [GetAllMedecins], que deve retornar a lista de todos os médicos, é análogo (linhas 37-57).
O método [GetCreneauxMedecin] é o seguinte:
// lista de horários disponíveis de um determinado médico
public List<Creneau> GetCreneauxMedecin(int idMedecin)
{
// lista de horários
try
{
// abrir contexto de persistência
using (var context = new RdvMedecinsContext())
{
// recuperar o médico com seus horários disponíveis
Medecin medecin = context.Medecins.Include("Creneaux").Single(m => m.Id == idMedecin);
// lista de horários do médico
return medecin.Creneaux.ToList<Creneau>();
}
}
catch (Exception ex)
{
throw new RdvMedecinsException(3, "GetCreneauxMedecin", ex);
}
}
- linha 9: abertura de um novo contexto de persistência;
- linha 11: busca-se o médico cuja chave primária se possui. Solicita-se a inclusão da dependência [Creneaux], que é uma coleção dos horários disponíveis do médico. Se o médico não existir, o método Single lança uma exceção;
- linha 13: retorna-se a lista de horários.
O método [GetRvMedecinJour] deve retornar a lista de consultas de um médico para um determinado dia. Seu código poderia ser o seguinte:
// lista dos RV de um médico para um determinado dia
public List<Rv> GetRvMedecinJour(int idMedecin, DateTime jour)
{
// lista de consultas
List<Rv> rvs = null;
try
{
// abertura do contexto de persistência
using (var context = new RdvMedecinsContext())
{
// recuperação do médico
Medecin medecin = context.Medecins.Find(idMedecin);
if (medecin == null)
{
throw new RdvMedecinsException(10, string.Format("Médecin [{0}] inexistant", idMedecin));
}
// lista de consultas
rvs = context.Rvs.Where(r => r.Creneau.Medecin.Id == idMedecin && r.Jour == jour).ToList();
}
}
catch (Exception ex)
{
throw new RdvMedecinsException(4, "GetRvMedecinJour", ex);
}
// retornando o resultado
return rvs;
}
- linha 13: insere-se no contexto o médico cuja chave primária foi fornecida;
- linhas 14-17: se ele não existir, lança-se uma exceção;
- linha 19: a consulta LINQ para recuperar as consultas desse médico;
O método [AjouterRv] deve adicionar uma consulta ao banco de dados e retornar a chave primária do elemento inserido. Seu código poderia ser o seguinte:
// adicionar um RV
public int AjouterRv(DateTime jour, int idCreneau, int idClient)
{
// nº da consulta adicionada
int idRv;
try
{
// abertura do contexto de persistência
using (var context = new RdvMedecinsContext())
{
// recuperando o horário
Creneau creneau = context.Creneaux.Find(idCreneau);
if (creneau == null)
{
throw new RdvMedecinsException(5, string.Format("Créneau [{0}] inexistant", idCreneau));
}
// recuperando o cliente
Client client = context.Clients.Find(idClient);
if (client == null)
{
throw new RdvMedecinsException(6, string.Format("Client [{0}] inexistant", idCreneau));
}
// criação do horário
Rv rv = new Rv { Jour = jour, Client = client, Creneau = creneau };
// adição ao contexto
context.Rvs.Add(rv);
// salvamento do contexto
context.SaveChanges();
// recuperação da chave primária do agendamento adicionado
idRv = (int)rv.Id;
}
}
catch (Exception ex)
{
throw new RdvMedecinsException(7, "AjouterRv", ex);
}
// resultado
return idRv;
}
- linha 12: busca-se o horário da consulta no banco de dados;
- linhas 13-16: se não for encontrado, lança-se uma exceção;
- linha 18: procura-se o cliente do compromisso no banco de dados;
- linhas 19-22: se não for encontrado, lança-se uma exceção;
- linha 24: cria-se um objeto [Rv] com as informações necessárias;
- linha 26: ele é adicionado ao contexto de persistência;
- linha 28: sincronizamos o contexto de persistência com o banco de dados. O compromisso será então inserido no banco de dados;
- linha 30: sabemos que, após a sincronização com o banco de dados, as chaves primárias dos elementos inseridos estão disponíveis. Recuperamos a chave primária do compromisso adicionado;
- linha 31: fecha-se o contexto de persistência.
O método [SupprimerRv] deve excluir um compromisso cuja chave primária lhe seja passada.
// excluir um RV
public void SupprimerRv(int idRv)
{
try
{
// abertura do contexto de persistência
using (var context = new RdvMedecinsContext())
{
// recuperação do Rv
Rv rv = context.Rvs.Find(idRv);
if (rv == null)
{
throw new RdvMedecinsException(5, string.Format("Rv [{0}] inexistant", idRv));
}
// exclusão do Rv
context.Rvs.Remove(rv);
// salvamento do contexto
context.SaveChanges();
}
}
catch (Exception ex)
{
throw new RdvMedecinsException(8, "SupprimerRv", ex);
}
}
- linha 7: novo contexto de persistência;
- linha 10: insere-se no contexto o compromisso a ser excluído;
- linhas 11-15: se ele não existir, lança-se uma exceção;
- linha 16: o compromisso é removido do contexto;
- linha 18: sincronizamos o contexto com o banco de dados;
- linha 19: fecha-se o contexto.
O método [Find<T>] permite pesquisar no banco de dados uma entidade do tipo T, por meio de sua chave primária. Seu código poderia ser o seguinte:
public T Find<T>(int id) where T : class
{
try
{
// abertura do contexto de persistência
using (var context = new RdvMedecinsContext())
{
return context.Set<T>().Find(id);
}
}
catch (Exception ex)
{
throw new RdvMedecinsException(20, "Find<T>", ex);
}
}
- linha 8: o método Set<T> permite recuperar um DbSet<T> ao qual é possível aplicar os métodos habituais.
O projeto evolui da seguinte forma:
![]() |
3.6.4. Teste da camada [DAO]
Vamos criar um programa de teste da camada [DAO]. A arquitetura do teste será a seguinte:
![]() |
Um programa de console solicita que [Spring.net] instancie a camada [DAO]. Feito isso, ele testa as diferentes funcionalidades da interface da camada [DAO]. Em vez de um programa de console, teria sido preferível escrever um programa de teste do tipo NUnit. Um programa de teste da camada [DAO] poderia ser o seguinte:
using System;
using System.Collections.Generic;
using RdvMedecins.Dao;
using RdvMedecins.Entites;
using RdvMedecins.Exceptions;
using Spring.Context.Support;
namespace RdvMedecins.Tests
{
class Program
{
public static void Main()
{
IDao dao = null;
try
{
// instanciação da camada [DAO] via Spring
dao = ContextRegistry.GetContext().GetObject("rdvmedecinsDao") as IDao;
// exibição de clientes
List<Client> clients = dao.GetAllClients();
DisplayClients("Liste des clients :", clients);
// exibição de médicos
List<Medecin> medecins = dao.GetAllMedecins();
DisplayMedecins("Liste des médecins :", medecins);
// lista de horários disponíveis do médico nº 0
List<Creneau> creneaux = dao.GetCreneauxMedecin((int)medecins[0].Id);
DisplayCreneaux(string.Format("Liste des créneaux horaires du médecin {0}", medecins[0]), creneaux);
// lista de consultas de um médico para um determinado dia
DisplayRvs(string.Format("Liste des RV du médecin {0}, le 23/11/2013 :", medecins[0]), dao.GetRvMedecinJour((int)medecins[0].Id, new DateTime(2013, 11, 23)));
// adicionar um RV ao médico nº 1 no horário nº 0
Console.WriteLine(string.Format("Ajout d'un RV au médecin {0} avec client {1} le 23/11/2013", medecins[0], clients[0]));
int idRv1 = dao.AjouterRv(new DateTime(2013, 11, 23), (int)creneaux[0].Id, (int)clients[0].Id);
Console.WriteLine("Rdv ajouté");
DisplayRvs(string.Format("Liste des RV du médecin {0}, le 23/11/2013 :", medecins[0]), dao.GetRvMedecinJour((int)medecins[0].Id, new DateTime(2013, 11, 23)));
// adicionar uma consulta em um horário já ocupado — deve gerar uma exceção
int idRv2;
Console.WriteLine("Ajout d'un RV dans un créneau déjà occupé");
try
{
idRv2 = dao.AjouterRv(new DateTime(2013, 11, 23), (int)creneaux[0].Id, (int)clients[0].Id);
Console.WriteLine("Rdv ajouté");
DisplayRvs(string.Format("Liste des RV du médecin {0}, le 23/11/2013 :", medecins[0]), dao.GetRvMedecinJour((int)medecins[0].Id, new DateTime(2013, 11, 23)));
}
catch (RdvMedecinsException ex)
{
Console.WriteLine(string.Format("L'erreur suivante s'est produite : {0}", ex));
}
// excluir uma consulta
Console.WriteLine(string.Format("Suppression du RV n° {0}", idRv1));
dao.SupprimerRv(idRv1);
DisplayRvs(string.Format("Liste des RV du médecin {0}, le 23/11/2013 :", medecins[0]), dao.GetRvMedecinJour((int)medecins[0].Id, new DateTime(2013, 11, 23)));
}
catch (Exception ex)
{
Console.WriteLine(string.Format("L'erreur suivante s'est produite : {0}", ex));
}
//pausa
Console.ReadLine();
}
// métodos utilitários — exibe listas
public static void DisplayClients(string Message, List<Client> clients)
{
Console.WriteLine(Message);
foreach (Client c in clients)
{
Console.WriteLine(c.ShortIdentity());
}
}
public static void DisplayMedecins(string Message, List<Medecin> medecins)
{
...
}
public static void DisplayCreneaux(string Message, List<Creneau> creneaux)
{
...
}
public static void DisplayRvs(string Message, List<Rv> rvs)
{
...
}
}
}
- linha 14: a referência à camada [DAO]. Para tornar o teste independente da implementação real dessa camada, essa referência é do tipo da interface [IDao] e não do tipo da classe [Dao];
- linha 18: a camada [DAO] é instanciada pelo Spring. Voltaremos à configuração necessária para que isso seja possível. Convertemos a referência do objeto retornada pelo Spring em uma referência do tipo da interface [IDao];
- linhas 21-22: exibem os clientes;
- linhas 25-26: exibem os pacientes;
- linhas 29-30: exibem a lista de horários do médico nº 0;
- linha 33: exibe as consultas do médico nº 0 na data de 23/11/2013. Não deve haver nenhuma;
- linha 37: adiciona uma consulta ao médico nº 0 para o dia 23/11/2013;
- linha 39: exibe as consultas do médico nº 0 na data de 23/11/2013. Deve haver uma;
- linha 46: adiciona pela segunda vez a mesma consulta. Deve ocorrer uma exceção;
- linha 57: exclui a única consulta adicionada;
- linha 58: exibe as consultas do médico nº 0 na data de 23/11/2013. Não deve haver nenhuma.
3.6.5. Configuração do Spring.net
No programa de teste acima, passamos rapidamente pela instrução que instancia a camada [DAO]:
dao = ContextRegistry.GetContext().GetObject("rdvmedecinsDao") as IDao;
A classe [ContextRegistry] é uma classe do Spring no espaço de nomes [Spring.Context.Support]. Para poder usar o Spring, precisamos adicionar seu DLL às referências do projeto. Procedemos da seguinte maneira:
![]() |
- em [1], procuramos pacotes com a ferramenta [NuGet];
![]() |
- em [2], procuramos pacotes on-line;
- em [3], insere-se a palavra-chave spring no campo de pesquisa;
- em [4], são exibidos os pacotes cuja descrição contém essa palavra-chave. Aqui, o [Spring.Core] é o que nos interessa. Vamos instalá-lo.
As referências do projeto passam a ser as seguintes:
![]() |
O pacote [Spring.Core] dependia do pacote [Common.Logging]. Este também foi carregado. Nesta fase, o projeto não deve mais apresentar erros.
Mas isso não significa que ele vá funcionar. Precisamos primeiro configurar o Spring no arquivo [App.config]. Essa é a parte mais delicada do projeto. O novo arquivo [App.config] é o seguinte:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
<!-- Para obter mais informações sobre a configuração do Entity Framework, acesse http://go.microsoft.com/fwlink/?LinkID=237468 -->
<section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
<!-- Spring -->
<sectionGroup name="spring">
<section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" />
<section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
</sectionGroup>
<!-- registro comum-->
<section name="logging" type="Common.Logging.ConfigurationSectionHandler, Common.Logging" />
</configSections>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
<!-- Entity Framework -->
<entityFramework>
<defaultConnectionFactory type="System.Data.Entity.Infrastructure.LocalDbConnectionFactory, EntityFramework">
<parameters>
<parameter value="v11.0" />
</parameters>
</defaultConnectionFactory>
</entityFramework>
<!-- Cadeias de conexão -->
<connectionStrings>
<add name="monContexte" connectionString="Data Source=localhost;Initial Catalog=rdvmedecins-ef;User Id=sa;Password=sqlserver2012;" providerName="System.Data.SqlClient" />
</connectionStrings>
<system.data>
<DbProviderFactories>
<add name="SqlClient Data Provider" invariant="System.Data.SqlClient" description=".Net Framework Data Provider for SqlServer" type="System.Data.SqlClient.SqlClientFactory, System.Data, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
</DbProviderFactories>
</system.data>
<!-- configuração do Spring -->
<spring>
<context>
<resource uri="config://spring/objects" />
</context>
<objects xmlns="http://www.springframework.net">
<object id="rdvmedecinsDao" type="RdvMedecins.Dao.Dao,RdvMedecins-SqlServer-02" />
</objects>
</spring>
<!-- configuração common.logging -->
<logging>
<factoryAdapter type="Common.Logging.Simple.ConsoleOutLoggerFactoryAdapter, Common.Logging">
<arg key="showLogName" value="true" />
<arg key="showDataTime" value="true" />
<arg key="level" value="DEBUG" />
<arg key="dateTimeFormat" value="yyyy/MM/dd HH:mm:ss:fff" />
</factoryAdapter>
</logging>
</configuration>
Vamos começar removendo tudo o que já é conhecido: Entity Framework, cadeias de conexão, ProviderFactory. O arquivo fica da seguinte forma:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
<!-- Para obter mais informações sobre a configuração do Entity Framework, acesse http://go.microsoft.com/fwlink/?LinkID=237468 -->
<section name="entityFramework" ... />
<!-- spring -->
<sectionGroup name="spring">
<section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" />
<section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
</sectionGroup>
<!-- registro comum-->
<sectionGroup name="common">
<section name="logging" type="Common.Logging.ConfigurationSectionHandler, Common.Logging" />
</sectionGroup>
</configSections>
...
<!-- configuração do Spring -->
<spring>
<context>
<resource uri="config://spring/objects" />
</context>
<objects xmlns="http://www.springframework.net">
<object id="rdvmedecinsDao" type="RdvMedecins.Dao.Dao,RdvMedecins-SqlServer-02" />
</objects>
</spring>
<!-- configuração common.logging -->
<common>
<logging>
<factoryAdapter type="Common.Logging.Simple.ConsoleOutLoggerFactoryAdapter, Common.Logging">
<arg key="showLogName" value="true" />
<arg key="showDataTime" value="true" />
<arg key="level" value="DEBUG" />
<arg key="dateTimeFormat" value="yyyy/MM/dd HH:mm:ss:fff" />
</factoryAdapter>
</logging>
</common>
</configuration>
- linhas 3-15: definem seções de configuração;
- linha 8: define a classe que irá gerenciar a seção <spring><context> do arquivo XML (linhas 19-21);
- linha 9: define a classe que irá gerenciar a seção <spring><objects> do arquivo XML (linhas 22-24);
- linha 13: define a classe que irá gerenciar a seção <common><logging> do arquivo XML (linhas 27-36);
- linhas 7-14: são fixas. Não precisam ser alteradas em outro projeto;
- linhas 18-25: configuração do Spring. É estável, exceto pelas linhas 22-24, que definem os objetos que o Spring precisará instanciar;
- linha 23: definição de um objeto. O atributo id é livre. É o identificador do objeto. O atributo type indica a classe a ser instanciada no formato “nome completo da classe, Assembly que contém a classe”. A classe aqui é aquela que implementa a camada [DAO]: [RdvMedecins.Dao.Dao]. Para saber qual é o assembly correspondente, é preciso consultar as propriedades do projeto:
![]() |
Em [1], o nome do assembly a ser fornecido;
- linhas 27-36: a configuração de “Common Logging” é estável. Pode ser necessário alterar o nível de informação, na linha 32. Após a fase de depuração, é possível alterar o nível para INFO.
No fim das contas, embora pareça complexo à primeira vista, o arquivo de configuração do Spring acaba sendo simples. Basta alterar:
- as linhas 22-24, que definem os objetos a serem instanciados;
- linha 32: o nível de log.
No programa de teste, a instrução que instancia a camada [DAO] é a seguinte:
dao = ContextRegistry.GetContext().GetObject("rdvmedecinsDao") as IDao;
[ContextRegistry] é uma classe do Spring que utiliza a configuração do Spring definida em um arquivo [Web.config] ou [App.config]. Neste caso, ela utilizará a seguinte seção do arquivo [App.config]:
<spring>
<context>
<resource uri="config://spring/objects" />
</context>
<objects xmlns="http://www.springframework.net">
<object id="rdvmedecinsDao" type="RdvMedecins.Dao.Dao,RdvMedecins-SqlServer-02" />
</objects>
</spring>
- ContextRegistry.GetContext() utiliza o contexto das linhas 2 a 4. A linha 3 significa que os objetos Spring estão definidos na seção [spring/objects] do arquivo de configuração. Essa seção corresponde às linhas 5 a 7;
- ContextRegistry.GetContext().GetObject("rdvmedecinsDao") utiliza a seção das linhas 5 a 7. Ela retorna uma referência ao objeto que possui o atributo id= "rdvmedecinsDao". Esse é o objeto definido na linha 6. O Spring, então, instanciará a classe definida pelo atributo type utilizando seu construtor sem parâmetros. Portanto, esse construtor deve existir. Feito isso, a referência do objeto criado é retornada ao código chamador. Se o objeto for solicitado uma segunda vez no código, o Spring simplesmente retorna uma referência ao primeiro objeto criado. Esse é o padrão de projeto (Design Pattern) chamado singleton.
A criação do objeto pode ser mais complexa. É possível usar um construtor com parâmetros ou especificar a inicialização de determinados campos do objeto após sua criação. Para mais informações sobre esse assunto, consulte o artigo “Tutorial Spring IOC para .NET”, disponível em URL [http://tahe.developpez.com/dotnet/springioc/].
Feito isso, podemos executar o aplicativo. Os resultados na tela são os seguintes:
Os resultados estão de acordo com o esperado. A partir de agora, consideraremos que nossa camada [DAO] é válida. O tutorial poderia terminar aqui. Até o momento, mostramos:
- os fundamentos do Entity Framework 5 para ORM;
- uma camada [DAO] que utiliza esse ORM.
Vamos relembrar nosso estudo de caso descrito no início deste documento. Partimos de um aplicativo existente com a seguinte arquitetura:
![]() |
que queremos transformar nesta:
![]() |
onde EF5 substituiu NHibernate. Acabamos de construir a camada [DAO2]. Na verdade, ela não apresenta a mesma interface que a camada [DAO1], cuja interface era mais reduzida:
public interface IDao
{
// lista de clientes
List<Client> GetAllClients();
// lista de médicos
List<Medecin> GetAllMedecins();
// lista de horários disponíveis de um médico
List<Creneau> GetCreneauxMedecin(int idMedecin);
// lista dos RV de um determinado médico, em um determinado dia
List<Rv> GetRvMedecinJour(int idMedecin, DateTime jour);
// adicionar um RV
int AjouterRv(DateTime jour, int idCreneau, int idClient);
// excluir um RV
void SupprimerRv(int idRv);
}
A camada [DAO2] adicionou a essa interface o método:
// localizar uma entidade T por meio de sua chave primária
T Find<T>(int id) where T : class;
A adição desse método se deve ao fato de que a camada ORM EF 5 opera, por padrão, no modo Lazy Loading. As entidades chegam à camada [ASP.NET] sem suas dependências. O método acima nos permite recuperá-las caso seja necessário, e em alguns casos isso é necessário. O NHibernate também funciona por padrão no modo Lazy Loading, mas eu o havia utilizado no modo Eager Loading. As entidades chegavam à camada [ASP.NET] com suas dependências.
Vamos concluir a migração do aplicativo ASP.NET / NHibernate para o aplicativo ASP.NET / EF 5. Mas, como isso não diz mais respeito ao EF5, não faremos comentários sobre o código da web. Explicaremos simplesmente como configurar o aplicativo web e testá-lo. Ele está disponível no site deste tutorial.
3.6.6. Geração do DLL a partir da camada [DAO]
Na seguinte arquitetura:
![]() |
a camada [ASP.NET] terá à sua disposição as camadas à sua direita na forma de DLL. Portanto, criamos a DLL a partir da camada [DAO].
![]() |
- em [1], seleciona-se o programa de teste e, em [2], ele não é incluído na DLL que será gerada;
- no [3], nas propriedades do projeto, indica-se que o assembly a ser criado é um DLL;
- no [4], no menu do VS, indica-se que será gerado um assembly do tipo [Release], que contém menos informações do que um assembly do tipo [Debug];
![]() |
- no [5], regenera-se o assembly do projeto. O DLL será gerado;
- em [6], exibimos todos os arquivos do projeto;
![]() |
- em [7], o DLL do projeto da camada [DAO]. É este que o projeto web ASP.NET utilizará;
- em [8], atualizamos a exibição do projeto;
![]() |
- no [9], os arquivos DLL da pasta [Release] são reunidos em uma pasta externa [lib], que é o [10]. É daí que o projeto web irá buscar suas referências.
3.6.7. A camada [ASP.NET]
Aqui, explicaremos a migração do aplicativo [ASP.NET / NHibernate] para o aplicativo [ASP.NET / EF 5]. Trabalharemos com o Visual Studio Express 2012 para a web, disponível gratuitamente no URL [http://www.microsoft.com/visualstudio/fra/downloads].
Vamos partir do projeto web existente criado com o VS 2010.
![]() |
- no [1], abrimos o projeto existente:
- no [2], o projeto carregado possui as seguintes referências: [3]:
- [NHibernate] é o DLL do framework NHibernate,
- [Spring.Core] é o DLL do framework Spring.net,
- [log4net] é o DLL do framework de logs log4net. Esse framework é utilizado pelo Spring.net,
- [MySql.Data] é o driver ADO.NET do SGBD MySQL,
- [rdvmedecins] é a DLL da camada [DAO] construída com NHibernate;
- em [4], alteramos o nome do projeto e, em [5], removemos as referências anteriores;
![]() |
- no [6], adicionamos referências ao projeto;
- em [7], no assistente, usamos a opção [Parcourir];
![]() |
- em [8], selecionamos todos os DLL do projeto nº 2 previamente colocados na pasta [lib];
- em [9], um resumo que validamos;
- em [10], o projeto web com suas novas referências.
Feito isso, o projeto fica da seguinte forma:
![]() |
- no [1], o código de gerenciamento das páginas da web está distribuído entre os dois arquivos [Global.asax] e [Default.aspx]. O código utilitário foi colocado na pasta [Entites]. Por fim, o aplicativo é configurado pelo arquivo [Web.config];
- no [2], geramos o assembly do projeto;
- no [3], surgem erros.
Vamos examinar os erros, por exemplo, o seguinte:
![]()
e sua explicação:
![]()
O tipo de [medecin.Id] é int?, enquanto o método [GetCreneauxMedecin] é do tipo int. Portanto, é necessário um cast. Esse erro é recorrente em todo o código, pois as entidades do projeto ASP.NET / NHibernate tinham chaves primárias do tipo int, enquanto as do projeto ASP.NET / EF 5 são do tipo int?. Corrigimos todos os erros desse tipo e regeneramos o projeto. Assim, eles desaparecem.
Resta-nos um detalhe a resolver antes de executar o projeto: a instanciação da camada [DAO] pelo framework Spring. Isso é feito em [Global.asax]:
protected void Application_Start(object sender, EventArgs e)
{
// armazenamos em cache alguns dados do banco de dados
try
{
// instanciação da camada [dao]
Dao = ContextRegistry.GetContext().GetObject("rdvmedecinsDao") as IDao;
...
}
catch (Exception ex)
{...
}
}
No programa de teste da camada [DAO], esta instanciava a camada [DAO] da seguinte maneira:
dao = ContextRegistry.GetContext().GetObject("rdvmedecinsDao") as IDao;
Os dois métodos são idênticos. Vale lembrar que essa instanciação da camada [DAO] se baseava em uma configuração feita na [App.config]. Substituímos, então, o conteúdo atual [Web.config] do projeto web pelo conteúdo de [App.config] do projeto da camada [DAO], a fim de obter a mesma configuração.
Estamos prontos para uma primeira execução. A página inicial é exibida como [1]:
![]() |
- em [2], insere-se uma data de compromisso e confirma-se;
![]() |
- em [3], ocorre um erro.
Ao examinar o texto de erro exibido pela página, percebe-se que a exceção sinalizada é a do Lazy Loading: tentou-se carregar uma dependência de um objeto, embora o contexto de persistência que o gerencia tenha sido fechado. O objeto está agora em um estado “desvinculado”. Esse erro se deve ao fato de que NHibernate foi utilizado no modo Eager Loading, enquanto EF funciona, por padrão, no Lazy Loading. Na linha em vermelho acima:
- rdv representa um objeto [Rv] que foi carregado sem suas dependências;
- para avaliar rdv.Creneau.Id, o aplicativo tenta carregar a dependência rdv.Creneau. Mas, como já não estamos mais no contexto, isso não é possível, daí a exceção.
Aqui, a solução é simples. Na linha 108, cria-se uma entrada em um dicionário com a chave primária do horário de um compromisso como chave. Ora, acontece que a entidade [Rv] encapsula a chave primária do horário associado. Portanto, escreve-se:
dicoRvPris[(int)rdv.CreneauId] = rdv;
Tentamos executar novamente. Desta vez, o erro é o seguinte:
![]() |
O erro é semelhante. Na linha 132, tenta-se carregar a dependência [Client] de um objeto [Rv] na camada ASP.NET, portanto fora do contexto. É preciso buscar o objeto [Client] no banco de dados. Para resolver esse problema, a interface [IDao] foi ampliada com o seguinte método:
// localizar uma entidade T por meio de sua chave primária
T Find<T>(int id) where T : class;
Isso permitirá buscar as dependências. Assim, a linha com erro acima será reescrita da seguinte forma:
Client client = Global.Dao.Find<Client>(agenda.Creneaux[i].Rdv.ClientId);
Mais uma vez, vale ressaltar a importância de as entidades incluírem suas chaves estrangeiras. Aqui, a entidade [Rv] nos dá acesso à chave estrangeira da dependência [Creneau] associada. Após essas duas correções, o aplicativo funciona. Convidamos o leitor a testar a aplicação [RdvMedecins-SqlServer-03], disponível na seção de downloads de exemplos do site deste artigo.
3.7. Conclusion
Concluímos com sucesso a portabilidade de uma aplicação ASP.NET / NHibernate:
![]() |
para um aplicativo ASP.NET / EF 5:
![]() |
Embora essa arquitetura devesse ter nos permitido manter intacta a camada [ASP.NET], tivemos que modificá-la por dois motivos:
- as entidades não eram exatamente as mesmas. O tipo das chaves primárias das entidades NHibernate era int, enquanto o das entidades EF 5 era int?. Isso nos levou a introduzir cast no código da web;
- o modo de carregamento das entidades não era o mesmo para as duas: Eager Loading para NHibernate e Lazy Loading para EF 5. Isso nos levou a aprimorar a interface da camada [DAO] com um método genérico que permite buscar uma entidade por meio de sua chave primária.
No entanto, a portabilidade revelou-se bastante simples, justificando mais uma vez, se é que era necessário, a arquitetura em camadas e a injeção de dependências com o Spring ou outro framework de injeção de dependências.
Vamos agora avaliar o impacto de uma alteração no SGBD na arquitetura anterior. Vamos portar todos os projetos anteriores para outros quatro SGBD:
- Oracle Database Express Edition 11g Versão 2;
- MySQL 5.5.28;
- PostgreSQL 9.2.1;
- Firebird 2.1.
Os códigos não sofrerão mais alterações. Apenas os seguintes elementos serão alterados:
- a definição, nas entidades, do campo utilizado para controlar a concorrência de acesso a uma entidade;
- os arquivos de configuração [App.config] ou [Web.config];
Comentaremos apenas os elementos que serão alterados.


















































































































































