RepositoryBase
La clase RepositoryBase
es una clase base abstracta para la implementación de repositorios que interactúan con MongoDB. Proporciona una base sólida para realizar operaciones CRUD comunes y transacciones, simplificando la gestión de entidades en una aplicación .NET.
¿Cómo Funciona?
RepositoryBase
funciona como un punto de acceso centralizado a las colecciones de MongoDB. Utiliza IMongoClient
para conectarse a la base de datos y proporciona métodos genéricos para realizar operaciones como la creación, lectura, actualización, y eliminación de entidades (IEntityBase
). También incluye soporte para transacciones, criterios de filtrado y proyección. La clase está diseñada para ser heredada por clases de repositorios concretos, que especifican el tipo de entidad con el que trabajan.
Métodos
Esta sección describe los métodos principales proporcionados por la clase RepositoryBase
.
GetCollection
Type: public IMongoCollection<TEntity> GetCollection<TEntity>() where TEntity : class, IEntityBase
Devuelve una instancia de IMongoCollection<TEntity>
para el tipo de entidad especificado, permitiendo el acceso a las operaciones de la base de datos sobre esta colección. La colección se obtiene de la base de datos de MongoDB configurada.
public Task CustomMethodAsync(){ var collection = this.GetCollection<UserEntity>();
// Do something with the collection
return Task.CompletedTask;}
ChangeStateAsync
Type: public Task ChangeStateAsync<TEntity>(Guid id, bool state, CancellationToken cancellationToken) where TEntity : class, IEntity
Cambia el estado (activo/inactivo) de una entidad identificada por su Guid
de forma asíncrona. Adicionalmente, actualiza la propiedad UpdatedAt
con la fecha y hora actual. Se utiliza comúnmente para habilitar o deshabilitar entidades.
// Change stateawait repository.ChangeStateAsync<UserEntity>(user.Id, false, CancellationToken.None);
CreateAsync
Type: public Task CreateAsync<TEntity>(TEntity entity, CancellationToken cancellationToken) where TEntity : class, IEntityBase
Inserta una nueva entidad en la colección de forma asíncrona. Este método toma la entidad a crear y un token de cancelación.
// Create a new userawait repository.CreateAsync(user, CancellationToken.None);
CreateRangeAsync
Type: public Task CreateRangeAsync<TEntity>(List<TEntity> entities, CancellationToken cancellationToken) where TEntity : class, IEntityBase
Inserta un conjunto de nuevas entidades en la colección de forma asíncrona. Este método toma la lista de entidades a crear y un token de cancelación.
// Create some usersawait repository.CreateRangeAsync(users, CancellationToken.None);
DeleteAsync
Type: public Task DeleteAsync<TEntity>(FilterDefinition<TEntity> filter, CancellationToken cancellationToken) where TEntity : class, IEntityBase
Elimina una entidad de la colección que cumpla con el filtro especificado de forma asíncrona. Este método toma un filtro y un token de cancelación.
// Delete uservar filterUser = Builders<UserEntity>.Filter.Eq(x => x.Id, user.Id);
await repository.DeleteAsync(filterUser, CancellationToken.None);
DeleteRangeAsync
Type: public async Task DeleteRangeAsync<TEntity>(List<TEntity> entities, CancellationToken cancellationToken) where TEntity : class, IEntityBase
Elimina un conjunto de entidades especificadas por su id, de la colección de forma asíncrona. Este método toma la lista de entidades a eliminar y un token de cancelación.
// Delete some usersawait repository.DeleteRangeAsync(users, CancellationToken.None);
UpdateAsync
Type: public Task UpdateAsync<TEntity>(TEntity entity, CancellationToken cancellationToken) where TEntity : class, IEntityBase
Actualiza una entidad existente en la colección de forma asíncrona. Utiliza el Guid
de la entidad para buscar y reemplazar el documento en la base de datos.
// Update uservar userUpdate = await repository.FindAsync<UserEntity>(user.Id, CancellationToken.None);
userUpdate.Name = "John Doe Updated";
await repository.UpdateAsync(userUpdate, CancellationToken.None);
UpdateRangeAsync
Type: public async Task UpdateRangeAsync<TEntity>(List<TEntity> entities, CancellationToken cancellationToken) where TEntity : class, IEntityBase
Actualiza un conjunto de entidades existentes en la colección de forma asíncrona. Utiliza el Guid
de cada entidad para buscar y reemplazar los documentos en la base de datos.
// Update some usersvar usersUpdate = await repository.MatchingAsync<UserEntity>(criteria, CancellationToken.None);
usersUpdate.ForEach(x => x.Name = $"{x.Name} Updated");
await repository.UpdateRangeAsync(usersUpdate, CancellationToken.None);
TransactionAsync
Type: public async Task TransactionAsync(Func<IMongoDatabase, IClientSessionHandle, Task> process, CancellationToken cancellationToken)
Ejecuta una transacción en MongoDB, donde el process
es la lógica que se ejecutará dentro de la transacción. En caso de error, la transacción se revierte y se registra el error en el log.
// Transactionawait repository.TransactionAsync(async (database, session) =>{ var userTransaction = new UserEntity { Id = Guid.NewGuid(), Name = "John Doe Transaction", Email = "john.doe@codedesignplus.com" };
var collection = database.GetCollection<UserEntity>(typeof(UserEntity).Name);
await collection.InsertOneAsync(session, userTransaction, cancellationToken: CancellationToken.None);
}, CancellationToken.None);
FindAsync
Type: public Task<TEntity> FindAsync<TEntity>(Guid id, CancellationToken cancellationToken) where TEntity : class, IEntityBase
Busca una entidad por su identificador (Guid
) de forma asíncrona. Si se encuentra, devuelve la entidad; de lo contrario, devuelve null
.
// Find a uservar userFound = await repository.FindAsync<UserEntity>(user.Id, CancellationToken.None);
MatchingAsync
Type: public Task<List<TEntity>> MatchingAsync<TEntity>(C.Criteria criteria, CancellationToken cancellationToken) where TEntity : class, IEntityBase
Busca un conjunto de entidades que cumplan con los criterios proporcionados y los retorna de forma asíncrona. La búsqueda incluye la aplicación de filtros y ordenamiento.
// Criteris to find usersvar criteria = new Criteria{ Filters = "IsActive=true"};
var usersFound = await repository.MatchingAsync<UserEntity>(criteria, CancellationToken.None);
MatchingAsync
Type: public Task<List<TResult>> MatchingAsync<TEntity, TResult>(C.Criteria criteria, Expression<Func<TEntity, TResult>> projection, CancellationToken cancellationToken) where TEntity : class, IEntityBase
Busca un conjunto de entidades que cumplan con los criterios proporcionados, proyectando los resultados a un nuevo tipo y los retorna de forma asíncrona. La búsqueda incluye la aplicación de filtros, ordenamiento y la proyeccion.
// Criteria with projectionvar projection = await repository.MatchingAsync<UserEntity, UserEntity>(criteria, x => new UserEntity{ Id = x.Id, Name = x.Name, Email = x.Email}, CancellationToken.None);
MatchingAsync
Type: public async Task<List<TProjection>> MatchingAsync<TEntity, TProjection>(Guid id, C.Criteria criteria, Expression<Func<TEntity, List<TProjection>>> projection, CancellationToken cancellationToken) where TEntity : class, IEntityBase where TProjection : class, IEntityBase
Busca una entidad por su Guid
y proyecta una lista de propiedades desde la entidad con los criterios proporcionados.
// Criteria at subdocument level and projectionvar projectionSubdocument = await repository.MatchingAsync<UserEntity, ProductEntity>(user.Id, criteria, x => x.Products, CancellationToken.None);
Implementación
La clase RepositoryBase
se implementa en la librería CodeDesignPlus.Net.Mongo
. Proporciona una base sólida para la creación de repositorios que interactúan con MongoDB en aplicaciones .NET. A continuación, se muestra un fragmento de código que muestra la implementación de la clase RepositoryBase
:
using CodeDesignPlus.Net.Mongo.Extensions;
namespace CodeDesignPlus.Net.Mongo.Repository;
/// <summary>/// Base class for MongoDB repository operations./// </summary>/// <remarks>/// Initializes a new instance of the <see cref="RepositoryBase"/> class./// </remarks>/// <param name="serviceProvider">The service provider.</param>/// <param name="mongoOptions">The MongoDB options.</param>/// <param name="logger">The logger instance.</param>/// <exception cref="ArgumentNullException">Thrown when any of the parameters are null.</exception>public abstract class RepositoryBase(IServiceProvider serviceProvider, IOptions<MongoOptions> mongoOptions, ILogger logger) : IRepositoryBase{ private readonly MongoOptions mongoOptions = mongoOptions.Value;
/// <summary> /// Gets the MongoDB collection for the specified entity type. /// </summary> /// <typeparam name="TEntity">The type of the entity.</typeparam> /// <returns>The MongoDB collection.</returns> public IMongoCollection<TEntity> GetCollection<TEntity>() where TEntity : class, IEntityBase { var client = serviceProvider.GetRequiredService<IMongoClient>();
var database = client.GetDatabase(this.mongoOptions.Database);
return database.GetCollection<TEntity>(typeof(TEntity).Name); }
/// <summary> /// Changes the state of an entity by its identifier asynchronously. /// </summary> /// <typeparam name="TEntity">The type of the entity.</typeparam> /// <param name="id">The identifier of the entity.</param> /// <param name="state">The new state of the entity.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>A task that represents the asynchronous change state operation.</returns> /// <exception cref="ArgumentNullException">Thrown when the entity is null.</exception> public Task ChangeStateAsync<TEntity>(Guid id, bool state, CancellationToken cancellationToken) where TEntity : class, IEntity { return this.ChangeStateAsync<TEntity>(id, state, Guid.Empty, cancellationToken); }
/// <summary> /// Changes the state of an entity by its identifier asynchronously. /// </summary> /// <typeparam name="TEntity">The type of the entity.</typeparam> /// <param name="id">The identifier of the entity.</param> /// <param name="state">The new state of the entity.</param> /// <param name="tenant">The tenant identifier only for entities that inherit from <see cref="AggregateRoot"/>.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>A task that represents the asynchronous change state operation.</returns> /// <exception cref="ArgumentNullException">Thrown when the entity is null.</exception> public Task ChangeStateAsync<TEntity>(Guid id, bool state, Guid tenant, CancellationToken cancellationToken) where TEntity : class, IEntity { var collection = this.GetCollection<TEntity>();
var filter = Builders<TEntity>.Filter.Eq(e => e.Id, id).BuildFilter(tenant); var update = Builders<TEntity>.Update .Set(e => e.IsActive, state) .Set(e => e.UpdatedAt, SystemClock.Instance.GetCurrentInstant());
return collection.UpdateOneAsync(filter, update, cancellationToken: cancellationToken); }
/// <summary> /// Creates a new entity asynchronously. /// </summary> /// <typeparam name="TEntity">The type of the entity.</typeparam> /// <param name="entity">The entity to create.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>A task that represents the asynchronous create operation.</returns> /// <exception cref="ArgumentNullException">Thrown when the entity is null.</exception> public Task CreateAsync<TEntity>(TEntity entity, CancellationToken cancellationToken) where TEntity : class, IEntityBase { var collection = this.GetCollection<TEntity>();
return collection.InsertOneAsync(entity, cancellationToken: cancellationToken); }
/// <summary> /// Creates a range of new entities asynchronously. /// </summary> /// <typeparam name="TEntity">The type of the entity.</typeparam> /// <param name="entities">The entities to create.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>A task that represents the asynchronous create range operation.</returns> /// <exception cref="ArgumentNullException">Thrown when the entities are null.</exception> public Task CreateRangeAsync<TEntity>(List<TEntity> entities, CancellationToken cancellationToken) where TEntity : class, IEntityBase { var collection = this.GetCollection<TEntity>();
return collection.InsertManyAsync(entities, cancellationToken: cancellationToken); }
/// <summary> /// Determines whether an entity exists by its identifier asynchronously. /// </summary> /// <typeparam name="TEntity">The type of the entity.</typeparam> /// <param name="id">The identifier of the entity.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>A task that represents the asynchronous existence operation.</returns> public Task<bool> ExistsAsync<TEntity>(Guid id, CancellationToken cancellationToken) where TEntity : class, IEntityBase { return this.ExistsAsync<TEntity>(id, Guid.Empty, cancellationToken); }
/// <summary> /// Determines whether an entity exists by its identifier asynchronously. /// </summary> /// <typeparam name="TEntity">The type of the entity.</typeparam> /// <param name="id">The identifier of the entity.</param> /// <param name="tenant">The tenant identifier only for entities that inherit from <see cref="AggregateRoot"/>.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>A task that represents the asynchronous existence operation.</returns> public Task<bool> ExistsAsync<TEntity>(Guid id, Guid tenant, CancellationToken cancellationToken) where TEntity : class, IEntityBase { var collection = this.GetCollection<TEntity>();
var filter = Builders<TEntity>.Filter.Eq(e => e.Id, id).BuildFilter(tenant);
return collection.Find(filter).AnyAsync(cancellationToken); }
/// <summary> /// Deletes an entity by its filter asynchronously. /// </summary> /// <typeparam name="TEntity">The type of the entity.</typeparam> /// <param name="id">The identifier of the entity.</param> /// <param name="tenant">The tenant identifier only for entities that inherit from <see cref="AggregateRoot"/>.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>A task that represents the asynchronous delete operation.</returns> public Task DeleteAsync<TEntity>(Guid id, Guid tenant, CancellationToken cancellationToken) where TEntity : class, IEntityBase { var filter = Builders<TEntity>.Filter.Eq(e => e.Id, id).BuildFilter(tenant);
return this.DeleteAsync(filter, tenant, cancellationToken); }
/// <summary> /// Deletes an entity by its filter asynchronously. /// </summary> /// <typeparam name="TEntity">The type of the entity.</typeparam> /// <param name="filter">The filter definition.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>A task that represents the asynchronous delete operation.</returns> public Task DeleteAsync<TEntity>(FilterDefinition<TEntity> filter, CancellationToken cancellationToken) where TEntity : class, IEntityBase { return this.DeleteAsync(filter, Guid.Empty, cancellationToken); }
/// <summary> /// Deletes an entity by its filter asynchronously. /// </summary> /// <typeparam name="TEntity">The type of the entity.</typeparam> /// <param name="filter">The filter definition.</param> /// <param name="tenant">The tenant identifier only for entities that inherit from <see cref="AggregateRoot"/>.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>A task that represents the asynchronous delete operation.</returns> public Task DeleteAsync<TEntity>(FilterDefinition<TEntity> filter, Guid tenant, CancellationToken cancellationToken) where TEntity : class, IEntityBase { var collection = this.GetCollection<TEntity>();
return collection.DeleteOneAsync(filter.BuildFilter(tenant), cancellationToken); }
/// <summary> /// Deletes a range of entities asynchronously. /// </summary> /// <typeparam name="TEntity">The type of the entity.</typeparam> /// <param name="entities">The entities to delete.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>A task that represents the asynchronous delete range operation.</returns> public Task DeleteRangeAsync<TEntity>(List<TEntity> entities, CancellationToken cancellationToken) where TEntity : class, IEntityBase { return this.DeleteRangeAsync(entities, Guid.Empty, cancellationToken); }
/// <summary> /// Deletes a range of entities asynchronously. /// </summary> /// <typeparam name="TEntity">The type of the entity.</typeparam> /// <param name="entities">The entities to delete.</param> /// <param name="tenant">The tenant identifier only for entities that inherit from <see cref="AggregateRoot"/>.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>A task that represents the asynchronous delete range operation.</returns> public async Task DeleteRangeAsync<TEntity>(List<TEntity> entities, Guid tenant, CancellationToken cancellationToken) where TEntity : class, IEntityBase { foreach (var entity in entities) { var filter = Builders<TEntity>.Filter.Eq(e => e.Id, entity.Id).BuildFilter(tenant);
await this.DeleteAsync(filter, tenant, cancellationToken); } }
/// <summary> /// Updates an entity asynchronously. /// </summary> /// <typeparam name="TEntity">The type of the entity.</typeparam> /// <param name="entity">The entity to update.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>A task that represents the asynchronous update operation.</returns> /// <exception cref="ArgumentNullException">Thrown when the entity is null.</exception> public Task UpdateAsync<TEntity>(TEntity entity, CancellationToken cancellationToken) where TEntity : class, IEntityBase { var collection = this.GetCollection<TEntity>();
FilterDefinition<TEntity> filter = Builders<TEntity>.Filter.Eq(e => e.Id, entity.Id);
return collection.ReplaceOneAsync(filter, entity, cancellationToken: cancellationToken); }
/// <summary> /// Updates a range of entities asynchronously. /// </summary> /// <typeparam name="TEntity">The type of the entity.</typeparam> /// <param name="entities">The entities to update.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>A task that represents the asynchronous update range operation.</returns> /// <exception cref="ArgumentNullException">Thrown when the entities are null.</exception> public async Task UpdateRangeAsync<TEntity>(List<TEntity> entities, CancellationToken cancellationToken) where TEntity : class, IEntityBase { foreach (var entity in entities) { await this.UpdateAsync(entity, cancellationToken); } }
/// <summary> /// Executes a transaction asynchronously. /// </summary> /// <param name="process">The process to execute within the transaction.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>A task that represents the asynchronous transaction operation.</returns> /// <exception cref="ArgumentNullException">Thrown when the process is null.</exception> public async Task TransactionAsync(Func<IMongoDatabase, IClientSessionHandle, Task> process, CancellationToken cancellationToken) { var client = serviceProvider.GetRequiredService<IMongoClient>();
var database = client.GetDatabase(this.mongoOptions.Database);
using var session = await client.StartSessionAsync(cancellationToken: cancellationToken);
session.StartTransaction();
try { await process(database, session);
await session.CommitTransactionAsync(cancellationToken); } catch (Exception ex) { await session.AbortTransactionAsync(cancellationToken);
logger.LogError(ex, "Failed to execute transaction"); } }
/// <summary> /// Finds an entity by its identifier asynchronously. /// </summary> /// <typeparam name="TEntity">The type of the entity.</typeparam> /// <param name="id">The identifier of the entity.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>A task that represents the asynchronous find operation.</returns> public Task<TEntity> FindAsync<TEntity>(Guid id, CancellationToken cancellationToken) where TEntity : class, IEntityBase { return this.FindAsync<TEntity>(id, Guid.Empty, cancellationToken); }
/// <summary> /// Finds an entity by its identifier asynchronously. /// </summary> /// <typeparam name="TEntity">The type of the entity.</typeparam> /// <param name="id">The identifier of the entity.</param> /// <param name="tenant">The tenant identifier only for entities that inherit from <see cref="AggregateRoot"/>.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>A task that represents the asynchronous find operation.</returns> public Task<TEntity> FindAsync<TEntity>(Guid id, Guid tenant, CancellationToken cancellationToken) where TEntity : class, IEntityBase { var collection = this.GetCollection<TEntity>();
var filter = Builders<TEntity>.Filter.Eq(e => e.Id, id).BuildFilter(tenant);
return collection.Find(filter).FirstOrDefaultAsync(cancellationToken); }
/// <summary> /// Finds entities matching the specified criteria asynchronously. /// </summary> /// <typeparam name="TEntity">The type of the entity.</typeparam> /// <param name="criteria">The criteria to match.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>A task that represents the asynchronous matching operation.</returns> public Task<List<TEntity>> MatchingAsync<TEntity>(C.Criteria criteria, CancellationToken cancellationToken) where TEntity : class, IEntityBase { return this.MatchingAsync<TEntity>(criteria, Guid.Empty, cancellationToken); }
/// <summary> /// Finds entities matching the specified criteria asynchronously. /// </summary> /// <typeparam name="TEntity">The type of the entity.</typeparam> /// <param name="criteria">The criteria to match.</param> /// <param name="tenant">The tenant identifier only for entities that inherit from <see cref="AggregateRoot"/>.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>A task that represents the asynchronous matching operation.</returns> public Task<List<TEntity>> MatchingAsync<TEntity>(C.Criteria criteria, Guid tenant, CancellationToken cancellationToken) where TEntity : class, IEntityBase { var query = Query<TEntity>(criteria, tenant);
return query.ToListAsync(cancellationToken); }
/// <summary> /// Finds entities matching the specified criteria and projects them to the specified result type asynchronously. /// </summary> /// <typeparam name="TEntity">The type of the entity.</typeparam> /// <typeparam name="TResult">The type of the result.</typeparam> /// <param name="criteria">The criteria to match.</param> /// <param name="projection">The projection expression.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>A task that represents the asynchronous matching operation.</returns> public Task<List<TResult>> MatchingAsync<TEntity, TResult>(C.Criteria criteria, Expression<Func<TEntity, TResult>> projection, CancellationToken cancellationToken) where TEntity : class, IEntityBase { return this.MatchingAsync(criteria, projection, Guid.Empty, cancellationToken); }
/// <summary> /// Finds entities matching the specified criteria and projects them to the specified result type asynchronously. /// </summary> /// <typeparam name="TEntity">The type of the entity.</typeparam> /// <typeparam name="TResult">The type of the result.</typeparam> /// <param name="criteria">The criteria to match.</param> /// <param name="projection">The projection expression.</param> /// <param name="tenant">The tenant identifier only for entities that inherit from <see cref="AggregateRoot"/>.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>A task that represents the asynchronous matching operation.</returns> public Task<List<TResult>> MatchingAsync<TEntity, TResult>(C.Criteria criteria, Expression<Func<TEntity, TResult>> projection, Guid tenant, CancellationToken cancellationToken) where TEntity : class, IEntityBase { var query = Query<TEntity>(criteria, tenant);
return query.Project(projection).ToListAsync(cancellationToken); }
/// <summary> /// Finds entities matching the specified criteria and projects them to the specified projection type asynchronously. /// </summary> /// <typeparam name="TEntity">The type of the entity.</typeparam> /// <typeparam name="TProjection">The type of the projection.</typeparam> /// <param name="id">The identifier of the entity.</param> /// <param name="criteria">The criteria to match.</param> /// <param name="projection">The projection expression.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>A task that represents the asynchronous matching operation.</returns> public Task<List<TProjection>> MatchingAsync<TEntity, TProjection>(Guid id, C.Criteria criteria, Expression<Func<TEntity, List<TProjection>>> projection, CancellationToken cancellationToken) where TEntity : class, IEntityBase where TProjection : class, IEntityBase { return this.MatchingAsync(id, criteria, projection, Guid.Empty, cancellationToken); }
/// <summary> /// Finds entities matching the specified criteria and projects them to the specified projection type asynchronously. /// </summary> /// <typeparam name="TEntity">The type of the entity.</typeparam> /// <typeparam name="TProjection">The type of the projection.</typeparam> /// <param name="id">The identifier of the entity.</param> /// <param name="criteria">The criteria to match.</param> /// <param name="projection">The projection expression.</param> /// <param name="tenant">The tenant identifier only for entities that inherit from <see cref="AggregateRoot"/>.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>A task that represents the asynchronous matching operation.</returns> public async Task<List<TProjection>> MatchingAsync<TEntity, TProjection>(Guid id, C.Criteria criteria, Expression<Func<TEntity, List<TProjection>>> projection, Guid tenant, CancellationToken cancellationToken) where TEntity : class, IEntityBase where TProjection : class, IEntityBase { var collection = this.GetCollection<TEntity>();
var propertyName = GetPropertyName(projection);
var filterCriteria = criteria.GetFilterExpression<TProjection>();
var filterDefinition = filterCriteria.ToFilterDefinition(true).BuildFilter(tenant);
var bsonFilter = ((BsonDocumentFilterDefinition<TProjection>)filterDefinition).Document;
var pipeline = new[] { new BsonDocument("$match", new BsonDocument("_id", new BsonBinaryData(id, GuidRepresentation.Standard))), new BsonDocument("$project", new BsonDocument { { propertyName, new BsonDocument("$filter", new BsonDocument { { "input", $"${propertyName}" }, { "as", "entity" }, { "cond", bsonFilter } }) } }), new BsonDocument("$unwind", new BsonDocument("path", $"${propertyName}")), new BsonDocument("$replaceRoot", new BsonDocument("newRoot", $"${propertyName}")) };
var cursor = await collection.AggregateAsync<BsonDocument>(pipeline, cancellationToken: cancellationToken).ConfigureAwait(false); var resultList = new List<BsonDocument>();
while (await cursor.MoveNextAsync(cancellationToken)) { resultList.AddRange(cursor.Current); }
return resultList.Select(doc => BsonSerializer.Deserialize<TProjection>(doc)).ToList(); }
/// <summary> /// Sorts the query based on the specified sort expression and order type. /// </summary> /// <typeparam name="TEntity">The type of the entity.</typeparam> /// <param name="criteria">The criteria containing the sort expression and order type.</param> /// <param name="tenant">The tenant identifier only for entities that inherit from <see cref="AggregateRoot"/>.</param> /// <returns>The sorted query.</returns> private IFindFluent<TEntity, TEntity> Query<TEntity>(C.Criteria criteria, Guid tenant) where TEntity : class, IEntityBase { var collection = this.GetCollection<TEntity>(); var filterExpression = criteria.GetFilterExpression<TEntity>(); var sortBy = criteria.GetSortByExpression<TEntity>();
var filter = filterExpression.ToFilterDefinition().BuildFilter(tenant);
var query = collection.Find(filter);
if (sortBy != null) if (criteria.OrderType == OrderTypes.Ascending) query = query.SortBy(sortBy); else query = query.SortByDescending(sortBy);
return query; }
/// <summary> /// Gets the property name from the specified projection expression. /// </summary> /// <typeparam name="TEntity">The type of the entity.</typeparam> /// <typeparam name="TResult">The type of the result.</typeparam> /// <param name="projection">The projection expression.</param> /// <returns>The property name.</returns> /// <exception cref="Exceptions.MongoException">Thrown when the expression is not a MemberExpression.</exception> private static string GetPropertyName<TEntity, TResult>(Expression<Func<TEntity, TResult>> projection) where TEntity : class, IEntityBase { if (projection.Body is MemberExpression memberExpression) return memberExpression.Member.Name; else throw new Exceptions.MongoException("The expression must be a MemberExpression."); }}
Ejemplo de Uso
Imaginemos que tenemos una entidad User
que queremos almacenar en una colección de MongoDB. Podemos crear un repositorio para esta entidad que herede de RepositoryBase
y definir los métodos específicos de la entidad. A continuación, se muestra un ejemplo de cómo usar RepositoryBase
en una aplicación .NET Core:
// See https://aka.ms/new-console-template for more informationusing CodeDesignPlus.Net.Core.Abstractions.Models.Criteria;using CodeDesignPlus.Net.Mongo.Extensions;using CodeDesignPlus.Net.Mongo.Sample.RepositoryBase;using CodeDesignPlus.Net.Mongo.Sample.RepositoryBase.DTOs;using CodeDesignPlus.Net.Mongo.Sample.RepositoryBase.Entities;using CodeDesignPlus.Net.Mongo.Sample.RepositoryBase.Respositories;using Microsoft.Extensions.Configuration;using Microsoft.Extensions.DependencyInjection;using MongoDB.Driver;
var configuration = new ConfigurationBuilder() .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) .Build();
var serviceCollection = new ServiceCollection();
serviceCollection.AddLogging();serviceCollection.AddMongo<Startup>(configuration);
var serviceProvider = serviceCollection.BuildServiceProvider();
var repository = serviceProvider.GetRequiredService<IUserRepository>();
var product = new ProductEntity{ Id = Guid.NewGuid(), Name = "Product 1", CreatedAt = SystemClock.Instance.GetCurrentInstant(), CreatedBy = Guid.NewGuid(), IsActive = true};
var tenant = Guid.NewGuid();var createdBy = Guid.NewGuid();
var user = UserAggregate.Create(Guid.NewGuid(), "John Doe", "john.doe@codedesignplus.com", tenant, createdBy);
user.AddProduct(product);
var users = new List<UserAggregate>{ UserAggregate.Create(Guid.NewGuid(), "Jane Doe", "jane.doe@codedesignplus.com", tenant, createdBy), UserAggregate.Create(Guid.NewGuid(), "John Smith", "john.smith@codedesignplus.com", tenant, createdBy)};
// Create a new userawait repository.CreateAsync(user, CancellationToken.None);
// Create some usersawait repository.CreateRangeAsync(users, CancellationToken.None);
// Change stateawait repository.ChangeStateAsync<UserAggregate>(user.Id, false, tenant, CancellationToken.None);
// Find a uservar userFound = await repository.FindAsync<UserAggregate>(user.Id, tenant, CancellationToken.None);
// Criteris to find usersvar criteria = new Criteria{ Filters = "IsActive=true"};
var usersFound = await repository.MatchingAsync<UserAggregate>(criteria, tenant, CancellationToken.None);
// Criteria with projectionvar projection = await repository.MatchingAsync<UserAggregate, UserDto>(criteria, x => new UserDto{ Id = x.Id, Name = x.Name, Email = x.Email}, tenant, CancellationToken.None);
// Criteria at subdocument level and projectionvar projectionSubdocument = await repository.MatchingAsync<UserAggregate, ProductEntity>(user.Id, criteria, x => x.Products, tenant, CancellationToken.None);
// Update uservar userUpdate = await repository.FindAsync<UserAggregate>(user.Id, tenant, CancellationToken.None);
userUpdate.UpdateName("John Doe Updated");
await repository.UpdateAsync(userUpdate, CancellationToken.None);
// Update some usersvar usersUpdate = await repository.MatchingAsync<UserAggregate>(criteria, tenant, CancellationToken.None);
usersUpdate.ForEach(x => x.UpdateName($"{x.Name} Updated"));
await repository.UpdateRangeAsync(usersUpdate, CancellationToken.None);
// Transactionawait repository.TransactionAsync(async (database, session) =>{ var userTransaction = UserAggregate.Create(Guid.NewGuid(), "John Doe Transaction", "john.doe@codedesignplus.com", tenant, createdBy);
var collection = database.GetCollection<UserAggregate>(typeof(UserAggregate).Name);
await collection.InsertOneAsync(session, userTransaction, cancellationToken: CancellationToken.None);
}, CancellationToken.None);
// Delete uservar filterUser = Builders<UserAggregate>.Filter.Eq(x => x.Id, user.Id);
await repository.DeleteAsync(filterUser, tenant, CancellationToken.None);
// Delete some usersawait repository.DeleteRangeAsync(users, tenant, CancellationToken.None);
-
Configuración de Mongo en
appsettings.json
:{"Core": {"Business": "CodeDesignPlus","AppName": "sample-mongo-repositorybase","Version": "v1","Description": "Sample of CodeDesignPlus.Net.Core","Contact": {"Name": "CodeDesignPlus","Email": "custom@outlook.com"}},"Mongo": {"Enable": true,"ConnectionString": "mongodb://localhost:27017","Database": "db_sample","RegisterAutomaticRepositories": true}} -
Creamos las entidades:
Procedemos a crear las entidades que representarán los documentos en la colección de MongoDB, estas entidades deben implementar la interfaz
IEntity
deCodeDesignPlus.Net.Core.Abstractions
.using System;using CodeDesignPlus.Net.Core.Abstractions;namespace CodeDesignPlus.Net.Mongo.Sample.RepositoryBase.Entities;public class ProductEntity : IEntity{public Guid Id { get; set; }public bool IsActive { get; set; }public Instant CreatedAt { get; set; }public Guid CreatedBy { get; set; }public Instant? UpdatedAt { get; set; }public Guid? UpdatedBy { get; set; }public required string Name { get; set; }public string? Description { get; set; }}using CodeDesignPlus.Net.Core.Abstractions;namespace CodeDesignPlus.Net.Mongo.Sample.RepositoryBase.Entities;public class UserAggregate(Guid id) : AggregateRoot(id){public string Name { get; private set; } = string.Empty;public string Email { get; private set; } = string.Empty;public List<ProductEntity> Products { get; private set; } = [];private UserAggregate(Guid id, string name, string email, Guid tenant, Guid createdBy): this(id){Name = name;Email = email;Tenant = tenant;IsActive = true;CreatedBy = createdBy;CreatedAt = SystemClock.Instance.GetCurrentInstant();}public static UserAggregate Create(Guid id, string name, string email, Guid tenant, Guid createdBy){return new UserAggregate(id, name, email, tenant, createdBy);}public void AddProduct(ProductEntity product){Products.Add(product);}public void UpdateName(string name){Name = name;}} -
Creamos el repositorio:
Para efectos de este ejemplo, procederemos a crear el repositorio
UserRepository
que hereda deRepositoryBase
.using CodeDesignPlus.Net.Mongo.Abstractions.Options;using CodeDesignPlus.Net.Mongo.Sample.RepositoryBase.Entities;using Microsoft.Extensions.Logging;using Microsoft.Extensions.Options;namespace CodeDesignPlus.Net.Mongo.Sample.RepositoryBase.Respositories;public class UserRepository(IServiceProvider serviceProvider, IOptions<MongoOptions> mongoOptions, ILogger<UserRepository> logger): CodeDesignPlus.Net.Mongo.Repository.RepositoryBase(serviceProvider, mongoOptions, logger), IUserRepository{public Task CustomMethodAsync(){var collection = this.GetCollection<UserAggregate>();// Do something with the collectionreturn Task.CompletedTask;}} -
Creamos la clase Startup:
Al utilizar el SDK de CodeDesignPlus, generalmente es necesario crear una clase
Startup
que implemente la interfazIStartup
. Esto facilita una mejor organización de los servicios que se desean registrar en el contenedor de dependencias de la aplicación, para cada una de las capas del servicio.using System;using CodeDesignPlus.Net.Core.Abstractions;using Microsoft.Extensions.Configuration;using Microsoft.Extensions.DependencyInjection;namespace CodeDesignPlus.Net.Mongo.Sample.RepositoryBase;public class Startup : IStartup{public void Initialize(IServiceCollection services, IConfiguration configuration){}} -
Registramos los servicios en el contenedor de dependencias:
serviceCollection.AddMongo<Startup>(configuration); -
Obtenemos la instancia de nuestro repositorio:
var repository = serviceProvider.GetRequiredService<IUserRepository>(); -
Creamos un nuevo usuario:
var user = new UserEntity{Id = Guid.NewGuid(),Name = "John Doe",Email = "john.doe@codedesignplus.com",CreatedAt = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),CreatedBy = Guid.NewGuid(),IsActive = true};await repository.CreateAsync(user, CancellationToken.None);
Conclusiones
La clase RepositoryBase
es una herramienta poderosa para simplificar el desarrollo de aplicaciones que interactúan con MongoDB. Al proporcionar un conjunto robusto de métodos para operaciones CRUD, transacciones y consultas complejas, permite a los desarrolladores concentrarse en la lógica de negocio de su aplicación, promoviendo la reutilización de código y la consistencia en la arquitectura de sus microservicios.