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
yUpdatedAt
. - 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:
-
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;}} -
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 interfazIOperationBase
para utilizar las operaciones de creación, actualización y eliminación proporcionadas por la claseOperationBase
.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>{} -
Implementación del Repositorio
A continuación, implementaremos la interfaz
IOrderRepository
en la claseOrderRepository
. En esta clase, utilizaremos la claseOperationBase
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{} -
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 claseOrderConfiguration
que implemente la interfazIEntityTypeConfiguration<OrderAggregate>
. En esta clase, configuramos las propiedades y relaciones de la entidadOrderAggregate
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();}} -
Configuración del Contexto
Por último, configuramos el contexto de la base de datos en la clase
OrderContext
. En esta clase, registramos la entidadOrderAggregate
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>();
// Createvar order = OrderAggregate.Create(Guid.NewGuid(), "Order 1", "Description 1", 1000, user.IdUser, user.Tenant);
await orderRepository.CreateAsync(order);
// Updateorder = 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);}
// Deleteawait orderRepository.DeleteAsync(order.Id);
Console.ReadLine();
-
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; -
Configuración de la Aplicación:
Se crea un
ConfigurationBuilder
para leer la configuración desde el archivoappsettings.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(); -
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 -
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);});}); -
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>(); -
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(); -
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ónawait context.Database.EnsureCreatedAsync(); -
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>(); -
Crear una Orden:
Se crea una nueva orden utilizando el método
Create
de la claseOrderAggregate
. Esta orden se añadirá a la base de datos utilizando el métodoCreateAsync
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.