RepositoryBase
La clase RepositoryBase
es una implementación genérica para interactuar con una base de datos utilizando Entity Framework Core. Esta clase abstracta proporciona métodos comunes para crear, actualizar, eliminar y gestionar entidades en la base de datos. Además, maneja las transacciones de manera eficiente y segura.
namespace CodeDesignPlus.Net.EFCore.Repository;
/// <summary>/// This abstract class implements the most common methods for interacting with the database./// </summary>/// <remarks>/// Create a new instance of RepositoryBase./// </remarks>/// <param name="context">Represents a session with the database and can be used to query and save instances of your entities.</param>/// <exception cref="ArgumentNullException">Thrown if context is null.</exception>public abstract class RepositoryBase(DbContext context) : IRepositoryBase{ /// <summary> /// Represents a session with the database and can be used to query and save instances of your entities. /// </summary> protected readonly DbContext Context = context ?? throw new ArgumentNullException(nameof(context));
/// <summary> /// Converts the DbContext to the specified generic type. /// </summary> /// <typeparam name="TContext">Type of context to return.</typeparam> /// <returns>Returns the context of the database.</returns> public TContext GetContext<TContext>() where TContext : DbContext => (TContext)this.Context;
/// <summary> /// Gets a DbSet that can be used to query and save instances of TEntity. /// </summary> /// <typeparam name="TEntity">The type of entity for which a set should be returned.</typeparam> /// <returns>A set for the given entity type.</returns> public DbSet<TEntity> GetEntity<TEntity>() where TEntity : class, IEntityBase => this.Context.Set<TEntity>();
/// <summary> /// Creates an entity in the database. /// </summary> /// <typeparam name="TEntity">Type of the entity to create.</typeparam> /// <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 Task CreateAsync<TEntity>(TEntity entity, CancellationToken cancellationToken = default) where TEntity : class, IEntityBase { ArgumentNullException.ThrowIfNull(entity);
return this.ProcessCreateAsync(entity, cancellationToken); }
/// <summary> /// Processes the creation of an entity in the database. /// </summary> /// <typeparam name="TEntity">Type of the entity to create.</typeparam> /// <param name="entity">Entity to create.</param> /// <param name="cancellationToken">Propagates notification that operations should be canceled.</param> /// <returns>Represents an asynchronous operation.</returns> private async Task ProcessCreateAsync<TEntity>(TEntity entity, CancellationToken cancellationToken) where TEntity : class, IEntityBase { await this.Context.AddAsync(entity, cancellationToken).ConfigureAwait(false);
await this.Context.SaveChangesAsync(cancellationToken).ConfigureAwait(false); }
/// <summary> /// Updates an entity in the database. /// </summary> /// <typeparam name="TEntity">Type of the entity to update.</typeparam> /// <param name="entity">Entity to update.</param> /// <param name="cancellationToken">Propagates notification that operations should be canceled.</param> /// <returns>Represents an asynchronous operation.</returns> public Task UpdateAsync<TEntity>(TEntity entity, CancellationToken cancellationToken = default) where TEntity : class, IEntityBase { ArgumentNullException.ThrowIfNull(entity);
return this.ProcessUpdateAsync(entity, cancellationToken); }
/// <summary> /// Processes the update of an entity in the database. /// </summary> /// <typeparam name="TEntity">Type of the entity to update.</typeparam> /// <param name="entity">Entity to update.</param> /// <param name="cancellationToken">Propagates notification that operations should be canceled.</param> /// <returns>Represents an asynchronous operation.</returns> private async Task ProcessUpdateAsync<TEntity>(TEntity entity, CancellationToken cancellationToken) where TEntity : class, IEntityBase { this.Context.Set<TEntity>().Update(entity);
var idUserCreatorProperty = this.Context.Entry(entity).Metadata.FindProperty(nameof(IEntity.CreatedBy)); var createdAtProperty = this.Context.Entry(entity).Metadata.FindProperty(nameof(IEntity.CreatedAt));
if (idUserCreatorProperty != null) this.Context.Entry(entity).Property(nameof(IEntity.CreatedBy)).IsModified = false;
if (createdAtProperty != null) this.Context.Entry(entity).Property(nameof(IEntity.CreatedAt)).IsModified = false;
await this.Context.SaveChangesAsync(cancellationToken).ConfigureAwait(false); }
/// <summary> /// Deletes an entity in the database. /// </summary> /// <typeparam name="TEntity">Type of the entity to delete.</typeparam> /// <param name="predicate">A function to test each element for a condition.</param> /// <param name="cancellationToken">Propagates notification that operations should be canceled.</param> /// <returns>Represents an asynchronous operation.</returns> public Task DeleteAsync<TEntity>(Expression<Func<TEntity, bool>> predicate, CancellationToken cancellationToken = default) where TEntity : class, IEntityBase { ArgumentNullException.ThrowIfNull(predicate);
return this.ProcessDeleteAsync(predicate, cancellationToken); }
/// <summary> /// Processes the deletion of an entity in the database. /// </summary> /// <typeparam name="TEntity">Type of the entity to delete.</typeparam> /// <param name="predicate">A function to test each element for a condition.</param> /// <param name="cancellationToken">Propagates notification that operations should be canceled.</param> /// <returns>Represents an asynchronous operation.</returns> private async Task ProcessDeleteAsync<TEntity>(Expression<Func<TEntity, bool>> predicate, CancellationToken cancellationToken) where TEntity : class, IEntityBase { var entity = await this.Context.Set<TEntity>().Where(predicate).FirstOrDefaultAsync(cancellationToken: cancellationToken).ConfigureAwait(false);
if (entity != null) { this.Context.Set<TEntity>().Remove(entity);
await this.Context.SaveChangesAsync(cancellationToken).ConfigureAwait(false); } }
/// <summary> /// Creates a set of entities in the database. /// </summary> /// <typeparam name="TEntity">Type of the entity to create.</typeparam> /// <param name="entities">List of entities to create.</param> /// <param name="cancellationToken">Propagates notification that operations should be canceled.</param> /// <returns>Represents an asynchronous operation.</returns> public async Task CreateRangeAsync<TEntity>(List<TEntity> entities, CancellationToken cancellationToken = default) where TEntity : class, IEntityBase { ArgumentNullException.ThrowIfNull(entities);
await ProcessCreateRangeAsync(entities, cancellationToken).ConfigureAwait(false); }
/// <summary> /// Processes the creation of a set of entities in the database. /// </summary> /// <typeparam name="TEntity">Type of the entity to create.</typeparam> /// <param name="entities">List of entities to create.</param> /// <param name="cancellationToken">Propagates notification that operations should be canceled.</param> /// <returns>Represents an asynchronous operation.</returns> private async Task ProcessCreateRangeAsync<TEntity>(List<TEntity> entities, CancellationToken cancellationToken) where TEntity : class, IEntityBase { await this.Context.AddRangeAsync(entities, cancellationToken).ConfigureAwait(false);
await this.Context.SaveChangesAsync(cancellationToken).ConfigureAwait(false); }
/// <summary> /// Updates a set of entities in the database. /// </summary> /// <typeparam name="TEntity">Type of the entity to update.</typeparam> /// <param name="entities">List of entities to update.</param> /// <param name="cancellationToken">Propagates notification that operations should be canceled.</param> /// <returns>Represents an asynchronous operation.</returns> public async Task UpdateRangeAsync<TEntity>(List<TEntity> entities, CancellationToken cancellationToken = default) where TEntity : class, IEntityBase { ArgumentNullException.ThrowIfNull(entities);
this.Context.UpdateRange(entities);
await this.Context.SaveChangesAsync(cancellationToken).ConfigureAwait(false); }
/// <summary> /// Deletes a set of entities in the database. /// </summary> /// <typeparam name="TEntity">Type of the entity to delete.</typeparam> /// <param name="entities">List of entities to delete.</param> /// <param name="cancellationToken">Propagates notification that operations should be canceled.</param> /// <returns>Represents an asynchronous operation.</returns> public async Task DeleteRangeAsync<TEntity>(List<TEntity> entities, CancellationToken cancellationToken = default) where TEntity : class, IEntityBase { ArgumentNullException.ThrowIfNull(entities);
this.Context.RemoveRange(entities);
await this.Context.SaveChangesAsync(cancellationToken).ConfigureAwait(false); }
/// <summary> /// Changes the state of a record in the database. /// </summary> /// <typeparam name="TEntity">Type of the entity to update.</typeparam> /// <param name="id">Id of the record to update.</param> /// <param name="state">Status that will be assigned to the record if it exists.</param> /// <param name="cancellationToken">Propagates notification that operations should be canceled.</param> /// <returns>Represents an asynchronous operation.</returns> public async Task ChangeStateAsync<TEntity>(Guid id, bool state, CancellationToken cancellationToken = default) where TEntity : class, IEntity { var entity = await this.Context.Set<TEntity>().FirstOrDefaultAsync(x => x.Id == id, cancellationToken: cancellationToken);
if (entity != null) { entity.IsActive = state;
await this.Context.SaveChangesAsync(cancellationToken).ConfigureAwait(false); } }
/// <summary> /// Allows multiple processes in the database in a single transaction. /// </summary> /// <typeparam name="TResult">Type of data to return if the process is successful.</typeparam> /// <param name="process">Process to execute in the transaction flow.</param> /// <param name="isolation">Specifies the transaction locking behavior for the connection.</param> /// <param name="cancellationToken">Propagates notification that operations should be canceled.</param> /// <returns>Represents an asynchronous operation.</returns> public async Task TransactionAsync<TResult>(Func<DbContext, Task> process, IsolationLevel isolation = IsolationLevel.ReadUncommitted, CancellationToken cancellationToken = default) { var strategy = this.Context.Database.CreateExecutionStrategy();
await strategy.ExecuteAsync(async (cancellation) => { using var transaction = await this.Context.Database.BeginTransactionAsync(isolation, cancellation).ConfigureAwait(false);
await process(this.Context).ConfigureAwait(false);
await transaction.CommitAsync(cancellation).ConfigureAwait(false); }, cancellationToken).ConfigureAwait(false); }}
¿Cómo Funciona?
El servicio funciona interactuando con el contexto de la base de datos (DbContext) proporcionado al momento de la instanciación de la clase. A través de sus métodos, los usuarios pueden realizar operaciones CRUD (crear, leer, actualizar, eliminar) de forma asíncrona, garantizando una correcta gestión de los cambios y la persistencia de los datos. También soporta operaciones en transacciones para asegurar la integridad de los datos.
Métodos
La clase RepositoryBase
proporciona una serie de métodos que facilitan la gestión de las entidades. A continuación, se describen los métodos más importantes:
GetContext
Type: public TContext GetContext<TContext>() where TContext : DbContext
El método GetContext
permite obtener el contexto de la base de datos asociado a un repositorio específico. En este caso, utilizaremos el repositorio de órdenes para obtener el contexto de la base de datos.
// code example
var context = orderRepository.GetContext<OrderContext>();
GetEntity
Type: public DbSet<TEntity> GetEntity<TEntity>() where TEntity : class, IEntityBase
El método GetEntity
permite obtener un conjunto de entidades de la base de datos basado en un tipo específico. En este caso, utilizaremos el repositorio de órdenes para obtener todas las órdenes de la base de datos.
// code example
var ordersCount = await orderRepository.GetEntity<OrderAggregate>().CountAsync();
CreateAsync
Type: public Task CreateAsync<TEntity>(TEntity entity, CancellationToken cancellationToken = default) where TEntity : class, IEntityBase
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.
// code example
var orderRepository = serviceProvider.GetRequiredService<IOrderRepository>();
var order = OrderAggregate.Create(Guid.NewGuid(), "Order 1", "Description 1", 1000, Guid.NewGuid(), Guid.NewGuid());
await orderRepository.CreateAsync(order);
UpdateAsync
Type: public Task UpdateAsync<TEntity>(TEntity entity, CancellationToken cancellationToken = default) where TEntity : class, IEntityBase
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.
// code example
var orderRepository = serviceProvider.GetRequiredService<IOrderRepository>();
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, Guid.NewGuid());
await orderRepository.UpdateAsync(order);}
DeleteAsync
Type: public Task DeleteAsync<TEntity>(Expression<Func<TEntity, bool>> predicate, CancellationToken cancellationToken = default) where TEntity : class, IEntityBase
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.
// code example
var orderRepository = serviceProvider.GetRequiredService<IOrderRepository>();
await orderRepository.DeleteAsync<OrderAggregate>(x => x.Id == order.Id);
CreateRangeAsync
Type: public async Task CreateRangeAsync<TEntity>(List<TEntity> entities, CancellationToken cancellationToken = default) where TEntity : class, IEntityBase
El método CreateRangeAsync
permite crear un conjunto de entidades en la base de datos de forma asincrónica. En este caso, utilizaremos el repositorio de órdenes para insertar múltiples órdenes a la vez en la base de datos.
// code example
var orderRepository = serviceProvider.GetRequiredService<IOrderRepository>();
var ordersList = new List<OrderAggregate>{ OrderAggregate.Create(Guid.NewGuid(), "Order 2", "Description 2", 2000, Guid.NewGuid(), Guid.NewGuid()), OrderAggregate.Create(Guid.NewGuid(), "Order 3", "Description 3", 3000, Guid.NewGuid(), Guid.NewGuid()), OrderAggregate.Create(Guid.NewGuid(), "Order 4", "Description 4", 4000, Guid.NewGuid(), Guid.NewGuid()), OrderAggregate.Create(Guid.NewGuid(), "Order 5", "Description 5", 5000, Guid.NewGuid(), Guid.NewGuid()),};
await orderRepository.CreateRangeAsync(ordersList);
UpdateRangeAsync
Type: public async Task UpdateRangeAsync<TEntity>(List<TEntity> entities, CancellationToken cancellationToken = default) where TEntity : class, IEntityBase
El método UpdateRangeAsync
permite actualizar un conjunto de entidades en la base de datos. En este caso, utilizaremos el repositorio de órdenes para modificar múltiples órdenes de una sola vez en la base de datos.
// code example
var orderRepository = serviceProvider.GetRequiredService<IOrderRepository>();
var ordersList = [.. orderRepository.GetEntity<OrderAggregate>().Where(x => x.Price > 3000)];
foreach (var item in ordersList){ item.Update(item.Name, item.Description, item.Price + 1000, Guid.NewGuid());}
await orderRepository.UpdateRangeAsync(ordersList);
DeleteRangeAsync
Type: public async Task DeleteRangeAsync<TEntity>(List<TEntity> entities, CancellationToken cancellationToken = default) where TEntity : class, IEntityBase
El método DeleteRangeAsync
permite eliminar un conjunto de entidades de la base de datos. En este caso, utilizaremos el repositorio de órdenes para eliminar múltiples órdenes simultáneamente de la base de datos.
// code example
var orderRepository = serviceProvider.GetRequiredService<IOrderRepository>();
var ordersList = [.. orderRepository.GetEntity<OrderAggregate>().Where(x => x.Price < 3000)];
await orderRepository.DeleteRangeAsync(ordersList);
ChangeStateAsync
Type: public async Task ChangeStateAsync<TEntity>(Guid id, bool state, CancellationToken cancellationToken = default) where TEntity : class, IEntity
El método ChangeStateAsync
permite cambiar el estado activo de una entidad en la base de datos. En este caso, utilizaremos el repositorio de órdenes para activar o desactivar una orden en lugar de eliminarla de la base de datos.
// code example
var orderRepository = serviceProvider.GetRequiredService<IOrderRepository>();
if (order is not null){ await orderRepository.ChangeStateAsync(order.Id, false);}
TransactionAsync
Type: public async Task TransactionAsync<TResult>(Func<DbContext, Task> process, IsolationLevel isolation = IsolationLevel.ReadUncommitted, CancellationToken cancellationToken = default)
El método TransactionAsync
permite ejecutar múltiples operaciones permitiendo al usuario controlar la transacción. En este caso, utilizaremos el repositorio de órdenes para crear, actualizar y eliminar una orden en una transacción única. Si alguna de las operaciones falla, se revertirán todas las operaciones realizadas en la transacción.
// code example
await orderRepository.TransactionAsync<bool>(async (context) =>{ var order = OrderAggregate.Create(Guid.NewGuid(), "Order 6", "Description 6", 6000, Guid.NewGuid(), Guid.NewGuid());
await orderRepository.CreateAsync(order);
order = orderRepository.GetEntity<OrderAggregate>().FirstOrDefault(x => x.Id == order.Id);
if (order is not null) { order.Update("Order 6 Updated", "Description 6 Updated", 7000, Guid.NewGuid());
await orderRepository.UpdateAsync(order); }
order = orderRepository.GetEntity<OrderAggregate>().FirstOrDefault(x => x.Id == order.Id);
if (order is not null) await orderRepository.DeleteAsync<OrderAggregate>(x => x.Id == order.Id);
});
Implementación
La clase RepositoryBase
es una implementación genérica que proporciona una serie de métodos comunes para interactuar con una base de datos utilizando Entity Framework Core. A continuación, se muestra la implementación de la clase RepositoryBase
:
namespace CodeDesignPlus.Net.EFCore.Repository;
/// <summary>/// This abstract class implements the most common methods for interacting with the database./// </summary>/// <remarks>/// Create a new instance of RepositoryBase./// </remarks>/// <param name="context">Represents a session with the database and can be used to query and save instances of your entities.</param>/// <exception cref="ArgumentNullException">Thrown if context is null.</exception>public abstract class RepositoryBase(DbContext context) : IRepositoryBase{ /// <summary> /// Represents a session with the database and can be used to query and save instances of your entities. /// </summary> protected readonly DbContext Context = context ?? throw new ArgumentNullException(nameof(context));
/// <summary> /// Converts the DbContext to the specified generic type. /// </summary> /// <typeparam name="TContext">Type of context to return.</typeparam> /// <returns>Returns the context of the database.</returns> public TContext GetContext<TContext>() where TContext : DbContext => (TContext)this.Context;
/// <summary> /// Gets a DbSet that can be used to query and save instances of TEntity. /// </summary> /// <typeparam name="TEntity">The type of entity for which a set should be returned.</typeparam> /// <returns>A set for the given entity type.</returns> public DbSet<TEntity> GetEntity<TEntity>() where TEntity : class, IEntityBase => this.Context.Set<TEntity>();
/// <summary> /// Creates an entity in the database. /// </summary> /// <typeparam name="TEntity">Type of the entity to create.</typeparam> /// <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 Task CreateAsync<TEntity>(TEntity entity, CancellationToken cancellationToken = default) where TEntity : class, IEntityBase { ArgumentNullException.ThrowIfNull(entity);
return this.ProcessCreateAsync(entity, cancellationToken); }
/// <summary> /// Processes the creation of an entity in the database. /// </summary> /// <typeparam name="TEntity">Type of the entity to create.</typeparam> /// <param name="entity">Entity to create.</param> /// <param name="cancellationToken">Propagates notification that operations should be canceled.</param> /// <returns>Represents an asynchronous operation.</returns> private async Task ProcessCreateAsync<TEntity>(TEntity entity, CancellationToken cancellationToken) where TEntity : class, IEntityBase { await this.Context.AddAsync(entity, cancellationToken).ConfigureAwait(false);
await this.Context.SaveChangesAsync(cancellationToken).ConfigureAwait(false); }
/// <summary> /// Updates an entity in the database. /// </summary> /// <typeparam name="TEntity">Type of the entity to update.</typeparam> /// <param name="entity">Entity to update.</param> /// <param name="cancellationToken">Propagates notification that operations should be canceled.</param> /// <returns>Represents an asynchronous operation.</returns> public Task UpdateAsync<TEntity>(TEntity entity, CancellationToken cancellationToken = default) where TEntity : class, IEntityBase { ArgumentNullException.ThrowIfNull(entity);
return this.ProcessUpdateAsync(entity, cancellationToken); }
/// <summary> /// Processes the update of an entity in the database. /// </summary> /// <typeparam name="TEntity">Type of the entity to update.</typeparam> /// <param name="entity">Entity to update.</param> /// <param name="cancellationToken">Propagates notification that operations should be canceled.</param> /// <returns>Represents an asynchronous operation.</returns> private async Task ProcessUpdateAsync<TEntity>(TEntity entity, CancellationToken cancellationToken) where TEntity : class, IEntityBase { this.Context.Set<TEntity>().Update(entity);
var idUserCreatorProperty = this.Context.Entry(entity).Metadata.FindProperty(nameof(IEntity.CreatedBy)); var createdAtProperty = this.Context.Entry(entity).Metadata.FindProperty(nameof(IEntity.CreatedAt));
if (idUserCreatorProperty != null) this.Context.Entry(entity).Property(nameof(IEntity.CreatedBy)).IsModified = false;
if (createdAtProperty != null) this.Context.Entry(entity).Property(nameof(IEntity.CreatedAt)).IsModified = false;
await this.Context.SaveChangesAsync(cancellationToken).ConfigureAwait(false); }
/// <summary> /// Deletes an entity in the database. /// </summary> /// <typeparam name="TEntity">Type of the entity to delete.</typeparam> /// <param name="predicate">A function to test each element for a condition.</param> /// <param name="cancellationToken">Propagates notification that operations should be canceled.</param> /// <returns>Represents an asynchronous operation.</returns> public Task DeleteAsync<TEntity>(Expression<Func<TEntity, bool>> predicate, CancellationToken cancellationToken = default) where TEntity : class, IEntityBase { ArgumentNullException.ThrowIfNull(predicate);
return this.ProcessDeleteAsync(predicate, cancellationToken); }
/// <summary> /// Processes the deletion of an entity in the database. /// </summary> /// <typeparam name="TEntity">Type of the entity to delete.</typeparam> /// <param name="predicate">A function to test each element for a condition.</param> /// <param name="cancellationToken">Propagates notification that operations should be canceled.</param> /// <returns>Represents an asynchronous operation.</returns> private async Task ProcessDeleteAsync<TEntity>(Expression<Func<TEntity, bool>> predicate, CancellationToken cancellationToken) where TEntity : class, IEntityBase { var entity = await this.Context.Set<TEntity>().Where(predicate).FirstOrDefaultAsync(cancellationToken: cancellationToken).ConfigureAwait(false);
if (entity != null) { this.Context.Set<TEntity>().Remove(entity);
await this.Context.SaveChangesAsync(cancellationToken).ConfigureAwait(false); } }
/// <summary> /// Creates a set of entities in the database. /// </summary> /// <typeparam name="TEntity">Type of the entity to create.</typeparam> /// <param name="entities">List of entities to create.</param> /// <param name="cancellationToken">Propagates notification that operations should be canceled.</param> /// <returns>Represents an asynchronous operation.</returns> public async Task CreateRangeAsync<TEntity>(List<TEntity> entities, CancellationToken cancellationToken = default) where TEntity : class, IEntityBase { ArgumentNullException.ThrowIfNull(entities);
await ProcessCreateRangeAsync(entities, cancellationToken).ConfigureAwait(false); }
/// <summary> /// Processes the creation of a set of entities in the database. /// </summary> /// <typeparam name="TEntity">Type of the entity to create.</typeparam> /// <param name="entities">List of entities to create.</param> /// <param name="cancellationToken">Propagates notification that operations should be canceled.</param> /// <returns>Represents an asynchronous operation.</returns> private async Task ProcessCreateRangeAsync<TEntity>(List<TEntity> entities, CancellationToken cancellationToken) where TEntity : class, IEntityBase { await this.Context.AddRangeAsync(entities, cancellationToken).ConfigureAwait(false);
await this.Context.SaveChangesAsync(cancellationToken).ConfigureAwait(false); }
/// <summary> /// Updates a set of entities in the database. /// </summary> /// <typeparam name="TEntity">Type of the entity to update.</typeparam> /// <param name="entities">List of entities to update.</param> /// <param name="cancellationToken">Propagates notification that operations should be canceled.</param> /// <returns>Represents an asynchronous operation.</returns> public async Task UpdateRangeAsync<TEntity>(List<TEntity> entities, CancellationToken cancellationToken = default) where TEntity : class, IEntityBase { ArgumentNullException.ThrowIfNull(entities);
this.Context.UpdateRange(entities);
await this.Context.SaveChangesAsync(cancellationToken).ConfigureAwait(false); }
/// <summary> /// Deletes a set of entities in the database. /// </summary> /// <typeparam name="TEntity">Type of the entity to delete.</typeparam> /// <param name="entities">List of entities to delete.</param> /// <param name="cancellationToken">Propagates notification that operations should be canceled.</param> /// <returns>Represents an asynchronous operation.</returns> public async Task DeleteRangeAsync<TEntity>(List<TEntity> entities, CancellationToken cancellationToken = default) where TEntity : class, IEntityBase { ArgumentNullException.ThrowIfNull(entities);
this.Context.RemoveRange(entities);
await this.Context.SaveChangesAsync(cancellationToken).ConfigureAwait(false); }
/// <summary> /// Changes the state of a record in the database. /// </summary> /// <typeparam name="TEntity">Type of the entity to update.</typeparam> /// <param name="id">Id of the record to update.</param> /// <param name="state">Status that will be assigned to the record if it exists.</param> /// <param name="cancellationToken">Propagates notification that operations should be canceled.</param> /// <returns>Represents an asynchronous operation.</returns> public async Task ChangeStateAsync<TEntity>(Guid id, bool state, CancellationToken cancellationToken = default) where TEntity : class, IEntity { var entity = await this.Context.Set<TEntity>().FirstOrDefaultAsync(x => x.Id == id, cancellationToken: cancellationToken);
if (entity != null) { entity.IsActive = state;
await this.Context.SaveChangesAsync(cancellationToken).ConfigureAwait(false); } }
/// <summary> /// Allows multiple processes in the database in a single transaction. /// </summary> /// <typeparam name="TResult">Type of data to return if the process is successful.</typeparam> /// <param name="process">Process to execute in the transaction flow.</param> /// <param name="isolation">Specifies the transaction locking behavior for the connection.</param> /// <param name="cancellationToken">Propagates notification that operations should be canceled.</param> /// <returns>Represents an asynchronous operation.</returns> public async Task TransactionAsync<TResult>(Func<DbContext, Task> process, IsolationLevel isolation = IsolationLevel.ReadUncommitted, CancellationToken cancellationToken = default) { var strategy = this.Context.Database.CreateExecutionStrategy();
await strategy.ExecuteAsync(async (cancellation) => { using var transaction = await this.Context.Database.BeginTransactionAsync(isolation, cancellation).ConfigureAwait(false);
await process(this.Context).ConfigureAwait(false);
await transaction.CommitAsync(cancellation).ConfigureAwait(false); }, cancellationToken).ConfigureAwait(false); }}
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.RepositoryBase.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 los métodos necesarios para gestionar las ordenes en nuestra base de datos. Esta interfaz hereda deIRepositoryBase
y proporciona una abstracción para las operaciones de creación, actualización y eliminación de ordenes.using CodeDesignPlus.Net.EFCore.Abstractions;namespace CodeDesignPlus.Net.EFCore.Sample.RepositoryBase.Repositories;public interface IOrderRepository : IRepositoryBase{} -
Implementación del Repositorio
A continuación, implementaremos la interfaz
IOrderRepository
en la claseOrderRepository
. Esta clase hereda deRepositoryBase
y proporciona una implementación concreta de los métodos definidos en la interfaz.namespace CodeDesignPlus.Net.EFCore.Sample.RepositoryBase.Repositories;public class OrderRepository(OrderContext context) : Repository.RepositoryBase(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.RepositoryBase.Entities;using Microsoft.EntityFrameworkCore;using Microsoft.EntityFrameworkCore.Metadata.Builders;namespace CodeDesignPlus.Net.EFCore.Sample.RepositoryBase.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;using System.Diagnostics.CodeAnalysis;using CodeDesignPlus.Net.EFCore.Extensions;using CodeDesignPlus.Net.EFCore.Sample.RepositoryBase.Entities;using Microsoft.EntityFrameworkCore;namespace CodeDesignPlus.Net.EFCore.Sample.RepositoryBase;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 RepositoryBase
para gestionar las ordenes en nuestra base de datos.
using CodeDesignPlus.Net.EFCore.Extensions;using CodeDesignPlus.Net.EFCore.Sample.RepositoryBase;using CodeDesignPlus.Net.EFCore.Sample.RepositoryBase.Entities;using CodeDesignPlus.Net.EFCore.Sample.RepositoryBase.Repositories;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.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>();
await context.Database.EnsureDeletedAsync();
await context.Database.EnsureCreatedAsync();
var orderRepository = serviceProvider.GetRequiredService<IOrderRepository>();
// Createvar order = OrderAggregate.Create(Guid.NewGuid(), "Order 1", "Description 1", 1000, Guid.NewGuid(), Guid.NewGuid());
await orderRepository.CreateAsync(order);
// Readvar orders = await orderRepository.GetEntity<OrderAggregate>().ToListAsync();
foreach (var item in orders){ Console.WriteLine($"Id: {item.Id}"); Console.WriteLine($"Name: {item.Name}"); Console.WriteLine($"Description: {item.Description}"); Console.WriteLine($"Price: {item.Price}"); Console.WriteLine($"Created At: {item.CreatedAt}"); Console.WriteLine($"Created By: {item.CreatedBy}"); Console.WriteLine($"Is Active: {item.IsActive}"); Console.WriteLine($"Tenant: {item.Tenant}"); Console.WriteLine();}
// 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, Guid.NewGuid());
await orderRepository.UpdateAsync(order);}
// Deleteawait orderRepository.DeleteAsync<OrderAggregate>(x => x.Id == order.Id);
// CreateRangeAsyncvar ordersList = new List<OrderAggregate>{ OrderAggregate.Create(Guid.NewGuid(), "Order 2", "Description 2", 2000, Guid.NewGuid(), Guid.NewGuid()), OrderAggregate.Create(Guid.NewGuid(), "Order 3", "Description 3", 3000, Guid.NewGuid(), Guid.NewGuid()), OrderAggregate.Create(Guid.NewGuid(), "Order 4", "Description 4", 4000, Guid.NewGuid(), Guid.NewGuid()), OrderAggregate.Create(Guid.NewGuid(), "Order 5", "Description 5", 5000, Guid.NewGuid(), Guid.NewGuid()),};
await orderRepository.CreateRangeAsync(ordersList);
// UpdateRangeAsyncordersList = [.. orderRepository.GetEntity<OrderAggregate>().Where(x => x.Price > 3000)];
foreach (var item in ordersList){ item.Update(item.Name, item.Description, item.Price + 1000, Guid.NewGuid());}
await orderRepository.UpdateRangeAsync(ordersList);
// DeleteRangeAsyncordersList = [.. orderRepository.GetEntity<OrderAggregate>().Where(x => x.Price < 3000)];
await orderRepository.DeleteRangeAsync(ordersList);
// ChangeStateAsyncorder = orderRepository.GetEntity<OrderAggregate>().FirstOrDefault();
if (order is not null) await orderRepository.ChangeStateAsync<OrderAggregate>(order.Id, false, CancellationToken.None);
// TransactionAsyncawait orderRepository.TransactionAsync<bool>(async (context) =>{ var order = OrderAggregate.Create(Guid.NewGuid(), "Order 6", "Description 6", 6000, Guid.NewGuid(), Guid.NewGuid());
await orderRepository.CreateAsync(order);
order = orderRepository.GetEntity<OrderAggregate>().FirstOrDefault(x => x.Id == order.Id);
if (order is not null) { order.Update("Order 6 Updated", "Description 6 Updated", 7000, Guid.NewGuid());
await orderRepository.UpdateAsync(order); }
order = orderRepository.GetEntity<OrderAggregate>().FirstOrDefault(x => x.Id == order.Id);
if (order is not null) await orderRepository.DeleteAsync<OrderAggregate>(x => x.Id == order.Id);
});
// GetContext
var context2 = orderRepository.GetContext<OrderContext>();
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;using CodeDesignPlus.Net.EFCore.Sample.Entities;using CodeDesignPlus.Net.EFCore.Sample.Repositories;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 registra la configuración de EFCore.var serviceCollection = new ServiceCollection();serviceCollection.AddSingleton(configuration);serviceCollection.AddEFCore(configuration); -
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 RepositoryBase
. 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 RepositoryBase
es una implementación genérica que facilita la gestión de entidades en una base de datos utilizando Entity Framework Core. Proporciona una serie de métodos comunes para realizar operaciones CRUD de forma eficiente y segura. Al utilizar esta clase, los desarrolladores pueden simplificar la creación y gestión de repositorios en sus aplicaciones .NET.