Como a maioria dos desenvolvedores .NET, quase todas as minhas aplicações fazem uso do Entity Framework. Fazer parte do ecossistema, ser integrado ao Visual Studio, possuir vários recursos, são sempre vantagens que nos leva a optar por seu uso.
Mas, as vezes, mesmo estas vantagens podem aborrecer. Pois quando que ser algo fora do padrão, o processo pode ser muito verboso ou caso o projeto seja muito simples, caímos na situação de matar uma barata com um canhão.
Para estes casos, felizmente, temos os micro-frameworks ORM. Em artigos passados, já falei sobre o Dapper e o ServiceStack.OrmLite.
No artigo de hoje abordarei o PetaPoco.
Curso C# (C Sharp) - ASP.NET MVC
Conhecer o cursoPetaPoco
O PetaPoco é um micro-framework ORM, criado por Brad Robinson, da topten software, que agora é mantido pelo grupo Collaborating Platypus.
Focado em rapidez e facilidade, o PetaPoco é uma solução de um arquivo, por isso é pequeno e não possui dependências. Como o nome sugere, trabalha com classes POCO, que requerem pouca configuração. Para facilitar, também inclui templates T4, que geram automaticamente classes POCO e suporta configurações Fluent.
No momento da criação deste artigo, ele suporta os bancos: SQL Server, SQL Server CE, Microsoft Access, SQLite, MySQL, MariaDB, Firebird e PostgreSQL (Oracle é suportado, mas não possui testes de integração).
Para exemplificar o seu uso, vamos ver um exemplo dele em uma aplicação ASP.NET Core.
Criando a aplicação
No terminal, digite o código abaixo para criar uma aplicação chamada AspNetCorePetaPoco:
dotnet new mvc -n AspNetCorePetaPoco
Acesse a pasta da aplicação e adicione nela o pacote do PetaPoco:
dotnet add package PetaPoco.Core.Compiled
Por fim, é necessário adicionar a biblioteca do provedor que iremos utilizar:
dotnet add package MySql.Data
Não esqueça de aplicar o restore:
dotnet restore
Agora podemos começar a configuração do PetaPoco.
Criando a classe POCO
Para esta entidade será utilizado a classe POCO (aka Model/Entidade) abaixo:
using System;
namespace AspNetCorePetaPoco.Models
{
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public int Quantity { get; set; }
public double Price { get; set; }
}
}
Caso queria modificar o comportamento padrão, pode ser utilizado fluent, ou definir os atributos decoradores, por exemplo:
[TableName("Pessoas")]
[PrimaryKey("Id", AutoIncrement = false)]
public class Pessoa
{
[Column]
public Guid Id { get; set; }
[Column]
public string Nome { get; set; }
[Column]
public int Idade { get; set; }
[Column]
public int Altura { get; set; }
[Column(Name = "Data Nascimento")]
public DateTime? Nascimento { get; set; }
[Ignore]
public string NomeIdade => $"{Nome} tem a idade {Idade}";
}
Neste arquivo, me aterei a classe Product
.
Configurando o acesso ao banco de dados
Assim como os outros micro-ORM, o PetaPoco não define uma classe de configuração, o acesso ao banco de dados pode ser obtido através do método DataBase
da classe PetaPoco
:
var db = new PetaPoco.Database("connectionStringName");
Ou através de fluent:
var db = DatabaseConfiguration.Build()
.UsingConnectionString("Server=127.0.0.1;Uid=root;Pwd=1234;Database=PetaPocoExample;Port=3306")
.UsingProvider<MySqlDatabaseProvider>()
.UsingDefaultMapper<ConventionMapper>(m =>
{
m.InflectTableName = (inflector, s) => inflector.Pluralise(inflector.Underscore(s));
m.InflectColumnName = (inflector, s) => inflector.Underscore(s);
})
.Create();
Ele também não cria as estruturas no banco de dados, então elas devem ser criadas “na mão”. Neste exemplo, estou definindo o código de criação da tabela em um arquivo chamado mysql_init.sql, que foi salvo em uma pasta chamada Scripts e contém o conteúdo abaixo:
CREATE TABLE IF NOT EXISTS Product (
id bigint AUTO_INCREMENT NOT NULL,
name varchar(500) NOT NULL,
quantity int NOT NULL,
price decimal(10,2) NOT NULL,
PRIMARY KEY (id)
) ENGINE=INNODB;
Agora, iremos definir a execução deste script e o acesso ao banco, em um repositório abstrato:
using System.Collections.Generic;
using Microsoft.Extensions.Configuration;
using PetaPoco;
using PetaPoco.Providers;
namespace AspNetCorePetaPoco.Repositories
{
public abstract class AbstractRepository<T>
{
private string _connectionString;
private IDatabase _db;
protected IDatabase Db => _db;
public AbstractRepository(IConfiguration configuration){
_connectionString = configuration.GetValue<string>("DBInfo:ConnectionString");
_db = DatabaseConfiguration.Build()
.UsingConnectionString(_connectionString)
.UsingProvider<MySqlDatabaseProvider>()
.Create();
_db.Execute(LoadTextResource("mysql_init.sql"));
}
public abstract void Add(T item);
public abstract void Remove(int id);
public abstract void Update(T item);
public abstract T FindByID(int id);
public abstract IEnumerable<T> FindAll();
private string LoadTextResource(string name)
{
var path = System.IO.Path.Combine(System.IO.Directory.GetCurrentDirectory(), "Scripts", name);
System.Console.WriteLine(path);
System.IO.StreamReader r = new System.IO.StreamReader(path);
string str = r.ReadToEnd();
r.Close();
return str;
}
}
}
Note que no método construtor um objeto de _Db
foi criado:
_db = DatabaseConfiguration.Build()
.UsingConnectionString(_connectionString)
.UsingProvider()
.Create();
Acima, é importante que o Provider reflita corretamente a biblioteca de acesso instalada.
Agora para finalizar a configuração, vamos definir o repositório abaixo:
using System.Collections.Generic;
using Microsoft.Extensions.Configuration;
using PetaPoco;
using PetaPoco.Providers;
using AspNetCorePetaPoco.Models;
namespace AspNetCorePetaPoco.Repositories
{
public class ProductRepository: AbstractRepository
{
public ProductRepository(IConfiguration configuration): base(configuration) { }
public override void Add(Product item)
{
Db.Insert(item);
}
public override void Remove(int id)
{
var product = Db.Single(id);
Db.Delete(product);
}
public override void Update(Product item)
{
Db.Update(item);
}
public override Product FindByID(int id)
{
return Db.Single(id);
}
public override IEnumerable FindAll()
{
return Db.Query("SELECT * FROM Products");
}
}
}
Note que o PelaPoco gera as queries SQL com base na classe POCO informada no parâmetro dos métodos:
Db.Insert(item);
E quando ele não consegue gerar, deve ser informado a query:
return Db.Query("SELECT * FROM Products");
Usando o PetaPoco
Para exemplificar o uso do PetaPoco, crie o controller abaixo:
using System.Linq;
using AspNetCorePetaPoco.Models;
using AspNetCorePetaPoco.Repositories;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
namespace AspNetCorePetaPoco.Controllers
{
public class ProductController : Controller
{
private readonly ProductRepository productRepository;
public ProductController(IConfiguration configuration){
productRepository = new ProductRepository(configuration);
}
// GET: Products
public ActionResult Index()
{
return View(productRepository.FindAll().ToList());
}
// GET: Products/Details/5
public ActionResult Details(int? id)
{
if (id == null)
{
return StatusCode(StatusCodes.Status404NotFound);
}
Product product = productRepository.FindByID(id.Value);
if (product == null)
{
return StatusCode(StatusCodes.Status404NotFound);
}
return View(product);
}
// GET: Products/Create
public ActionResult Create()
{
return View();
}
// POST: Products/Create
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind("Id,Name,Quantity,Price")] Product product)
{
if (ModelState.IsValid)
{
productRepository.Add(product);
return RedirectToAction("Index");
}
return View(product);
}
// GET: Products/Edit/5
public ActionResult Edit(int? id)
{
if (id == null)
{
return StatusCode(StatusCodes.Status400BadRequest);
}
Product product = productRepository.FindByID(id.Value);
if (product == null)
{
return StatusCode(StatusCodes.Status404NotFound);
}
return View(product);
}
// POST: Products/Edit/5
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit([Bind("Id,Name,Quantity,Price")] Product product)
{
if (ModelState.IsValid)
{
productRepository.Update(product);
return RedirectToAction("Index");
}
return View(product);
}
// GET: Products/Delete/5
public ActionResult Delete(int? id)
{
if (id == null)
{
return StatusCode(StatusCodes.Status400BadRequest);
}
Product product = productRepository.FindByID(id.Value);
if (product == null)
{
return StatusCode(StatusCodes.Status404NotFound);
}
return View(product);
}
// POST: Products/Delete/5
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public ActionResult DeleteConfirmed(int id)
{
productRepository.Remove(id);
return RedirectToAction("Index");
}
}
}
Também crie as views para as actions acima e então podemos ver o sistema funcionando: