###### tags: `Entity Framework Core` # Entity Framework Core ## O que é o Entity Framework Core? Entity Framework Core é o nome do produto, o papel que ele desempenha é o de um ORM. Ele nos permite focar exatamente nas regras de negócio, abstraindo totalmente a camada de acesso ao banco de dados, ou seja, você não precisa escrever nenhum comando SQL em sua aplicação. ### Por que usar um ORM? A maioria das estruturas de desenvolvimento inclui bibliotecas que permitem o acesso a dados de banco de dados relacionais por meio de estruturas de dados semelhantes a um conjunto de registros. O exemplo a seguir inlustra um cenário que os dados são recuperados de um banco de dados e armazenados em um ADO.NET DataTable para que sejam acessíveis ao código do programa: ```csharp using(var conexao = new SqlConnection(connectionString)) using(var command = new SqlCommand("Select * From Produto", conexao)) { var tabela = new DataTable(); using(var da = new SqlDataAdapter(command)) { da.Fill(tabela) } } ``` Os dados dentro da DataTable são acessíveis por meio de indexadores numéricos ou de string e precisam ser convertidos para o objeto correto. ```csharp foreach (DataRow row in tabela.Rows) { int Id = Convert.ToInt32(row[0]); string Nome = row["NomeProduto"].ToString(); } ``` Essa abordagem de ligação tardia ou *fracamente tipada* para acesso a dados está sujeita a erros. Os problemas normalmente surgem de erros de digitação do nome de uma coluna, ou descoberta de que o nome da coluna foi alterado no banco de dados, ou de uma mudança na ordem em que os campos são especificados na instrução SQL sem que uma mudança correspondente seja feita no código do aplicativo. da mesma forma, as conversões de tipo de dados podem falhar. O código ainda será compilado, mas apresentará erro em tempo de execução. Consequentemente, os desenvolvedores profissionais preferem trabalhar com dados de uma maneira fortemente tipada. ### Digitação Forte Quando é adotado uma abordagem fortemente tipada para os dados, trabalhar com propriedades de classes predefinidas que compôe um **modelo de domínio** de maneira orietada a objetos: ```csharp public class Produto { public Id {get; set;} public Nome {get; set;} } int Id = Produdo.Id; string Nome = Produto.Nome; ``` Ainda ha trabalho a ser realizado para recuperar e mapear os dados para uma instância de objeto de domínio. Uma opção é escrever seu próprio código para gerenciar isso. No entanto, conforme o modelo de domínio cresce, a quantidade de código necessário pode aumentar e precisará de mais e mais tempo de desenvolvimento para ser mantida. isso aumentará o tempo total necessário para concluir um produto de software. ORMs são bibliotecas de código pré-escritas que fazem esse trabalho para você. ORMs com recursos completos também fazem muito mais. Eles podem: * mapear um modelo de domínio para objetos de banco de dados; * criar bancos de dados e manter o esquema alinhado com as mudanças no modelo; * gerar SQL e executá-lo no banco de dados; * gerenciar transações; * manter o controle de objetos que já foram recuperados; ## DbContext #### O Entity Framework Core DbContext A DbContext classe Entity Framework Core representa uma sessão com um banco de dados e fornece uma API para comunicação com o banco de dados com os seguintes recursos: * Conexões de banco de dados * Operações de dados, como consulta e persistência * Rastreamento de mudança * Construção de Modelo * Mapeamento de dados * Cache de objeto * Gestão de transações #### Conexões de banco de dados O DbContext é responsável por abrir e gerenciar conexões com o banco de dados. #### Operações de dados O DbContext fornece métodos para realizar as seguintes operações de dados diretamente. * Adicionando dados * Modificando dados * Excluindo dados O DbContext também fornece capacidade de consulta de dados por meio da propriedade DbSet #### Rastreamento de mudança O contralador de alterações detecta as alterações realizadas em uma entidade e define o EntityState de um objeto. O estado da entidade determina o tipo de operação que o banco de dados deverá executar sobre ela e, portanto, o SQL que será gerado. #### Construção de modelo O DbContext constrói um modelo conceitual baseado em conveção e configuração, e o mapeia para o banco de dados. O modelo e seus mapeamentos são construídos na inicialização do aplicativo e são persistido na memória durante o tempo de vida do aplicativo. #### Mapeamento de dados O DbContext inclui uma camada de mapeador de dados responsável por mapear os resultados da consultas SQL para instância de entidade e outros tipos definidos pelo aplicativo cliente. #### Cache de objetos O DbContext Fornece um chace de primeiro nível para objetos que são solicitados a recuperar do armazenamento de dados. Solcitações subsequentes para o mesmo objeto retornarão o objeto em cache em vez de executar outra solicitação de banco de dados. #### Gestão de transações Quando SaveChanges método DbContext e chamado, uam transação é criada e todas as alterações pndentes são agrupadas nela com uma única unidade de trabalho. Se ocorre um erro quando as alterações forem aplicadas ao banco de dados, elas serão revertidas e o banco de daos será deixado em uma condição inalterada. ### Adicionando Dados #### Adicionando dados por meio do DbContext Os principais métodos para adicionar entidades por meio do DbContext são. ```csharp Add<TEntity>(TEntity entity) Add(object entity) AddRange(IEnumerable<object> entities) AddRange(params object[] entities) ``` Esses métodos são novos DbContext Framework Core e não tem equivalentes na versão anterior do Entity Framework onde o DbContext está disponível. Na maioria das vezes, você usará a versão genérica de, Add mas omitirá o parâmetro de tipo, porque o compilador interirá o tipo a partir do argumento passado para o método. Os dois exemplos a seguir são idênticos: ```csharp var ator = new Ator { PrimeiroNome = "Stanley", UltimoNome = "Paulo"}; context.Add<Ator>(ator); context.SaveChanges(); var ator = new Ator { PrimeiroNome = "Stanley", UltimoNome = "Paulo"}; context.Add(ator); context.SaveChanges(); ``` #### Adicionando vários registros O AddRange método é usado para adicionar vários objetos ao banco de dados em uma chamada de método. O código no próximo exemplo é muito semelhante ao exemplo anterior, mas o AddRange método é usado para salvar todos os livros e autores no banco de dados de uma só vez. ```csharp var context = new ExemploContext(); var autor = new Autor { PrimeiroNome = "Stanley", UltimoNome = "Paulo"}; var livros = new List<Livro> { new Livro {Titulo = "DDD", Autor = autor} new Livro {Titulo = "Matematica", Autor = autor} new Livro {Titulo = "GOF", Autor = autor} }; context.AddRange(livros); context.SaveChanges(); ``` Esta versão do AddRange método requer um IEnumerable<object>. EF Core é inteligente o suficiente para identificar o tipo de objetos que estão sendo adicionados ao contexto e formará o SQL apropriado. O autor está relacionado a todos os livros, por isso faz parte do gráfico e também é adicionado. A outra versão do AddRange método usa uma matriz params e fornce a facilidade de adicionar uma série de objetos não relacionados ao banco de dados de uma só vez: ```csharp var context = new ExemploContext(); var autor = new Autor {PrimeiroNome = "Stanley", UltimoNome = "Paulo"}; var livro = new Livro {Titulo = "Patterns, Principles, and Practices of Domain-Driven Design"}; context.AddRange(autor, book); context.SaveChanges(); ``` Quando o SaveChanges método é chamado no DbContext, todas as entidades com uma EntityState das Added será inserido no banco de dados; ### Modificando Dados #### Modificando dados por meio do DbContext A abordagem que você adota para modificar entidades depende se o contexto está rastreando a entidade sendo modificada ou não. No exemplo a seguir, a entidade é obtida pelo contexto, então o contexto começa a rastreá-lo imediatamente. Quando você altera os valores da propriedade em entidade rastreada, o contexto muda EntityState para a entidade para Modified e o ChangeTracker registra os valores da propriedade antiga e os novos valores da propriedade. Quando SaveChanges é chamado, um UPDATE instrução é gerada e executada pelo banco de dados. ```csharp var autor = context.Autor.First(a => a.AutorId == 1); autor.PrimeiroNome = "Stanley"; context.SaveChanges(); ``` Como o ChangeTracker rastreia quais propriedades foram modificadas, o contexto emitirá uma instrução SQL que atualiza apenas as propriedades que foram alteradas: ```sql exec sp_executesql N'SET NOCOUNT ON; UPDATE [Autor] SET [PrimeiroNome] = @p0 WHERE [AutorId] = @p1; SELECT @@ROWCOUNT; ',N'@p1 int, @p0 nvarchar(400)', @p1=1, @p0=N'Stanley' ``` #### Cenário Desconectado Em um cenário desconectado, como um aplicativo ASP.NET, as alterações nos valores de propriedade de uma entidade existente podem ocorrer em um controlador ou método de serviço, bem longe do contexto. Nesses casos, o contexto precisa ser informado que a entidade está em um estado modificado. Isso pode ser feito de várias maneiras: definindo EntityState explicitamente para a entidade; usadno o DbContext.Update método (que é novo no EF Core); estado das propreidades individuais no gráfico. #### Configurando EntityState Você pode definir o EntityState de uma entidade por meio da EntityEntry.State propriedade, que é disponibilizada pelo *DbContext.Entry* método. ```csharp public void Salvar(Autor autor) { context.Entry(autor).State = EntityState.Modified; context.SaveChanges(); } ``` Esta abordagem resultará na atribuição do *modified* estado apenas à entidade de autor. Quaisquer objetos relacionado não serão rastreados. Como o ChangeTracker não sabe quais propriedades forma modificadas, o contexto emitira uma instrução SQL atualizando todos os valores das propriedades (exceto o valor da chave primária). #### Atualizando DbContext A *DbContext* classe fornece *Update* e *UpdateRange* métodos para trabalhar com entidades individuais ou múltiplas. ```csharp public void Salvar(Autor autor) { context.Update(autor); context.SaveChanges(); } ``` Tal como acontece com a definição da entidde State, este método resulta na entidade sendo rastreada pelo contexto como Modified. Mais uma vez, o contexto não tem como identificar quais valores de propriedade foram alterados e gerará SQL para atualizar todas as propriedades. Este método difere da definição explícita da State propriedade no fato de que o contexto começará a rastrear quaisquer entidade relacionadas (como uma coleção de livros deste exemplo) no Modified estado, resultando no UPDATE geração de declarações para cada uma delas. Se a entidade relacionada não tiver um valor-chave atribuído, ela será marcada como Added e um INSERT demonstrativo será gerado. ```csharp var context = new ExemploContext(); var autor = new Autor { AutorId = 1, PrimeiroNome = "Stanley", UltimoNome = "Paulo" }; autor.Livros.Add(new Livro {LivroId = 1, Titulo = "O desejo de apreender"}); context.Attach(autor); context.Entry(autor).Property("PrimeiroNome").IsModified = true; context.SaveChanges(); ``` O código acima resultará na entidade de autor sendo marcada como *Modified* e SQL sendo gerado para atualizar apenas *PrimeiroNome* propriedade: ```sql exec sp_executesql N'SET NOCOUNT ON; UPDATE [Autor] SET [PrimeiroNome] = @p0 WHERE [AutorId] = @p1; SELECT @@ROWCOUNT; ',N'@p1 int,@p0 nvarchar(4000)',@p1=1,@p0=N'Stanley' ``` #### TrackGraph A TrackGraph API fornece acesso a entidades individuais em um gráfico de objeto e permite que você execute código personalizado em cada uma individualmente. Isso é útil em cenários onde você está lidando com gráficos de objetos complexos que consistem em várias entidades relacionadas com estados diferentes. O exemplo a seguir replica um cenário em que um gráfico de objeto é construído fora do contexto. Em seguida, o método TrackGraph é usado para "percorrer o gráfico": ```csharp var autor = new Autor { AutorId = 1, PrimeiroNome = "Stanley", UltimoNome = "Paulo" }; autor.Livros.Add(new Livro { AutorId = 1, LivroId = 1, Titulo = "Dedicação", Isbn = "1234" }); autor.Livros.Add(new Livro { AutorId = 1, LivroId = 2, Titulo = "Persistencia", Isbn = "4321" }); autor.Livros.Add(new Livro { AutorId = 1, LivroId = 3, Titulo = "Resultado", Isbn = "5678" }); var context = new ExemploContext(); context.ChangeTracker.TrackGraph(autor, e => { if((e.Entry.Entity as Autor) != null) { e.Entry.State = EntityState.Unchanged; } else { e.Entry.State = EntityState.Modified; } }); context.SaveChanges(); ``` Neste cenário, presume-se que a entidde do autor não foi alterada, mas os livros podem ter sido editados. O TrackGraph método usa a entidade raiz com um argumento e uma lambda expression especificando a ação a ser executada. Neste caso, a entidade raiz o autor EntityState definiu com UnChange. A configuração de EntityState é necessária para que o contexto comece a rastrear a entidade. Só então as entidades relacionadas podem ser descobertas. Os livros têm seu EntityState conjunto para Modified, onde nos exemplos anteriores, resultará em um SQL que atualiza todas as propriedades da entidade: ```sql exec sp_executesql N'SET NOCOUNT ON; UPDATE [Livros] SET [AutorId] = @p0, [Isbn] = @p1, [Titulo] = @p2 WHERE [LivroId] = @p3; SELECT @@ROWCOUNT; UPDATE [Livros] SET [AutorId] = @p4, [Isbn] = @p5, [Titulo] = @p6 WHERE [LivroId] = @p7; SELECT @@ROWCOUNT; UPDATE [Livros] SET [AutorId] = @p8, [Isbn] = @p9, [Titulo] = @p10 WHERE [LivroId] = @p11; SELECT @@ROWCOUNT; ',N'@p3 int,@p0 int,@p1 nvarchar(4000),@p2 nvarchar(150),@p7 int,@p4 int,@p5 nvarchar(4000), @p6 nvarchar(150),@p11 int,@p8 int,@p9 nvarchar(4000),@p10 nvarchar(150)', @p3=1,@p0=1,@p1=N'1234',@p2=N'Dedicação', @p7=2,@p4=1,@p5=N'4321',@p6=N'Persistência', @p113,@p8=1,@p9=N'5678',@p10=N'Resultado' ``` Como o SQL atualiza todas as proprieddes, todas elas precisam estar presentes e ter um valor válido atribuído, caso contrário, serão atualizadas para seus valores padrão. No exemplo seguinte, o gráfico do objeto é mais uma vez construído fora do contexto, mas apenas a ISBN propriedade dos livros é modificada. Portanto, outras propriedades (além da chave da entidade) são omitidas: ```csharp var autor = new Autor { AutorId = 1, PrimeiroNome = "Stanley", UltimoNome = "Paulo" }; autor.Livros.Add(new Livro { LivroId = 1, Isbn = "1234" }); autor.Livros.Add(new Livro { LivroId = 2, Isbn = "4321" }); autor.Livros.Add(new Livro { LivroId = 3, Isbn = "5678" }); var context = new ExemploContext(); context.ChangeTracker.TrackGraph(autor, e => { e.Entry.State = EntityState.Unchanged; //Inicio do rastreamento if((e.Entry.Entity as Livro) != null) { context.Entry(e.Entry.Entity as Livro).Property("Isbn").IsModified = true; } }); ``` Desta vez, o corpo do método da lambda expression garante que todas as entidades sejam rastreadas no UnChanged estado e, a seguir, indica que o ISBN propriedade foi modificada. Isso resulta na geração de SQL que apenas atualiza o ISBN valor da propriedae: ```sql exec sp_executesql N'SET NOCOUNT ON; UPDATE [Livros] SET [Isbn] = @p0 WHERE [LivroId] = @p1; SELECT @@ROWCOUNT; UPDATE [Livros] SET [Isbn] = @p2 WHERE [LivroId] = @p3; SELECT @@ROWCOUNT; UPDATE [Livros] SET [Isbn] = @p4 WHERE [LivroId] = @p5; SELECT @@ROWCOUNT; ',N'@p1 int,@p0 nvarchar(4000),@p3 int,@p2 nvarchar(4000),@p5 int,@p4 nvarchar(4000)', @p1=1,@p0=N'1234', @p3=2,@p2=N'4321', @p5=3,@p4=N'5678' ``` ### Excluindo Dados #### Excluindo dados pr meio do DbContext A abordagem que você adota para excluir entidades por meio do DbContext depende se o contexto está rastreando a entidade sendo excluída ou não. No exemplo a seguir, a entidade a ser excluída é obtida pelo contexto, portanto, o contexto começa a rastreá-la imediatamente. O DbContext.Remove método resulta na EntityState definição da entidade para Deleted. ```csharp context.Remove(context.Autores.Single(a=>a.AutorId == 1)); context.SaveChanges(); ``` Quando SaveChanges é chamado, um DELETE instrução é gerada e executada pelo banco de dados. ```sql exec sp_executesql N'SET NOCOUNT ON; DELETE FROM [Autores] WHERE [AutorId] = @p0; SELECT @@ROWCOUNT; ',N'@p0 int',@p0=1 ``` Esta abordagem executa duas instrução SQL: uma para recuperar a entidade da base de dados e outra para excluí-la. Você pode usar um stub para representar a entidade a ser excluída e, assim, interroper a recuperação a entidade na base de dados: ```csharp var context = new ExemploContext(); var autor = new Autor {AutorId = 1}; context.Remove(autor); context.SaveChanges(); ``` A única propriedade que o stub requer é o valor da chave primária. #### Configurando EntityState Você pode definir explicitamente o EntityState de uma entidade como Deleted por meio da EntityEntry.State propriedade, que é disponbilizada pelo DbContext.Entry método. ```csharp var context = new ExemploContext(); var autor = new Autor {AutorId = 1}; context.Entry(autor).State = EntityState.Deleted; context.SaveChanges(); ``` #### Dados Relacionados Se a entidade que você deseja excluir possui dados relacionados, a aobrdagem que você tomará dependerá de como o relacionamento foi configurado. Um relacionamento totalmente defindo téra uma restrição referencial em cascata defindia como Delete ou SetNull, assim como um relacionamento que foi configurado poer meio da API Fluent. Nesses casos, você pode excluir o principal e deixar que o banco cuide das linhas dependentes. Onde a ação de restrição referencial é definida como NoAction, você precisa cuidar de todos os dados relacionados explicitamente. O próximo exemplo ilustra um relacionamento configurado em um modelo que não inclui uma propriedade de chave estrangeira: Continua aqui... ### Rastreador de Mudanças https://www.learnentityframeworkcore.com/