Skip to content

OperationBase

La clase OperationBase proporciona operaciones básicas para la creación, actualización y eliminación de registros en el repositorio, asignando información a las propiedades transversales de la entidad. Esta clase está diseñada para trabajar con entidades que implementan la interfaz IEntityBase y se integra con el contexto de la base de datos y la información del usuario autenticado.

namespace CodeDesignPlus.Net.EFCore.Operations;
/// <summary>
/// Provides base operations for creating, updating, and deleting records in the repository, while assigning information to the transversal properties of the entity.
/// </summary>
/// <typeparam name="TEntity">The entity type to be configured.</typeparam>
/// <param name="authenticatetUser">Information of the authenticated user during the request.</param>
/// <param name="context">Represents a session with the database and can be used to query and save instances of your entities.</param>
public abstract class OperationBase<TEntity>(IUserContext authenticatetUser, DbContext context) : RepositoryBase(context), IOperationBase<TEntity>
where TEntity : class, IEntityBase
{
/// <summary>
/// List of properties that will not be updated.
/// </summary>
private readonly List<string> blacklist = [
nameof(IEntityBase.Id),
nameof(IEntity.CreatedAt),
nameof(IEntity.CreatedBy),
nameof(IEntity.UpdatedAt),
nameof(IEntity.UpdatedBy)
];
/// <summary>
/// Provides the information of the authenticated user during the request.
/// </summary>
protected readonly IUserContext AuthenticateUser = authenticatetUser;
/// <summary>
/// Creates a record in the database.
/// </summary>
/// <param name="entity">Entity to create.</param>
/// <param name="cancellationToken">Propagates notification that operations should be canceled.</param>
/// <returns>Represents an asynchronous operation.</returns>
public virtual async Task CreateAsync(TEntity entity, CancellationToken cancellationToken = default)
{
if (entity is IEntity auditTrailEntity)
{
auditTrailEntity.CreatedBy = this.AuthenticateUser.IdUser;
auditTrailEntity.CreatedAt = SystemClock.Instance.GetCurrentInstant();
}
await base.CreateAsync(entity, cancellationToken);
}
/// <summary>
/// Deletes a record in the database.
/// </summary>
/// <param name="id">Id of the record to delete.</param>
/// <param name="cancellationToken">Propagates notification that operations should be canceled.</param>
/// <returns>Represents an asynchronous operation.</returns>
public virtual Task DeleteAsync(Guid id, CancellationToken cancellationToken = default)
{
return base.DeleteAsync<TEntity>(x => x.Id.Equals(id), cancellationToken);
}
/// <summary>
/// Updates a record in the database.
/// </summary>
/// <param name="id">Id of the record to update.</param>
/// <param name="entity">Entity with the information to update.</param>
/// <param name="cancellationToken">Propagates notification that operations should be canceled.</param>
/// <returns>Represents an asynchronous operation.</returns>
public virtual async Task UpdateAsync(Guid id, TEntity entity, CancellationToken cancellationToken = default)
{
var entityUpdated = await base.GetEntity<TEntity>().FindAsync([id], cancellationToken: cancellationToken);
if (entityUpdated != null)
{
var properties = typeof(TEntity).GetProperties().Where(x => !this.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)
entityUpdated.GetType().GetProperty(property).SetValue(entityUpdated, value, null);
}
await base.Context.SaveChangesAsync(cancellationToken);
}
}
}

Características Clave


  • Gestión de Entidades: Permite crear, actualizar y eliminar registros en la base de datos.
  • Propiedades Transversales: Asigna automáticamente información relevante a las propiedades transversales de la entidad, como CreatedBy, CreatedAt, UpdatedBy y UpdatedAt.
  • Lista de Exclusión: Mantiene una lista de propiedades que no deben ser actualizadas durante las operaciones de actualización.
  • Integración con Usuario Autenticado: Utiliza la información del usuario autenticado para completar las propiedades de auditoría de las entidades.
  • Operaciones Asíncronas: Soporta operaciones asíncronas para mejorar el rendimiento y la capacidad de respuesta de la aplicación.

Métodos


La clase OperationBase proporciona una serie de métodos para gestionar las operaciones de creación, actualización y eliminación de entidades en la base de datos.

CreateAsync

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

El método CreateAsync permite crear una entidad en la base de datos de forma asincrónica. En este caso, utilizaremos el repositorio de órdenes para crear una nueva orden en la base de datos.

// ...
var user = serviceProvider.GetRequiredService<IUserContext>();
var orderRepository = serviceProvider.GetRequiredService<IOrderRepository>();
var order = OrderAggregate.Create(Guid.NewGuid(), "Order 1", "Description 1", 1000, user.IdUser, user.Tenant);
await orderRepository.CreateAsync(order);

UpdateAsync

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

El método UpdateAsync permite actualizar una entidad existente en la base de datos. En este caso, utilizaremos el repositorio de órdenes para modificar una orden existente en la base de datos.

// ...
var order = orderRepository.GetEntity<OrderAggregate>().FirstOrDefault(x => x.Id == order.Id);
if (order is not null)
{
order.Update("Order 1 Updated", "Description 1 Updated", 2000, user.IdUser);
await orderRepository.UpdateAsync(order.Id, order, CancellationToken.None);
}

DeleteAsync

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

El método DeleteAsync permite eliminar una entidad de la base de datos basada en una condición. En este caso, utilizaremos el repositorio de órdenes para eliminar una orden específica de la base de datos.

// ...
await orderRepository.DeleteAsync(order.Id);

Ejemplo de Uso


Imaginemos que estamos desarrollando una aplicación de comercio electrónico y queremos implementar un repositorio para gestionar las ordenes en nuestra base de datos. Utilizaremos la clase RepositoryBase para simplificar la creación y actualización de productos en la base de datos.

Recursos Necesarios

Para este ejemplo, primero debemos llevar a cabo la creación de una serie de recursos que se mostrarán a continuación:

  1. Creación del Aggregate

    Lo primero que debemos hacer es crear una clase que represente el agregado de ordenes en nuestra aplicación. En este caso, crearemos una clase OrderAggregate que defina las propiedades y métodos necesarios para gestionar las ordenes en nuestra base de datos.

    using CodeDesignPlus.Net.Core.Abstractions;
    namespace CodeDesignPlus.Net.EFCore.Sample.OperationBase.Entities;
    public class OrderAggregate : AggregateRoot, IEntity
    {
    public string Name { get; private set; }
    public string Description { get; private set; }
    public decimal Price { get; private set; }
    public OrderAggregate() : base()
    {
    Name = string.Empty;
    Description = string.Empty;
    }
    public OrderAggregate(Guid id, string name, string description, decimal price) : base(id)
    {
    Name = name;
    Description = description;
    Price = price;
    }
    public static OrderAggregate Create(Guid id, string name, string description, decimal price, Guid tenant, Guid createBy)
    {
    var aggregate = new OrderAggregate(id, name, description, price)
    {
    CreatedAt = SystemClock.Instance.GetCurrentInstant(),
    CreatedBy = createBy,
    IsActive = true,
    Tenant = tenant
    };
    return aggregate;
    }
    public void Update(string name, string description, decimal price, Guid updatedBy)
    {
    Name = name;
    Description = description;
    Price = price;
    UpdatedAt = SystemClock.Instance.GetCurrentInstant();
    UpdatedBy = updatedBy;
    }
    public void Delete(Guid updatedBy)
    {
    UpdatedAt = SystemClock.Instance.GetCurrentInstant();
    UpdatedBy = updatedBy;
    }
    }
  2. Creación del Repositorio

    A continuación, crearemos una interfaz IOrderRepository que defina las operaciones básicas para gestionar las ordenes en nuestra base de datos. Esta interfaz heredará de la interfaz IOperationBase para utilizar las operaciones de creación, actualización y eliminación proporcionadas por la clase OperationBase.

    using CodeDesignPlus.Net.EFCore.Abstractions.Operations;
    using CodeDesignPlus.Net.EFCore.Sample.OperationBase.Entities;
    namespace CodeDesignPlus.Net.EFCore.Sample.OperationBase.Repositories;
    public interface IOrderRepository: IOperationBase<OrderAggregate>
    {
    }
  3. Implementación del Repositorio

    A continuación, implementaremos la interfaz IOrderRepository en la clase OrderRepository. En esta clase, utilizaremos la clase OperationBase para gestionar las operaciones de creación, actualización y eliminación de ordenes en nuestra base de datos.

    using CodeDesignPlus.Net.EFCore.Sample.OperationBase.Entities;
    using CodeDesignPlus.Net.Security.Abstractions;
    namespace CodeDesignPlus.Net.EFCore.Sample.OperationBase.Repositories;
    public class OrderRepository(IUserContext userContext, OrderContext context)
    : Operations.OperationBase<OrderAggregate>(userContext, context), IOrderRepository
    {
    }
  4. Configuración del Repositorio

    Continuamos con la configuración de la entidad OrderAggregate en el contexto de la base de datos. Para ello, creamos una clase OrderConfiguration que implemente la interfaz IEntityTypeConfiguration<OrderAggregate>. En esta clase, configuramos las propiedades y relaciones de la entidad OrderAggregate en la base de datos.

    using CodeDesignPlus.Net.EFCore.Extensions;
    using CodeDesignPlus.Net.EFCore.Sample.OperationBase.Entities;
    using Microsoft.EntityFrameworkCore;
    using Microsoft.EntityFrameworkCore.Metadata.Builders;
    namespace CodeDesignPlus.Net.EFCore.Sample.OperationBase.EntityConfiguration;
    public class OrderConfiguration : IEntityTypeConfiguration<OrderAggregate>
    {
    public void Configure(EntityTypeBuilder<OrderAggregate> builder)
    {
    builder.ConfigurationBase();
    builder.ToTable("Orders");
    builder.Property(x => x.Name).HasColumnType("varchar(64)").IsRequired();
    builder.Property(x => x.Description).HasColumnType("varchar(512)").IsRequired();
    }
    }
  5. Configuración del Contexto

    Por último, configuramos el contexto de la base de datos en la clase OrderContext. En esta clase, registramos la entidad OrderAggregate y su configuración en el modelo de datos.

    using System.Diagnostics.CodeAnalysis;
    using CodeDesignPlus.Net.EFCore.Extensions;
    using CodeDesignPlus.Net.EFCore.Sample.OperationBase.Entities;
    using Microsoft.EntityFrameworkCore;
    namespace CodeDesignPlus.Net.EFCore.Sample.OperationBase;
    public class OrderContext([NotNull] DbContextOptions options) : DbContext(options)
    {
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
    base.OnModelCreating(modelBuilder);
    modelBuilder.RegisterEntityConfigurations<OrderContext>();
    }
    public DbSet<OrderAggregate> Order { get; set; }
    }

Aplicación Base de Consola

Al finalizar la creación de los recursos necesarios, podemos ahora registrar el repositorio en el contenedor de dependencias de .NET Core y utilizar la clase OperationBase para gestionar las ordenes en nuestra base de datos, asegurando que la información del usuario autenticado se asigne correctamente a las propiedades transversales de la entidad durante las operaciones de creación y actualización.

using CodeDesignPlus.Net.EFCore.Extensions;
using CodeDesignPlus.Net.EFCore.Sample.OperationBase;
using CodeDesignPlus.Net.EFCore.Sample.OperationBase.Entities;
using CodeDesignPlus.Net.EFCore.Sample.OperationBase.Repositories;
using CodeDesignPlus.Net.EFCore.Sample.OperationBase.Services;
using CodeDesignPlus.Net.Security.Abstractions;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
var configurationBuilder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);
var configuration = configurationBuilder.Build();
var serviceCollection = new ServiceCollection();
serviceCollection.AddSingleton(configuration);
serviceCollection.AddEFCore<OrderContext>(configuration);
serviceCollection.AddSingleton<IUserContext, UserContext>();
serviceCollection.AddDbContext<OrderContext>(options =>
{
options.UseSqlServer(configuration.GetConnectionString("DefaultConnection"), x =>
{
x.EnableRetryOnFailure(5, TimeSpan.FromSeconds(10), null);
});
});
var serviceProvider = serviceCollection.BuildServiceProvider();
var context = serviceProvider.GetRequiredService<OrderContext>();
var user = serviceProvider.GetRequiredService<IUserContext>();
await context.Database.EnsureDeletedAsync();
await context.Database.EnsureCreatedAsync();
var orderRepository = serviceProvider.GetRequiredService<IOrderRepository>();
// Create
var order = OrderAggregate.Create(Guid.NewGuid(), "Order 1", "Description 1", 1000, user.IdUser, user.Tenant);
await orderRepository.CreateAsync(order);
// Update
order = orderRepository.GetEntity<OrderAggregate>().FirstOrDefault(x => x.Id == order.Id);
if (order is not null)
{
order.Update("Order 1 Updated", "Description 1 Updated", 2000, user.IdUser);
await orderRepository.UpdateAsync(order.Id, order, CancellationToken.None);
}
// Delete
await orderRepository.DeleteAsync(order.Id);
Console.ReadLine();
  1. Importación de Dependencias:

    Se importan las bibliotecas necesarias para la configuración de Entity Framework Core, la inyección de dependencias y la configuración de la aplicación.

    using CodeDesignPlus.Net.EFCore.Extensions;
    using CodeDesignPlus.Net.EFCore.Sample.OperationBase;
    using CodeDesignPlus.Net.EFCore.Sample.OperationBase.Entities;
    using CodeDesignPlus.Net.EFCore.Sample.OperationBase.Repositories;
    using CodeDesignPlus.Net.EFCore.Sample.OperationBase.Services;
    using CodeDesignPlus.Net.Security.Abstractions;
    using Microsoft.EntityFrameworkCore;
    using Microsoft.Extensions.Configuration;
    using Microsoft.Extensions.DependencyInjection;
  2. Configuración de la Aplicación:

    Se crea un ConfigurationBuilder para leer la configuración desde el archivo appsettings.json. Este archivo contiene las configuraciones necesarias, como la cadena de conexión a la base de datos.

    var configurationBuilder = new ConfigurationBuilder()
    .SetBasePath(Directory.GetCurrentDirectory())
    .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);
    var configuration = configurationBuilder.Build();
  3. Configuración de los Servicios:

    Se crea una colección de servicios ServiceCollection para registrar los servicios necesarios en la aplicación. Se añade la configuración de la aplicación al contenedor de dependencias y se registran los servicios de Entity Framework Core y de seguridad.

    var serviceCollection = new ServiceCollection();
    serviceCollection.AddSingleton(configuration);
    serviceCollection.AddEFCore(configuration);
    serviceCollection.AddSingleton<IUserContext, UserContext>(); // This implementation is provided by CodeDesignPlus.Net.Security package
  4. Registro del Contexto de la Base de Datos:

    Se registra el contexto de la base de datos OrderContext en el contenedor de dependencias. Se configura la conexión a la base de datos utilizando la cadena de conexión proporcionada en la configuración.

    serviceCollection.AddDbContext<OrderContext>(options =>
    {
    options.UseSqlServer(configuration.GetConnectionString("DefaultConnection"), x =>
    {
    x.EnableRetryOnFailure(5, TimeSpan.FromSeconds(10), null);
    });
    });
  5. Registro de Repositorios:

    Se escanean y registran automáticamente todos los repositorios en el contenedor de dependencias. Esto facilita la inyección de dependencias para los repositorios al detectar y registrar todas las implementaciones de IRepositoryBase.

    serviceCollection.AddRepositories<OrderContext>();
  6. Construcción del Proveedor de Servicios:

    Se construye el proveedor de servicios a partir de la colección de servicios registrados. Este proveedor se utiliza para obtener instancias de los servicios registrados en la aplicación.

    var serviceProvider = serviceCollection.BuildServiceProvider();
  7. Creación de la Base de Datos:

    Se obtiene una instancia del contexto de la base de datos OrderContext a partir del proveedor de servicios. Se asegura que la base de datos esté creada y vacía antes de continuar con las operaciones de repositorio.

    var context = serviceProvider.GetRequiredService<OrderContext>();
    await context.Database.EnsureDeletedAsync(); //Esto aplica solo para pruebas, no se debe hacer en producción
    await context.Database.EnsureCreatedAsync();
  8. Obtención del Repositorio de Órdenes:

    Se obtiene una instancia del repositorio de órdenes IOrderRepository a partir del proveedor de servicios. Esta instancia se utilizará para gestionar las órdenes en la base de datos.

    var orderRepository = serviceProvider.GetRequiredService<IOrderRepository>();
  9. Crear una Orden:

    Se crea una nueva orden utilizando el método Create de la clase OrderAggregate. Esta orden se añadirá a la base de datos utilizando el método CreateAsync del repositorio de órdenes.

    var order = OrderAggregate.Create(Guid.NewGuid(), "Order 1", "Description 1", 1000, Guid.NewGuid(), Guid.NewGuid());
    await orderRepository.CreateAsync(order);

En este ejemplo, hemos creado un repositorio para gestionar las ordenes en nuestra base de datos utilizando la clase OperationBase. Hemos definido una serie de recursos necesarios para la creación y configuración del repositorio, incluyendo la clase OrderAggregate, la interfaz IOrderRepository, la clase OrderRepository, la clase OrderConfiguration y la clase OrderContext.

Conclusiones


La clase OperationBase proporciona una forma sencilla y eficaz de gestionar las operaciones de creación, actualización y eliminación de entidades en la base de datos. Al utilizar esta clase, puedes simplificar la gestión de entidades en tu aplicación y asegurarte de que la información del usuario autenticado se asigne correctamente a las propiedades transversales de la entidad durante las operaciones de creación y actualización.