Skip to content

OperationBase

La clase OperationBase<TEntity> es una clase base abstracta para la implementación de operaciones de negocio sobre entidades en MongoDB. Hereda de RepositoryBase y añade funcionalidades como el registro de auditoría y la actualización parcial de entidades. Está diseñada para ser la base de los servicios de negocio que gestionan las entidades.

¿Cómo Funciona?

OperationBase<TEntity> extiende la funcionalidad de RepositoryBase añadiendo lógica de negocio común. Al crear una entidad, establece automáticamente las propiedades de auditoría (CreatedBy y CreatedAt). Al actualizar una entidad, actualiza dinámicamente solo las propiedades no nulas y no protegidas en una lista negra, dejando intactas las propiedades de auditoría y la propiedad Id. Utiliza el IUserContext para obtener información del usuario autenticado y realiza las operaciones en MongoDB a través de los métodos heredados de RepositoryBase.

Métodos


Esta sección describe los métodos principales proporcionados por la clase OperationBase<TEntity>.

CreateAsync

Type: public virtual Task CreateAsync(TEntity entity, CancellationToken cancellationToken = default)

Crea una nueva entidad en la base de datos de forma asíncrona. Antes de insertar, establece las propiedades de auditoría (CreatedBy y CreatedAt) utilizando la información del IUserContext. TEntity debe ser una clase que implemente la interfaz IEntityBase y la interfaz IEntity cuando sea necesario.

// Create a new user
await repository.CreateAsync(user, CancellationToken.None);

DeleteAsync

Type: public virtual Task DeleteAsync(Guid id, CancellationToken cancellationToken = default)

Elimina una entidad de la base de datos por su Guid de forma asíncrona. TEntity debe ser una clase que implemente la interfaz IEntityBase.

// Delete a user
await repository.DeleteAsync(user.Id, CancellationToken.None);

UpdateAsync

Type: public virtual async Task UpdateAsync(Guid id, TEntity entity, CancellationToken cancellationToken = default)

Actualiza una entidad en la base de datos por su Guid de forma asíncrona. Actualiza solo las propiedades no nulas de la entidad, excluyendo propiedades de auditoría y la propiedad Id. TEntity debe ser una clase que implemente la interfaz IEntityBase.

// Find a user to update
user.Name = "John Doe Updated";
await repository.UpdateAsync(user.Id, user, CancellationToken.None);

Implementación


La clase OperationBase<TEntity> se puede utilizar para crear servicios de negocio que gestionen entidades en MongoDB. Al heredar de OperationBase<TEntity>, los servicios obtienen funcionalidades de auditoría y actualización parcial sin tener que implementarlas manualmente. A continuación, se muestra un ejemplo de cómo usar OperationBase<TEntity> en una aplicación .NET Core:

using CodeDesignPlus.Net.Mongo.Extensions;
namespace CodeDesignPlus.Net.Mongo.Operations;
/// <summary>
/// Base class for MongoDB operations.
/// </summary>
/// <typeparam name="TEntity">The type of the entity.</typeparam>
public abstract class OperationBase<TEntity> : RepositoryBase, IOperationBase<TEntity>
where TEntity : class, IEntityBase
{
private readonly List<string> blacklist = new List<string>
{
nameof(IEntityBase.Id),
nameof(IEntity.CreatedAt),
nameof(IEntity.CreatedBy),
nameof(IEntity.UpdatedAt),
nameof(IEntity.UpdatedBy)
};
/// <summary>
/// The authenticated user context.
/// </summary>
protected readonly IUserContext AuthenticateUser;
/// <summary>
/// Initializes a new instance of the <see cref="OperationBase{TEntity}"/> class.
/// </summary>
/// <param name="authenticatetUser">The authenticated user context.</param>
/// <param name="serviceProvider">The service provider.</param>
/// <param name="options">The MongoDB options.</param>
/// <param name="logger">The logger instance.</param>
/// <exception cref="ArgumentNullException">Thrown when any of the parameters are null.</exception>
protected OperationBase(IUserContext authenticatetUser, IServiceProvider serviceProvider, IOptions<MongoOptions> options, ILogger logger)
: base(serviceProvider, options, logger)
{
ArgumentNullException.ThrowIfNull(authenticatetUser);
AuthenticateUser = authenticatetUser;
}
/// <summary>
/// Creates a new entity asynchronously.
/// </summary>
/// <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 virtual Task CreateAsync(TEntity entity, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(entity);
if (entity is IEntity auditTrailEntity)
{
auditTrailEntity.CreatedBy = AuthenticateUser.IdUser;
auditTrailEntity.CreatedAt = SystemClock.Instance.GetCurrentInstant();
}
return base.CreateAsync(entity, cancellationToken);
}
/// <summary>
/// Deletes an entity by its identifier asynchronously.
/// </summary>
/// <param name="id">The identifier of the entity to delete.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A task that represents the asynchronous delete operation.</returns>
public virtual Task DeleteAsync(Guid id, CancellationToken cancellationToken)
{
return DeleteAsync(id, Guid.Empty, cancellationToken);
}
/// <summary>
/// Deletes an entity by its identifier asynchronously.
/// </summary>
/// <param name="id">The identifier of the entity 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 operation.</returns>
public virtual Task DeleteAsync(Guid id, Guid tenant, CancellationToken cancellationToken)
{
var filter = Builders<TEntity>.Filter.Eq(x => x.Id, id);
return DeleteAsync(filter, tenant, cancellationToken);
}
/// <summary>
/// Updates an entity by its identifier asynchronously.
/// </summary>
/// <param name="id">The identifier of the entity to update.</param>
/// <param name="entity">The entity with updated values.</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 virtual Task UpdateAsync(Guid id, TEntity entity, CancellationToken cancellationToken)
{
return UpdateAsync(id, entity, Guid.Empty, cancellationToken);
}
/// <summary>
/// Updates an entity by its identifier asynchronously.
/// </summary>
/// <param name="id">The identifier of the entity to update.</param>
/// <param name="entity">The entity with updated values.</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 update operation.</returns>
/// <exception cref="ArgumentNullException">Thrown when the entity is null.</exception>
public virtual async Task UpdateAsync(Guid id, TEntity entity, Guid tenant, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(entity);
var filter = Builders<TEntity>.Filter.Eq(x => x.Id, id).BuildFilter(tenant);
var updates = new List<UpdateDefinition<TEntity>>();
var properties = typeof(TEntity).GetProperties().Where(x => !blacklist.Contains(x.Name)).ToList();
foreach (var property in properties.Select(property => property.Name))
{
var value = entity.GetType().GetProperty(property).GetValue(entity);
if (value != null)
{
updates.Add(Builders<TEntity>.Update.Set(property, value));
}
}
var update = Builders<TEntity>.Update.Combine(updates);
await GetCollection<TEntity>().UpdateOneAsync(filter, update, cancellationToken: cancellationToken);
}
}

Ejemplo de Uso


Este código usa CodeDesignPlus.Net.Mongo para realizar operaciones CRUD en MongoDB. Configura la base de datos, crea un repositorio (UserRepository que herada de OperationBase), y luego crea, actualiza y elimina un usuario (UserEntity).

using CodeDesignPlus.Net.Mongo.Extensions;
using CodeDesignPlus.Net.Mongo.Sample.OperationBase;
using CodeDesignPlus.Net.Mongo.Sample.OperationBase.Entities;
using CodeDesignPlus.Net.Mongo.Sample.OperationBase.Respositories;
using CodeDesignPlus.Net.Mongo.Sample.OperationBase.Services;
using CodeDesignPlus.Net.Security.Abstractions;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
var configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.Build();
var serviceCollection = new ServiceCollection();
serviceCollection.AddLogging();
serviceCollection.AddMongo<Startup>(configuration);
serviceCollection.AddSingleton<IUserContext, FakeUserContext>();
var serviceProvider = serviceCollection.BuildServiceProvider();
var repository = serviceProvider.GetRequiredService<IUserRepository>();
var tenant = Guid.NewGuid();
var createdBy = Guid.NewGuid();
var user = UserAggregate.Create(Guid.NewGuid(), "John Doe", "john.doe@codedesignplus.com", tenant, createdBy);
// Create a new user
await repository.CreateAsync(user, CancellationToken.None);
// Find a user to update
user.UpdateName("John Doe Updated");
await repository.UpdateAsync(user.Id, user, tenant, CancellationToken.None);
// Delete a user
await repository.DeleteAsync(user.Id, tenant, CancellationToken.None);
  1. Configuración de Mongo en appsettings.json:

    {
    "Core": {
    "Business": "CodeDesignPlus",
    "AppName": "sample-mongo-operationbase",
    "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
    }
    }
  2. Creamos la entidad para el ejemplo:

    Procedemos a crear la entidad que representará los documentos en la colección de MongoDB, esta entidad debe implementar la interfaz IEntity de CodeDesignPlus.Net.Core.Abstractions.

    using System;
    using CodeDesignPlus.Net.Core.Abstractions;
    namespace CodeDesignPlus.Net.Mongo.Sample.OperationBase.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.OperationBase.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;
    }
    }
  3. Creamos el repositorio:

    Para efectos de este ejemplo, procederemos a crear el repositorio UserRepository que hereda de OperationBase.

    using CodeDesignPlus.Net.Mongo.Abstractions.Options;
    using CodeDesignPlus.Net.Mongo.Repository;
    using CodeDesignPlus.Net.Mongo.Sample.OperationBase.Entities;
    using CodeDesignPlus.Net.Security.Abstractions;
    using Microsoft.Extensions.Logging;
    using Microsoft.Extensions.Options;
    namespace CodeDesignPlus.Net.Mongo.Sample.OperationBase.Respositories;
    public class UserRepository(IUserContext authenticatetUser, IServiceProvider serviceProvider, IOptions<MongoOptions> mongoOptions, ILogger<UserRepository> logger)
    : CodeDesignPlus.Net.Mongo.Operations.OperationBase<UserAggregate>(authenticatetUser, serviceProvider, mongoOptions, logger), IUserRepository
    {
    }
  4. Creamos la clase Startup:

    Al utilizar el SDK de CodeDesignPlus, generalmente es necesario crear una clase Startup que implemente la interfaz IStartup. 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.OperationBase;
    public class Startup : IStartup
    {
    public void Initialize(IServiceCollection services, IConfiguration configuration)
    {
    }
    }
  5. Creamos el servicio de usuario:

    Para efectos de este ejemplo, procederemos a crear el servicio FakeUserContext que implementa IUserContext. Este servicio se utiliza para obtener información del usuario autenticado. Para un uso en producción se debe usar la implementación que provee la libreria CodeDesignPlus.Net.Security.

    using System;
    using System.Security.Claims;
    using CodeDesignPlus.Net.Security.Abstractions;
    namespace CodeDesignPlus.Net.Mongo.Sample.OperationBase.Services;
    public class FakeUserContext : IUserContext
    {
    public bool IsApplication => false;
    public Guid IdUser => Guid.Parse("1f4eac7b-4b3b-4b3b-8b3b-4b3b4b3b4b3b");
    public bool IsAuthenticated => true;
    public string Name => "John Doe";
    public string FirstName => "John";
    public string LastName => "Doe";
    public string City => "Bogotá";
    public string Country => "Colombia";
    public string PostalCode => "110111";
    public string StreetAddress => "Calle 123 # 123 - 123";
    public string State => "Cundinamarca";
    public string JobTitle => "Software Developer";
    public string[] Emails => ["john.doe@codedesignplus.com"];
    public Guid Tenant => Guid.Parse("2f4eac7b-4b3b-4b3b-8b3b-4b3b4b3b4b3b");
    public ClaimsPrincipal User => new();
    public TValue GetClaim<TValue>(string claimType)
    {
    throw new NotImplementedException();
    }
    public TValue GetHeader<TValue>(string header)
    {
    throw new NotImplementedException();
    }
    }
  6. Registramos los servicios en el contenedor de dependencias:

    serviceCollection.AddMongo<Startup>(configuration);
    serviceCollection.AddSingleton<IUserContext, FakeUserContext>();
  7. Obtenemos la instancia de nuestro repositorio:

    var repository = serviceProvider.GetRequiredService<IUserRepository>();
  8. Creamos un nuevo usuario:

    var user = new UserEntity
    {
    Id = Guid.NewGuid(),
    Name = "John Doe",
    Email = "john.doe@codedesignplus.com",
    //CreatedAt = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(), //Valor asignado por la clase OperationBase
    //CreatedBy = Guid.NewGuid(), //Valor asignado por la clase OperationBase
    IsActive = true
    };
    await repository.CreateAsync(user, CancellationToken.None);

Conclusiones


La clase OperationBase<TEntity> simplifica la creación de servicios que interactúan con MongoDB al proporcionar una base sólida para las operaciones de negocio. Al extender RepositoryBase con funcionalidades de auditoría y actualización parcial, permite a los desarrolladores concentrarse en la lógica específica de cada servicio sin tener que repetir código común.