O ServiceStack é um framework leve desenvolvido “sob” o ASP.NET que permite criar web services e aplicações web. Composto por vários serviços, podemos dizer que o ServiceStack é uma alternativa mais leve dos frameworks WCF, Web API, ASP.NET MVC.
Entre seus recursos podemos destacar:
- Web Services Framework: REST, SOAP e Message Queuing;
- JSON Serializer: Serialização automática de JSON, CSV e JSV;
- ORMLite: um micro framework ORM;
- Injeção de dependência;
- Logging API;
- Autenticação e autorização;
- C# Redis Client.
Geralmente os seus recursos são utilizados em conjunto, mas neste artigo iremos abordar apenas o ORMLite, como uma forma de introdução aos recursos do ServiceStack.
Curso C# (C Sharp) - Introdução ao ASP.NET Core
Conhecer o cursoServiceStack.OrmLite
O objetivo do OrmLite é fornecer um wrapper de configuração simples, DRY e agnóstico; que mantém uma alta afinidade com o SQL, expondo API intuitivas que geram expressões SQL para classes POCOs desconectadas.
Esta abordagem facilita o acesso aos dados, tornando óbvio qual é o SQL gerado, e quando é executado; enquanto mitiga qualquer comportamento inesperado, comuns em ORMs mais pesadas.
O OrmLite foi criado visando os seguintes objetivos:
- Fornecer um conjunto de métodos de extensão leves para as interfaces System.Data.*;
- Mapear classes POCO para tabelas do banco de dados, de forma clara, livre de convenções e necessidade de atributos;
- Criação e exclusão de tabelas utilizando apenas definições de classes POCO;
- Simplicidade: API de acesso simples;
- Alto desempenho: com suporte a índices, text, blobs, etc;
- Entre os mais rápidos micro ORM para .NET
- Poder e flexibilidade: com acesso a interface IDbCommand e expressões SQL;
- Multiplataforma: suporta vários bancos de dados (Atualmente: Sql Server, Sqlite, MySql, PostgreSQL, Firebird), tanto no .NET Standard quanto no .NET Core;
No OrmLite uma classe é igual a uma tabela. Não há nenhum comportamento escondido, a query criada pode até retornar resultados diferentes da classe POCO utilizada para criá-la, mas apenas se isso for a opção do desenvolvedor. Por exemplo, quando se quer listar apenas alguns campos da tabela.
Por padrão, tipos complexos (não escalares) são tratados como text ou blob. Mas a API também suporta relacionamentos, podemos persistir dados relacionados de forma simples.
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 AspNetCoreOrmLite:
dotnet new mvc -n AspNetCoreDapper
Agora, adicione o pacote do OrmLite:
dotnet add package ServiceStack.OrmLite.Sqlite.Core
Acima estou usando o pacote do banco de dados que irei utilizar neste artigo. Você pode ver aqui as demais versões disponíveis.
Não se esqueça de aplicar o restore no projeto:
dotnet restore
Com isso já podemos começar a nossa configuração OrmLite, iniciando pela criação da entidade/classe POCO.
Criando a classe POCO
Para este exemplo será utilizado a classe abaixo:
using System;
namespace AspNetCoreOrmLite.Models
{
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public int Quantity { get; set; }
public double Price { get; set; }
}
}
Configurando o acesso ao banco de dados
O OrmLite não define uma classe de configuração, o acesso ao banco de dados pode ser obtido com um objeto da classe OrmLiteConnectionFactory
:
var dbFactory = new OrmLiteConnectionFactory(
connectionString,
SqliteDialect.Provider);
Caso queria utilizar IOC, ela pode ser registrada como singleton:
container.Register<IDbConnectionFactory>(c =>
new OrmLiteConnectionFactory(connectionString, SqliteDialect.Provider));
Para o nosso exemplo, irei configurar o acesso com base em repositórios:
using System.Collections.Generic;
using Microsoft.Extensions.Configuration;
using ServiceStack.OrmLite;
namespace AspNetCoreOrmLite.Repositories
{
public abstract class AbstractRepository<T>
{
private string _connectionString;
private OrmLiteConnectionFactory _dbFactory;
protected OrmLiteConnectionFactory DbFactory => _dbFactory;
public AbstractRepository(IConfiguration configuration){
_connectionString = configuration.GetValue<string>("DBInfo:ConnectionString");
_dbFactory = new OrmLiteConnectionFactory(_connectionString, SqliteDialect.Provider);
}
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();
}
}
Note que no método construtor um objeto de DbFactory
foi criado:
_dbFactory = new OrmLiteConnectionFactory(_connectionString, SqliteDialect.Provider);
Por mais que seja aceito outro, o provider definido nesta classe deve ser do banco do pacote adicionado na aplicação. Caso seja referenciado outro banco, um erro será apresentado.
Agora para finalizar a configuração, vamos definir o repositório abaixo:
namespace AspNetCoreOrmLite.Repositories
{
public class ProductRepository: AbstractRepository
{
public ProductRepository(IConfiguration configuration): base(configuration) { }
public override void Add(Product item)
{
using (var db = DbFactory.Open())
{
if (db.CreateTableIfNotExists())
{
db.Insert(item);
}
}
}
public override void Remove(int id)
{
using (var db = DbFactory.Open())
{
db.Delete(p => p.Id == id);
}
}
public override void Update(Product item)
{
using (var db = DbFactory.Open())
{
db.Update(item);
}
}
public override Product FindByID(int id)
{
using (var db = DbFactory.Open())
{
return db.SingleById(id);
}
}
public override IEnumerable FindAll()
{
if (db.CreateTableIfNotExists())
{
return db.Select();
}
return db.Select();
}
}
}
Note que o OrmLite gera as queries SQL com base na classe POCO informadas nos seus métodos genéricos:
using (var db = DbFactory.Open())
{
db.Delete(p => p.Id == id);
}
Ou de acordo com o parâmetro do método:
using (var db = DbFactory.Open())
{
db.Insert(item);
}
Quando há retorno de dados:
using (var db = DbFactory.Open())
{
return db.SingleById(id);
}
Eles podem ser atribuídos a um objeto da classe POCO como acima, ou a outro objeto caso seja filtrado:
var q = db.From()
.Where(x => x.Quantity new { x.Id, x.Name });
Dictionary results = db.Dictionary(q);
Acima também é possível reparar que o filtro pode ser criado com LINQ e a query resultante passada para o OrmLite.
Ou isso pode ser definido como uma query SQL:
var tracks = db.Select<Track>("SELECT * FROM track WHERE Artist = @artist AND Album = @album", new { artist = "Nirvana", album = "Heart Shaped Box" });
Há muitas outras opções de métodos e criação de queries, você pode ver no repositório do framework.
Usando o OrmLite
Para exemplificar o uso do OrmLite, crie o controller abaixo:
using System.Linq;
using AspNetCoreOrmLite.Models;
using AspNetCoreOrmLite.Repositories;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
namespace AspNetCoreOrmLite.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:
Você pode baixar o código desta aplicação clicando aqui.