Skip to main content

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

RepositoryInterfacePurpose
DirectorTecnicoIRepositorioDTManage technical directors
EquipoIRepositorioEquipoManage teams
JugadorIRepositorioJugadorManage players
MunicipioIRepositorioMunicipioManage municipalities
PartidoIRepositorioPartidoManage matches
PosicionIRepositorioPosicionManage player positions

Common Patterns

Standard CRUD Operations

All repositories implement consistent CRUD operations:
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:
DataContext.cs
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:
Program.cs (Lines 16-21)
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

Apply .AsNoTracking() to queries that don’t need to update entities. This improves performance by preventing EF Core from tracking changes.
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

Build docs developers (and LLMs) love