É inegável a evolução pela qual passou o Entity Framework Core desde suas primeiras versões, com inúmeras melhorias buscando o acesso a bases relacionais com uma maior performance.
Ainda assim o monitoramento envolvendo o acesso a dados em projetos que dependem desta tecnologia seguirá como uma atividade vital, fornecendo, com isso, insumos para a evolução contínua dos sistemas considerados.
E como podemos realizar tal monitoramento de maneira efetiva em uma aplicação ASP.NET Core, porém, com um mínimo esforço de codificação?
Uma boa resposta a essa pergunta seria a adoção do Application Insights, um serviço de monitoramento de aplicações Web que integra o Microsoft Azure.
Embora se trate de uma solução na nuvem, o Application Insights pode ser configurado também para utilização em soluções on-premise ou, até mesmo, em sites hospedados em outras alternativas de Cloud Computing.
Já abordei anteriormente o uso do Application Insights com ASP.NET Core em uma apresentação online do Canal .NET (juntamente com outros serviços do Azure). A gravação deste evento pode ser assistida gratuitamente no YouTube:
Na apresentação a seguir:
E também no seguinte artigo, onde eu detalhei inclusive os procedimentos necessários para ativar o Application Insights em uma aplicação ASP.NET Core (os ajustes necessários foram omitidos neste artigo para efeitos de simplificação):
O projeto utilizado para os testes descritos neste artigo já está no GitHub (trata-se de uma API REST criada com o ASP.NET Core 2.2 e que utiliza uma base do SQL Server/Azure SQL):
Na listagem a seguir está a implementação da classe BolsasController, na qual é possível observar o uso do Entity Framework Core através de uma consulta LINQ:
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Mvc;
using APIIndicadores.Data;
using APIIndicadores.Models;
namespace APIIndicadores.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class BolsasController : ControllerBase
{
[HttpGet]
public List<BolsaValores> Get(
[FromServices]ApplicationDbContext context)
{
return (from b in context.BolsasValores
select b).ToList();
}
}
}
E quais seriam os ajustes requeridos para o monitoramento do que acontece neste Controller em termos de acesso a um banco de dados SQL?
Na classe BolsasController não será necessária nenhuma alteração! Ao ativar o Application Insights, o mesmo será executado trazendo pouco impacto à performance geral das operações monitoradas, logando por default informações sobre a execução de instruções envolvendo o uso de um provider ADO.NET (o próprio Entity Framework Core foi construído a partir do ADO.NET).
É óbvio que o package que possibilita a integração do ASP.NET Core com o Application Insights será adicionado à aplicação:
Uma chave de instrumentação (Instrumentation Key) deverá ser informada ao arquivo appsettings.json:
{
"ApplicationInsights": {
"InstrumentationKey": "CHAVE INSTRUMENTAÇÃO"
},
"ConnectionStrings": {
"BaseIndicadores": "Data Source=?;Initial Catalog=?;User Id=?;Password=?;",
"CacheRedis": "STRING REDIS"
},
"Logging": {
"LogLevel": {
"Default": "Warning"
}
},
"AllowedHosts": "*"
}
E o método AddApplicationInsightsTelemetry será acionado em ConfigureServices na classe Startup, de forma a ativar o uso do Application Insights no projeto considerado:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Net.Mime;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.EntityFrameworkCore;
using APIIndicadores.Data;
using Newtonsoft.Json;
using Microsoft.Extensions.Diagnostics.HealthChecks;
namespace APIIndicadores
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
// Configurando o uso do Entity Framework Core
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("BaseIndicadores")));
// Ativando o uso de cache via Redis
services.AddDistributedRedisCache(options =>
{
options.Configuration =
Configuration.GetConnectionString("CacheRedis");
options.InstanceName = "Cache-APIIndicadores-";
});
// Verificando a disponibilidade dos bancos de dados
// da aplicação através de Health Checks
services.AddHealthChecks()
.AddSqlServer(Configuration.GetConnectionString("BaseIndicadores"), name: "baseSql")
.AddRedis(Configuration.GetConnectionString("CacheRedis"), name: "cacheRedis");
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
// Ativando o Application Insights
services.AddApplicationInsightsTelemetry(Configuration);
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseHsts();
}
// Ativando o middlweare de Health Check
app.UseHealthChecks("/status",
new HealthCheckOptions()
{
ResponseWriter = async (context, report) =>
{
var result = JsonConvert.SerializeObject(
new
{
statusApplication = report.Status.ToString(),
healthChecks = report.Entries.Select(e => new
{
check = e.Key,
ErrorMessage = e.Value.Exception?.Message,
status = Enum.GetName(typeof(HealthStatus), e.Value.Status)
})
});
context.Response.ContentType = MediaTypeNames.Application.Json;
await context.Response.WriteAsync(result);
}
});
app.UseHttpsRedirection();
app.UseMvc();
}
}
}
Para o monitoramento das instruções geradas por meio do Entity Framework Core acessar o item Performance do recurso do Application Insights vinculado a um projeto:
Selecionar, então, uma operação na qual aconteçam acessos a uma base de dados relacional (GET Bolsas/Get neste exemplo):
Acessar os exemplos de amostragem (Samples) em Take action:
Selecionar uma das operações registradas:
Na imagem a seguir temos o detalhamento da operação escolhida:
Ao marcar a dependência groffesql:
Serão apresentados detalhes da mesma (tipo, duração em milissegundos), com destaque para o comando SQL gerado durante o acesso ao banco de dados via Entity Framework Core (tudo isso sem a necessidade de escrever uma linha de código para logging dessas informações):
Consultas também podem ser executadas sobre os logs, com isso acontecendo por meio da função Analytics:
A seguir temos um exemplo de consulta, a qual filtrará as operações de acesso a dados consultando dados de bolsas de valores a partir das dependências registradas e que tenham levado um tempo de processamento superior a cinco milissegundos:
dependencies
| limit 1000
| where operation_Name == "GET Bolsas/Get" and duration > 5
| project timestamp, data, duration
O resultado da execução dessa instrução está na próxima imagem:
Ao analisar o código dessa consulta logo se percebe que o mesmo não segue o padrão SQL. De fato as consultas a dados do Application Insights seguem uma especificação própria, sendo que o link a seguir traz um guia de referência sobre como escrever tais queries: