EF Core propone borrar tablas que no ve: cómo detectarlo antes de migrar
En aplicaciones modulares, la herramienta de migraciones carga solo una parte del modelo y puede proponer un DROP destructivo sobre una tabla legítima.

Un aviso de EF Core puede ser correcto sobre el modelo que tiene delante y, aun así, proponer una operación que te destroce la base de datos. Ocurre en aplicaciones modulares: el proceso de línea de comandos que genera las migraciones no carga los mismos ensamblados que la aplicación en ejecución, así que construye un modelo parcial. El resultado es un snapshot que sí incluye una tabla aportada por un módulo que la herramienta no ve, y un diff que concluye que esa tabla sobra.
El modelo cambia según quién lo construye
El escenario habitual: un módulo de persistencia de bajo nivel es dueño del DbContext y los módulos superiores aportan configuraciones de entidades mediante un mecanismo de descubrimiento. Así se mantiene la dirección de dependencias, porque el módulo inferior no necesita referencias hacia cada característica que lo extiende. El problema aparece cuando se lanza el tooling de migraciones desde ese proyecto de bajo nivel: el contribuyente de arriba no se carga, porque añadir la referencia crearía un ciclo entre proyectos.
La herramienta genera un modelo válido. Solo que es la porción visible desde ese proceso. El snapshot, en cambio, se creó con el modelo completo y tiene que seguir ahí para cualquier host que comparta el esquema. El comparador ve la tabla en el snapshot y no en el modelo actual, y su arreglo propuesto es lógico desde ese punto de vista estrecho: eliminarla.
Regenerar el snapshot hasta que el aviso desaparezca es tentador y es justo lo que no hay que hacer cuando el modelo de diseño está incompleto por construcción. La primera pregunta de diagnóstico es cuál es la diferencia completa que se propone, y una migración de prueba lo deja por escrito.
Acotar la excepción, no gastar la señal
Decir que el aviso es un falso positivo, sin más, es demasiado amplio. Un aviso concreto, en un contexto concreto y bajo un límite de carga documentado, puede serlo. Otras diferencias pueden ser reales. La decisión sensata es estrecha: permitir que la ruta legítima de actualización siga adelante con esa condición conocida y mantener aparte una comparación con el modelo completo como prueba autoritativa de deriva.
Silenciar el aviso elimina la fricción, no la ambigüedad. Lo que se pierde es una señal de seguridad, y hay que sustituirla por evidencia más cerca del fallo: no un desajuste abstracto, sino una migración que borra, renombra o daña una tabla que el proceso de diseño no ve. Eso se testea. Una guarda ejecutable revisa las operaciones de cada migración commiteada y falla si encuentra un drop de la tabla protegida, un rename que la saque del contrato, la eliminación de una columna de esa tabla o SQL en crudo con la operación destructiva.
Que el test no pase en vacío
Hay una trampa sutil: una prueba que escanea migraciones y no encuentra nada prohibido puede pasar porque las migraciones son seguras o porque el escaneo no vio nada. Es un aprobado vacuo. Añadir un control positivo que demuestre que el escaneo sí inspecciona la migración que creó la tabla protegida cierra las dos mitades de la afirmación. El patrón sirve para cualquier prueba del tipo "aquí no hay nada malo": escaneos de dependencias, mapas de autorización, inventarios de rutas.
La comparación con el modelo completo sigue siendo la autoridad, pero cada pieza hace un trabajo distinto: una detecta deriva real, otra bloquea el artefacto destructivo conocido y la tercera demuestra que la guarda miró algo con sentido. El coste es mantenimiento deliberado: si cambia el mecanismo de extensión, cambian los tests y la documentación. La regla práctica es simple: cuando el tooling solo ve una proyección, documenta el punto ciego, acota la excepción y codifica la propiedad de seguridad real como un test que falle.


