• Saltar a la navegación principal
  • Saltar al contenido principal

Isaw Navarro

Acercando la Tecnología

  • Inicio
  • Sobre mí
  • Curso Dev Web

EntityFramework y relaciones muchos a muchos. Cómo tratarlo sin morir en el intento.

21/02/2016 by admin 20 comentarios

Hoy vengo con un post de programación avanzada. Es un caso concreto que me he ido encontrando en los últimos proyectos .Net que he hecho. Voy a explicar algunos conceptos y truquillos de cómo trata el Entity Framework las relaciones too many – too many entre tablas.

El Entity Framework nos facilita enormemente las funcionalidades estándars de consulta, inserción y modificación de una base de datos, pero hay que conocer bien como maneja las relaciones para que no nos suponga una desventaja en lugar de una ventaja, y por mi experiencia las relaciones muchos a muchos en EF, a veces no son fáciles de tratar.

Empecemos haciendo una breve guía de cómo funciona.

Imaginemos que tenemos una base de datos donde estamos guardando información de cómics y sus autores. Sabemos que un cómic puede tener más de un autor y que un autor puede haber escrito o dibujado más de un cómic, por lo que un posible diagrama reducido de BBDD quedaría así.

DiagramaBBDDAhora bien,… el EF al mapear estas tablas genera el siguiente diagrama.

EF

Cómo vemos, el EF detecta que es una relación muchos a muchos y en lugar de generar la tabla explosionada intermedia incorpora una colección en cada una de las clases haciendo referencia a la otra clase. El código generado sería el siguiente:

autores comics

Insertar datos

Para insertar datos en la tabla intermedia podemos hacer lo siguiente: incorporar en las vistas una select multiple con el listado de autores o cómics (dependiendo de la vista en la que estemos) y enviarlo al controlador para que lo incorpore en la colección correspondiente de la clase. De esta forma añadiendo el nuevo autor o cómic, directamente se añadirán las relaciones. Veámos como sería con código:

Suponemos que queremos dar de alta un cómic y asignarle sus autores que previamente ya existen en la base de datos:

  1. En el Get de Create del controlador de cómics, incluiremos una lista con todos los autores de la base de datosGetCreateAutores
  2. En la vista Create de cómics añadiremos una select multiple que recogerá todos los autores que hemos pasado a través del ViewBag para que seleccionemos los que queremos relacionar con el nuevo cómic. (Lógicamente si existen muchos autores optaremos por otro componente o bien utilizaremos librerías o ajax para que nos facilite la búsqueda entre ellos)
    vistaCreateAutores Visualmente podríamos tener algo de este estilo:CreateComics
  3. Una vez seleccionado los autores que queremos añadirle al comic, los enviamos como una lista al controlador, donde los recogeremos, añadiremos a la colección de Autores de la clase Comics, haciendo de esta forma que cuando llamemos a su método Add, añadirá no sólo la información en la tabla de Comics, sino que también añadirá la relación con sus autores en la tabla ComicAutor:
  4. postCreatecomicEn base de datos obtendremos este resultado: comicAutorCreate

Bueno,… hasta ahora parece que todo bien y no es complicado,… funciona perfecto y nos ahorra tener que llamar explícitamente al método Add de la tabla ComicAutor.

Vamos a ver cómo funciona ahora el modificar con el EF

Modificar datos

Para el proceso de modificar datos, hay muchas formas de hacerlo (como en todo proceso de programación) pero la que suelo usar más a menudo sería similar al ya explicado para el create, que sería añadir en el controlador (en el Get Edit) una lista con todos los autores y pasarla a la vista para rellenar una select multiple. En el caso del Edit, además debemos pasar los autores que ya estan dados de alta en la tabla intermedia para poder hacer que en la vista salgan seleccionados. Para ello suelo añadir un Include que me devuelva la colección de los Autores en la propia clase de Comics que quiero editar. El código sería este:

  1. En el Get de Edit del controlador del cómic, incluiremos una lista con todos los autores de la base de datos y añadiremos en la obtención del comic el Include para que traiga los valores de la tabla ComicAutorgetEdit
  2. En la vista Edit de cómics añadiremos una select multiple con la lista de todos los autores y una variable que añadirá el texto «selected» a los autores que ya están relacionados con el cómic para que aparezcan seleccionados.viewEditVisualmente quedaría así
  3. vistaEditComicImaginemos ahora que queremos hacer cambios en los autores tal y como se muestra en la imagen siguiente:
  4. cambioAutoresEn este caso nos encontramos con 3 situaciones diferentes: Hay que quitar un autor (Ino Asano), añadir otro (Tsugumi Oba) y mantener otro (Takeshi Obata).
    En nuestra ingenuidad, y creyendo que la programación es lógica y casi automática podríamos pensar que una solución como la del Create bastaría para hacer esta modificación, y que el propio EF se encargaría de detectar qué es lo que tiene que quitar, añadir o mantener,… pero no, lo siento,… no es tan adivino.
    La solución reflejada en la imagen siguiente no funciona, lo único que hace es modificar los cambios que realizamos en los datos de la clase Comics pero no modifica las relaciones con Autores, ya que sólo cambia el estado a modificado de la entidad Comic.noeditcomicY entonces os preguntareis,… ¿cómo puedo cambiar el estado de los registros de la entidad ComicAutor si el EF no me ha creado una clase para ella?. Pues efectivamente éste es el quid de la cuestión y después de muchos batacazos y probar diferentes formas de hacerlo, lo encontré,… por eso decidí escribir este post. No os impacientéis que os lo cuento enseguida,…
  5. El truco está en primero guardar los datos de la clase Comics y después volver a recuperarla con el Include de la colección de autores para tratar la colección y hacer las operaciones pertinentes para borrar, añadir o mantener los datos de ComicAutor.
    Una de las formas sería comparar en el controlador la lista de autores antiguos, con la de los autores seleccionados en la vista y hacer operaciones entre ellas: los datos que sean comunes, mantenerlos,… si hay datos en la lista antigua que no se haya seleccionado en la nueva, quitarlos,… y si hay datos en la lista nueva que no estén en la antigua añadirlos.
    Pero la verdad, es que yo soy de la ley del mínimo esfuerzo y me parece un poco engorroso el comparar listas en este caso, así que yo prefiero, borrar todas las relaciones que había anteriormente en la colección de Autores para ese cómic y añadir las nuevas que vienen seleccionadas desde la vista, tal y como muestro en la siguiente imagen:editComicAutores

OJO!! CASO ESPECIAL CON DISTINTO DBCONTEXT

En el ejemplo que estoy haciendo estoy trabajando con el dbContext en el propio controlador, si tenemos una arquitectura de software dividia en proyectos donde pasamos toda la lógica de acceso a datos a otro proyecto con otro dbContext, es posible que tengamos que hacer primero un Detach de cada uno de los Autores antes de poder borrar las relaciones con Comic.  En la siguiente imagen muestro como hacer el Detach antes de borrar:dbcontext

Y AQUÍ VIENE LO BUENO: TRUCO PARA ENGAÑAR AL ENTITY

Si te pasa como a mi  que eres de la vieja escuela de programadores y te gusta tener el control de todas las tablas y no dejarle la gestión al Entity Framework, hay un truco para «engañarle» y que te cree la clase de la tabla intermedia, con lo que serás tú quien hagas cuando tú quieras y cómo quieras los inserts, updates y deletes.

El truco es tan fácil cómo añadirle una columna más a la tabla ComicAutor que no esté relacionada con ninguna otra tabla. En mi caso podría ser añadir un campo Función donde se pudiera poner que es lo que hace ese Autor en ese Cómic, por ejemplo si hace el guión, el dibujo, entintado…

También en muchos casos se podría añadir columnas de auditorías con el usuario que modifica y el timestamp de la modificación o creación,… o si no sabes que añadir, incluye simplemente una columna Dummy que no utilices para nada,… el caso es añadir algo,..

Con esta columna añadida lo que conseguimos es que el EF mapee las tablas con el siguiente diagrama:

AutoresComics

Y cree las clases siguientes:

ClassAutores

classComic

classComicAutor

Y de esta forma tendrás acceso a todos los métodos de la clase ComicAutor para añadir, modificar, borrar y manejar los datos como quieras,….

metodosComicAutorYo casi lloré con este descubrimiento,…. TT,… gracias a él ya no le tengo tanta manía a las relaciones muchos a muchos con EF.

Bueno, al final me ha salido un poco largo y denso este POST, pero espero que te ayude a entender más sobre este tema y a encontrar truquillos para manejarlos mejor.

Nos leemos en el siguiente

Technology is coming!!!

Publicado en: .Net, Programación

Interacciones con los lectores

Comentarios

  1. jonathan dice

    11/09/2016 a las 16:20

    muy bueno muy bien explicado

    Responder
  2. José Miquilena dice

    24/12/2016 a las 13:28

    hola, excelente post muy bien explicado. esto era lo esta buscando, yo estaba como tu partiendome el coco jejejeje.
    saludos desde Venezuela

    Responder
  3. YARR Blog dice

    17/07/2017 a las 17:12

    Muchas gracias, me fue de muchas ayuda.

    Responder
  4. Byron dice

    20/07/2017 a las 21:29

    Me habría mucho gustado q pongas el ejemplo de hacer la eliminación por el camino largo.
    Cómo garantizas consistencia en caso q se corte comunicación con la base si ya enviaste a realizar el Delete??

    Responder
  5. Alu dice

    11/10/2017 a las 08:24

    GENIO…gracias…

    Responder
  6. Hernan dice

    29/04/2018 a las 04:11

    Me ayudaste un Monton!!! Gracias Loco!!!!!

    Responder
  7. Andres Hernandez dice

    19/07/2018 a las 22:03

    Muy Buen Post.
    Quisiera saber si es posible que en la tabla intermedia los datos que se almacene no sean los de la columna ID sino una columna que denomine CodUsuario, por decir un nombre. la cual representa un valor alfanumérico. Cual es el fin, si se borra el usuario y se crea nuevamente el ID cambia automáticamente porque es el incrementa, pero su código no cambiaría ya que es propio.
    Gracias y saludos desde Venezuela.

    Responder
  8. Alvaro dice

    23/08/2018 a las 15:02

    De donde sale EFMenytoMeny?

    Responder
    • admin dice

      01/03/2020 a las 16:24

      El EFManytoMany que sale en las vistas es el namespace de mi proyecto.
      No tiene por qué ser ese, solo lo he puesto yo así para identificar este ejemplo.
      Y lo añado en las vistas simplemente para que al poner el punto el programa me ofrezca la lista de posibilidades y de esa forma ir más rápido a la hora de programar (o no tener que pensar tanto).
      Si pones using al inicio no hace falta que se añada, solo llamando a Autores o Comics sería bastante.
      Espero haberte ayudado!

      Responder
  9. Jancer Fabian dice

    28/09/2018 a las 00:12

    Como serian los controladores y vistas al utilizar la ultima forma, donde se crea una clase intermedia ??

    Responder
  10. Hans Ulloa dice

    10/12/2018 a las 01:19

    excelente muy bien explicado

    Responder
  11. THEO LOEFFELMANN IBARRA dice

    19/12/2018 a las 17:31

    Sonaré machista quizás, pero que buen trabajo, no muchas mujeres entienden del desarrollo y menos dan una explicación igual, de los hombres mismo caso no lo explican tan bien, sirvió mucho, gracias

    Responder
    • admin dice

      19/12/2018 a las 19:14

      Bueno,… tal vez es que ahora tenemos más oportunidades de demostrar que somos igual de capaces! ;-P 😜
      Gracias por el comentario, me alegro que te haya ayudado.

      Responder
  12. Alex García dice

    08/07/2019 a las 18:10

    Excelente solución.. te pasaste! jaja

    Responder
  13. Patricio Mera dice

    16/10/2019 a las 20:40

    Hola y gracias por la explicación. Una duda, cuando se usa DB First se crean las dos clases (Autores y Comics) y a través de las propiedades de navegación se infiere a VS a crear la tabla intermedia. Si queremos agregar campos a esa tabla intermedia debemos crear la clase de la misma manualmente?

    Saludos desde Ecuador

    Responder
    • admin dice

      01/03/2020 a las 16:20

      Si utilizas db First, significa que ya tienes las tablas creadas. Por lo tanto en la tabla intermedia que ya está creada es donde le añades la columna nueva que no esté relacionada con las otras tablas.
      Al hacer eso y crear el modelo desde la base de datos, ya te creará la clase de la tabla intermedia automáticamente de forma separada.
      Si utilizas el model first, lo mismo,… creas en la definición del modelo las columnas de más y así tendrás la clase separada.
      Espero haberte ayudado!

      Responder
  14. Esteban dice

    23/01/2020 a las 16:22

    De donde sale EFManytoMany?

    Responder
    • admin dice

      01/03/2020 a las 16:24

      El EFManytoMany que sale en las vistas es el namespace de mi proyecto.
      No tiene por qué ser ese, solo lo he puesto yo así para identificar este ejemplo.
      Y lo añado en las vistas simplemente para que al poner el punto el programa me ofrezca la lista de posibilidades y de esa forma ir más rápido a la hora de programar (o no tener que pensar tanto).
      Si pones using al inicio no hace falta que se añada, solo llamando a Autores o Comics sería bastante.
      Espero haberte ayudado!

      Responder
  15. manu dice

    29/02/2020 a las 17:35

    por dios me soluciona la vida este post , sos una GENIA 🙂

    Responder
  16. Sergiii dice

    28/01/2021 a las 01:15

    Todo un crack maestro te felicito muy util muy bien expicado. Es una exelente referencia para i iniciantes. Saludos dease Argentina.

    Responder

Deja una respuesta Cancelar la respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.

Copyright © 2023 · Genesis Sample on Genesis Framework · WordPress · Acceder