ASP.NET Core - Padrão de Repositório MongoDB e Unidade de Trabalho

4/23/2019

A implementação de repositórios genéricos e Unit Of Work são tarefas complexas no MongoDB. A falta de mecanismos no componente dificultam sua adoção. Veja neste artigo como implementar.

Unidade de Trabalho

O Unit Of Work é um padrão usado para agrupar uma ou mais operações (geralmente operações de banco de dados) em uma única transação ou “unidade de trabalho”, de modo que todas as operações sejam aprovadas ou reprovadas como uma só.

Padrão de Repositório

Repositórios são classes ou componentes que encapsulam a lógica necessária para acessar o banco de dados (repositório). Centralizam a funcionalidade comum de acesso a dados. Proporcionando melhor manutenção e desacoplando a infraestrutura da camada de domínio.

Benefícios

Desacoplamento e reusabilidade de código.

Criar um Repository Pattern com Generics no .NET é permite implementar as operações de CRUD com muito pouco esforço e código.

Juntamente com o Unit Of Work, caso aconteça algum erro no fluxo da operação, evita que seja feita qualquer modificação no banco.

E esse controle está na aplicação ao invés de abrir transações no banco e evitar locks nas tabelas.

E o MongoDB onde fica?

O MongoDB não tem controle de transações no driver do .NET. Com a popularidade do MongoDB cada vez maior, mais e mais sistemas estão sendo implementados com ele. Logo este artigo vai abordar como implementar um.

Implementação

Para esta implementação, será necessário os componentes MongoDB.Driver e ServiceStack.Core.

Para instalar os componentes, abra o Package Manager (View > Other Windows > Package Manager Console) e digite os comandos:

Install-Package ServiceStack.CoreInstall-Package MongoDB.Driver
 
Repositório Genérico

Para implementar o padrão Repository Pattern, será utilizado uma classe com generics abstrata com as operações de CRUD.

Interface

interface pública IRepository < TEntity >: IDisposable onde TEntity : class
{
Adicionar tarefa ( obj TEntity );
Tarefa < TEntity > GetById ( id do Guid );
Tarefa < IEnumerable < TEntity >> GetAll ();
Atualização de tarefas ( obj TEntity );
Remover Tarefa ( id do Guid );
}

Implementação

public abstract class BaseRepository < TEntity >: IRepository < TEntity > onde TEntity : class
{
protegido somente leitura IMongoContext _context ;
protegido somente leitura IMongoCollection < TEntity > DbSet ;

protegido BaseRepository ( contexto IMongoContext )
{
_context = contexto ;
DbSet = _context . GetCollection < TEntity > ( typeof ( TEntity ). Name );
}

public virtual Task Add(TEntity obj)
{
return _context . AddCommand ( assíncrona () => aguardam DbSet . InsertOneAsync ( obj ));
}

public virtual async Task < TEntity > GetById ( Guid id )
{
var data = aguarda DbSet . FindAsync ( Builders < TEntity >. Filter . Eq ( " _id " , id ));
dados de retorno . FirstOrDefault ();
}

tarefa assíncrona virtual pública < IEnumerable < TEntity >> GetAll () 
{
var all = aguarda DbSet . FindAsync ( Builders < TEntity >. Filter . Empty );
devolver tudo . ToList ();
}

public virtual Task Update(TEntity obj)
{
return _context . AddCommand ( async () =>
{
aguarde DbSet . ReplaceOneAsync ( Builders < TEntity >. Filter . Eq ( " _id " , obj . GetId ()), obj );
});
}

Remoção de tarefa virtual pública ( id do Guid ) => _context . AddCommand (() => DbSet . DeleteOneAsync ( Builders < TEntity >. Filter . Eq ( " _id " , id ))); 

public void Dispose()
{
GC . SuppressFinalize ( this );
}
}

Cada Model, terá sua própria implementação. Por exemplo uma classe Product:

pública de interface IProductRepository : IRepository < produtos >
{
}

// Implementação
public class ProductRepository : BaseRepository<Product>, IProductRepository
{
public ProductRepository(IMongoContext context) : base(context)
{
}
}
Unidade de Trabalho

O Unit Of Work será responsável por executar as transações que os Repositories fizeram. Para que esse trabalho seja feito, uma Context do Mongo deverá ser criada. Esta Context será a conexão entre o Repository e o UoW.

Contexto Mongo
public class MongoContext : IMongoContext
{
banco de dados IMongoDatabase privado { get ; definir ; }
public MongoClient MongoClient { get ; definir ; }
private readonly List < Func < Task >> _commands ;
Sessão IClientSessionHandle pública { get ; definir ; }
public MongoContext ( configuração IConfiguration )
{
// Definir Guid para o estilo CSharp (com traço -)
BsonDefaults . GuidRepresentation = GuidRepresentation . CSharpLegacy ;

// Todo comando será armazenado e processado em SaveChanges
_comandos = nova Lista < Func < Tarefa >> ();

RegisterConventions();

// Configure o mongo (você pode injetar a configuração, apenas para simplificar)
MongoClient = new MongoClient ( Environment . GetEnvironmentVariable ( " MONGOCONNECTION " ) ?? configuração . GetSection ( " MongoSettings " ). GetSection ( " Connection " ). Value );

Banco de dados = MongoClient . GetDatabase ( Environment . GetEnvironmentVariable ( " DATABASENAME " ) ?? configuração . GetSection ( " MongoSettings " ). GetSection ( " DatabaseName " ). Value );

}



private void RegisterConventions ()
{
var pack = new ConventionPack
{
new IgnoreExtraElementsConvention ( true ),
novo IgnoreIfDefaultConvention ( true )
};
ConventionRegistry . Register ( " My Solution Conventions " , pack , t => true );
}

public async Task < int > SaveChanges ()
{
utilizando ( Sessão = Await MongoClient . StartSessionAsync ())
{
Sessão . StartTransaction ();

var commandTasks = _commands . Selecione ( c => c ());

aguarde a tarefa . WhenAll ( commandTasks );

aguardar a sessão . CommitTransactionAsync ();
}

return _commands . Contar ;
}

public IMongoCollection < T > GetCollection < T > ( nome da string )
{
return Database . GetCollection < T > ( nome );
}

public void Dispose()
{
while ( Session ! = null && Session . IsInTransaction )
Tópico . Sleep ( TimeSpan . FromMilliseconds ( 100 ));

GC . SuppressFinalize ( this );
}

public void AddCommand ( Func < Task > func )
{
_commands . Adicionar ( função );
}
}
UoW implementação
public interface IUnitOfWork : IDisposable
{
bool Commit ();
}

public class UnitOfWork : IUnitOfWork
{
private readonly IMongoContext _context ;

public UnitOfWork(IMongoContext context)
{
_context = contexto ;
}

public bool Commit()
{
return _context . SaveChanges () > 0 ;
}

public void Dispose()
{
_context . Dispose ();
}
}
Configurando o Startup.cs

Para finalizar a configuração, abra o Startup.cs do projeto e adicione as configurações do DI.

public void ConfigureServices ( serviços IServiceCollection )
{
serviços . AddMvc (). SetCompatibilityVersion ( CompatibilityVersion . Version_2_2 );
serviços . AddScoped < IMongoContext , MongoContext > ();
serviços . AddScoped < IUnitOfWork , UnitOfWork > ();
serviços . AddScoped < IProductRepository , ProductRepository > ();
}