Utilizando o Micro ORM ServiceStack.OrmLite em uma aplicação ASP.NET Core

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.

ServiceStack.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:

Aplicação AspNet Core OrmLite

Você pode baixar o código desta aplicação clicando aqui.

Deixe seu comentário

Instrutor, nerd, cinéfilo e desenvolvedor nas horas vagas. Graduado em Ciências da Computação pela Universidade Metodista de São Paulo.

JUNTE-SE A MAIS DE 150.000 PROGRAMADORES