Este artigo aborda a persistência de arquivos de imagem diretamente em uma base de dados relacional, utilizando MySQL como sistema de armazenamento e ASP.NET Core como plataforma de desenvolvimento.
Estrutura da Tabela para Armazenamento Binário
Para guardar dados binários de imagens, o MySQL oferece tipos específicos com diferentes limites de capacidade:
| Tipo | Tamanho Máximo |
|---|---|
| TINYBLOB | 255 bytes |
| BLOB | 65 KB |
| MEDIUMBLOB | 16 MB |
| LONGBLOB | 4 GB |
Para a maioria dos cenários com fotos de média resolução, MEDIUMBLOB é adequado. A definição da tabela:
CREATE TABLE galeria_imagens (
id INT AUTO_INCREMENT PRIMARY KEY,
entidade_id INT NOT NULL,
dados_imagem MEDIUMBLOB NOT NULL,
ativo BOOLEAN DEFAULT TRUE,
criado_em TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
Interface de Upload no Frontend
O formulário HTML utiliza enctype="multipart/form-data" para permitir o envio de arquivos:
<form method="post" enctype="multipart/form-data" asp-action="SalvarGaleria">
<div class="upload-area">
<label for="arquivosFotos" class="botao-upload">
<input type="file"
accept="image/*"
name="ArquivosFotos"
id="arquivosFotos"
multiple
class="input-oculto"/>
<img src="/assets/icone-upload.svg" alt="Adicionar fotos"/>
</label>
</div>
<div id="previsualizacao" class="container-miniaturas"></div>
</form>
Modelo e Processamento no Backend
A propriedade para receber os arquivos na classe de modelo:
public class GaleriaViewModel
{
public List<IFormFile> ArquivosFotos { get; set; }
public List<byte[]> DadosImagensProcessadas { get; set; }
}
O controller recebe os dados e inicia o processamento:
[HttpPost]
public async Task<IActionResult> SalvarGaleria(GaleriaViewModel dados)
{
var resultado = new ResultadoOperacao();
dados.DadosImagensProcessadas = await GerarMiniaturas(
dados.ArquivosFotos,
largura: 256,
altura: 256
);
resultado.Sucesso = await _servicoGaleria.PersistirImagens(dados);
return Json(resultado);
}
Geração de Miniaturas em Paralelo
O processamento de múltiplas imagens utiliza paralelismo para melhor performence:
private async Task<List<byte[]>> GerarMiniaturas(
List<IFormFile> arquivos,
int largura,
int altura)
{
var miniaturas = new ConcurrentBag<byte[]>();
if (arquivos == null || arquivos.Count == 0)
return new List<byte[]>();
var opcoesParalelas = new ParallelOptions
{
MaxDegreeOfParallelism = Environment.ProcessorCount / 2
};
Parallel.ForEach(arquivos.Take(12), opcoesParalelas, arquivo =>
{
try
{
if (arquivo?.Length > 0)
{
using var fluxoEntrada = new MemoryStream();
arquivo.CopyTo(fluxoEntrada);
fluxoEntrada.Position = 0;
using var imagemOriginal = Image.FromStream(fluxoEntrada);
using var imagemRedimensionada = new Bitmap(largura, altura);
using (var graficos = Graphics.FromImage(imagemRedimensionada))
{
graficos.InterpolationMode = InterpolationMode.HighQualityBicubic;
graficos.DrawImage(
imagemOriginal,
new Rectangle(0, 0, largura, altura)
);
}
using var fluxoSaida = new MemoryStream();
imagemRedimensionada.Save(fluxoSaida, ImageFormat.Jpeg);
miniaturas.Add(fluxoSaida.ToArray());
}
}
catch (Exception erro)
{
_registrador.LogError(erro, "Falha ao processar imagem");
}
});
return miniaturas.ToList();
}
Persistência com Dapper
A inserção das imagens processadas na base de dados:
public async Task<bool> PersistirImagens(GaleriaViewModel dados)
{
const string sqlInsercao = @"
INSERT INTO galeria_imagens (entidade_id, dados_imagem, ativo)
VALUES (@idEntidade, @bytesImagem, @estaAtivo);
SELECT LAST_INSERT_ID();";
using var conexao = ObterConexao();
await conexao.OpenAsync();
using var transacao = conexao.BeginTransaction();
try
{
foreach (var bytesImagem in dados.DadosImagensProcessadas)
{
var parametros = new
{
idEntidade = dados.EntidadeId,
bytesImagem = bytesImagem,
estaAtivo = true
};
await conexao.ExecuteAsync(
sqlInsercao,
parametros,
transacao
);
}
transacao.Commit();
return true;
}
catch (Exception erro)
{
_registrador.LogError(erro, "Erro na persistência das imagens");
transacao.Rollback();
throw;
}
}
Recuperação e Exibição
Para apresentar as imagens armazenadas, converte-se os bytes para Base64:
public List<string> ConverterParaBase64(List<byte[]> imagensBinarias)
{
return imagensBinarias
.Select(bytes => Convert.ToBase64String(bytes))
.ToList();
}
Na view Razor, a exibição utiliza Data URI:
<div class="galeria-fotos">
@foreach (var imagemBase64 in Model.ImagensEmBase64)
{
<div class="item-galeria"
style="background-image: url('data:image/jpeg;base64,@imagemBase64')">
</div>
}
</div>
Considerações sobre Performance
Armazenar imagens diretamente no banco de dados oferece integridade referencial e backup simplificdao, porém aumenta o tamanho da base de dados. Para aplicações com grande volume de mídia, considere:
- Armazenar apenas metadados no banco e arquivos em serviço de storage (S3, Azure Blob)
- Implementar cache em CDN para imagens frequentemente acessadas
- Comprimir imagens antes da persistência
- Limitar dimensões máximas aceitas no upload