Crear una capa de servicios sobre Entity Framework aporta orden, testabilidad y claridad. Aquí verás cómo definir interfaces, implementar métodos async con await, optimizar lecturas con AsNoTracking y registrar los servicios en Program.cs usando un ciclo de vida adecuado.
¿Por qué separar en servicios con AppDbContext y controllers?
Diseñar una capa intermedia evita que el controller consuma directamente el AppDbContext. Así, la lógica de acceso a datos se concentra en servicios inyectables por dependencia, facilitando mantenimiento y pruebas.
Inyección por constructor: recibes AppDbContext en el servicio y lo asignas a un campo privado.
Buenas prácticas: nombres de métodos con sufijo Async cuando son asíncronos.
¿Qué métodos async definen IUserService e ITaskService?
Ambas interfaces comparten contratos genéricos, cambiando solo el tipo de modelo: User o TaskItem. Los nombres no incluyen el tipo (p. ej., no usar getUserById), lo que simplifica la implementación.
GetAllAsync: devuelve toda la lista como lectura.
GetByIdAsync: devuelve un elemento o null si no existe.
¿Cómo implementar UserService y TaskService con buenas prácticas?
Primero, inyecta AppDbContext por el constructor. Luego, implementa los métodos usando await y persistiendo cambios con SaveChangesAsync. Para lecturas, favorece AsNoTracking y métodos asíncronos como FirstOrDefaultAsync.
Inyección por constructor: patrón consistente, similar al logger usado en WeatherForecastController.
CreateAsync: usar Add y luego SaveChangesAsync, retornando el objeto creado.
GetAllAsync: lista completa con AsNoTracking() cuando no modificas el contexto.
GetByIdAsync: FirstOrDefaultAsync para obtener el primer elemento o null.
Nullabilidad: User? y TaskItem? para permitir null cuando no existe el ID buscado.
Cuando no vas a modificar entidades, no necesitas tracking. Usar AsNoTracking() en listas de solo lectura elimina costos de seguimiento y mejora el rendimiento en queries de lectura.
¿Cómo manejar null con tipos anulables?
GetByIdAsync puede no encontrar el elemento: devuelve null. Marca el tipo como anulable (User?, TaskItem?) para reflejar esa posibilidad y evitar errores en tiempo de compilación.
¿Cómo registrar los servicios en Program.cs y elegir el ciclo de vida?
La configuración se agrega junto a otros servicios: login, DB context, etc. Se sugiere registrar con scope y conocer alternativas: singleton y "traction".
scope: instancia por petición, estándar en acceso a datos.
singleton: una única instancia en todo el ciclo de vida.
"traction": crea una nueva instancia por cada llamado.
Nota: se recomienda scope salvo casos donde se inyecte por parámetros y por constructor al mismo tiempo.
Ejemplo de registro:
// En Program.csservices.AddScoped<IUserService, UserService>();services.AddScoped<ITaskService, TaskService>();
Siguiente paso: completa los métodos pendientes Update y Delete para ambos servicios y piensa cómo mapearlos en el controller. ¿Qué casos borde tendrás en update y cómo manejarás null en delete?
Si tienes dudas o quieres compartir tu solución de update/delete, deja un comentario y comencemos la conversación.