Repository Pattern
The Tournament Management App implements the Repository Pattern to abstract data access logic and provide a clean separation between the business logic and data persistence layers. Each domain entity has a corresponding repository interface and implementation.
Architecture
Layer Structure
Torneo.App.Frontend (Presentation)
↓ (Uses)
Repositories (Data Access)
↓ (Uses)
DataContext (Entity Framework Core)
↓ (Uses)
SQLite Database
Key Components
Interfaces Define the contract for data operations (CRUD + custom queries)
Implementations Concrete classes that interact with Entity Framework Core DbContext
DataContext EF Core DbContext managing all DbSets and database configuration
Dependency Injection Repositories registered as singletons in Program.cs
Available Repositories
Repository Interface Purpose DirectorTecnico IRepositorioDTManage technical directors Equipo IRepositorioEquipoManage teams Jugador IRepositorioJugadorManage players Municipio IRepositorioMunicipioManage municipalities Partido IRepositorioPartidoManage matches Posicion IRepositorioPosicionManage player positions
Common Patterns
Standard CRUD Operations
All repositories implement consistent CRUD operations:
Add Entity
Get All
Get By ID
Update
Delete
public DirectorTecnico AddDT ( DirectorTecnico directorTecnico )
{
var dtInsertado = _dataContext . DirectoresTecnicos . Add ( directorTecnico );
_dataContext . SaveChanges ();
return dtInsertado . Entity ;
}
Eager Loading with Include
Repositories use .Include() to load related entities:
var equipos = _dataContext . Equipos
. Include ( e => e . Municipio ) // Load municipality
. Include ( e => e . DirectorTecnico ) // Load technical director
. Include ( e => e . Jugadores ) // Load players
. Include ( e => e . PartidosLocal ) // Load home matches
. Include ( e => e . PartidosVisitante ) // Load away matches
. AsNoTracking ()
. ToList ();
AsNoTracking for Read Operations
Query operations use .AsNoTracking() for better performance:
var jugadores = _dataContext . Jugadores
. Include ( j => j . Equipo )
. Include ( j => j . Posicion )
. AsNoTracking () // Improves read performance
. ToList ();
AsNoTracking() tells EF Core not to track entities in the change tracker, improving performance for read-only queries.
Duplicate Validation
All repositories implement a validateDuplicates method:
public bool validateDuplicates ( DirectorTecnico dtIngresado )
{
try
{
IEnumerable < DirectorTecnico > allDT = GetAllDTs ();
bool duplicado = false ;
foreach ( DirectorTecnico directoresTecnicos in allDT )
{
if ( directoresTecnicos . Documento == dtIngresado . Documento . Trim ())
{
if ( directoresTecnicos . Id == dtIngresado . Id )
{
duplicado = false ; // Same entity being edited
}
else
{
duplicado = true ;
break ;
}
}
}
return duplicado ;
}
catch ( Exception e )
{
Console . WriteLine ( "Error Validacion duplicado " + e . Message );
return false ;
}
}
DataContext Configuration
The DataContext class inherits from DbContext and manages all entity sets:
using Microsoft . EntityFrameworkCore ;
using Torneo . App . Dominio ;
namespace Torneo . App . Persistencia
{
public class DataContext : DbContext
{
public DbSet < Municipio > Municipios { get ; set ; }
public DbSet < DirectorTecnico > DirectoresTecnicos { get ; set ; }
public DbSet < Equipo > Equipos { get ; set ; }
public DbSet < Partido > Partidos { get ; set ; }
public DbSet < Posicion > Posiciones { get ; set ; }
public DbSet < Jugador > Jugadores { get ; set ; }
protected override void OnConfiguring ( DbContextOptionsBuilder optionsBuilder )
{
if ( ! optionsBuilder . IsConfigured )
{
var connectionString = Environment . GetEnvironmentVariable ( "DATABASE_CONNECTION_STRING" )
?? "Data Source=/app/Torneo.db" ;
optionsBuilder . UseSqlite ( connectionString );
}
}
protected override void OnModelCreating ( ModelBuilder modelBuilder )
{
base . OnModelCreating ( modelBuilder );
// Set all foreign keys to Restrict delete behavior
foreach ( var relationship in modelBuilder . Model . GetEntityTypes ()
. SelectMany ( e => e . GetForeignKeys ()))
{
relationship . DeleteBehavior = DeleteBehavior . Restrict ;
}
}
}
}
The DataContext configures all foreign keys with DeleteBehavior.Restrict to prevent cascading deletes and maintain referential integrity.
Dependency Injection Setup
Repositories are registered in Program.cs as singletons :
builder . Services . AddSingleton < IRepositorioMunicipio , RepositorioMunicipio >();
builder . Services . AddSingleton < IRepositorioDT , RepositorioDT >();
builder . Services . AddSingleton < IRepositorioEquipo , RepositorioEquipo >();
builder . Services . AddSingleton < IRepositorioPartido , RepositorioPartido >();
builder . Services . AddSingleton < IRepositorioPosicion , RepositorioPosicion >();
builder . Services . AddSingleton < IRepositorioJugador , RepositorioJugador >();
Using Repositories in Controllers
public class EquipoController : Controller
{
private readonly IRepositorioEquipo _repositorioEquipo ;
private readonly IRepositorioMunicipio _repositorioMunicipio ;
private readonly IRepositorioDT _repositorioDT ;
public EquipoController (
IRepositorioEquipo repositorioEquipo ,
IRepositorioMunicipio repositorioMunicipio ,
IRepositorioDT repositorioDT )
{
_repositorioEquipo = repositorioEquipo ;
_repositorioMunicipio = repositorioMunicipio ;
_repositorioDT = repositorioDT ;
}
}
Error Handling
Repositories use two main error handling patterns:
1. Exception Throwing
public DirectorTecnico UpdateDT ( DirectorTecnico dt )
{
var dtEncontrado = _dataContext . DirectoresTecnicos . Find ( dt . Id );
if ( dtEncontrado != null )
{
// Update logic
_dataContext . SaveChanges ();
}
return dtEncontrado ?? throw new Exception ( "DT not found" );
}
2. Null Returns
public Equipo DeleteEquipo ( int idEquipo )
{
var equipoEncontrado = GetEquipo ( idEquipo );
if ( equipoEncontrado != null )
{
_dataContext . Equipos . Remove ( equipoEncontrado );
_dataContext . SaveChanges ();
}
else
{
Console . WriteLine ( "No se encontró el equipo" );
}
return equipoEncontrado ; // May return null
}
Database Provider
The application uses SQLite as its database provider:
Connection String : Data Source=/app/Torneo.db
Provider : Microsoft.EntityFrameworkCore.Sqlite
Configurable : Via DATABASE_CONNECTION_STRING environment variable
The codebase includes commented-out SQL Server configuration for future migration scenarios.
Best Practices
Use AsNoTracking for Read Operations
Apply .AsNoTracking() to queries that don’t need to update entities. This improves performance by preventing EF Core from tracking changes.
Eager Load Related Entities
Use .Include() to load related entities in a single query, avoiding N+1 query problems.
Validate Before Persisting
Call validateDuplicates before Add/Update operations to prevent constraint violations.
Check for null returns from repository methods before accessing properties.
Always inject repositories through constructor injection rather than creating instances directly.
Next Steps
DirectorTecnico Repository Manage technical directors
Equipo Repository Manage teams with advanced queries
Jugador Repository Manage players and positions
Partido Repository Manage matches and scores