none
MVC, EF y modelos con claves externas RRS feed

  • Pregunta

  • Muy buenas,

    he estado probando el framework MVC junto con EF y la verdad es que se obtienen aplicaciones básicas de forma muy rápida mezclando ambos entornos. Sin embargo, a la hora de realizar algunas cosas "más complicadas" la verdad es que no lo veo tan fácil de lograr.

    El problema actual está relacionado con el manejo de tablas que tienen claves externas (FK) con otras tablas de la base de datos. Imaginemos que tenemos dos: una tabla A y una tabla B, con los siguientes campos:

    TablaA

    • Id
    • Campo1
    • IdTablaB

    TablaB

    • Id
    • Campo2

     

    A partir de esto, creamos nuestro modelo con el EF que contiene las entidades EntidadA y EntidadB. En nuestra vista Index, queremos mostrar un listado de los objetos presentes de A con los valores de sus diferentes campos; sin embargo, en lugar de mostrar el valor de IdTablaB queremos que se muestre el valor de Campo2 con el que se encuentra relacionado.

    Para lograrlo, he realizado en la acción Index del controlador lo siguiente:

            public ActionResult Index()
            {
                var lista = (from B in _data.TablaB
                               select new
                               {
                                   Id = B.Id,
                                   Campo2 = B.Campo2
                               });
    
                Dictionary<int, string> relacion = new Dictionary<int, string>();
    
                foreach (var i in lista)
                {
                    relacion.Add(i.Id, i.Campo2);
                }
    
                ViewData["listaCompleta"] = relacion;
                return View("Index", _data.TablaA.ToList());
    
            }

    Posteriormente, en una vista tipada del tipo lista con el modelo EntidadA lo he mostrado de la siguiente forma:

    <asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
    
        <h2>Index</h2>
    
        <table>
            <tr>
                <th></th>
                <th>
                    Id
                </th>
                <th>
                    Campo1
                </th>
                <th>
                    Campo2
                </th>
             </tr>
    
        <% foreach (var item in Model) { %>
        
            <tr>
                <td>
                    <%= Html.ActionLink("Edit", "Edit", new { id=item.Id }) %> |
                    <%= Html.ActionLink("Details", "Details", new { id=item.Id })%>
                </td>
                <td>
                    <%= Html.Encode(item.Id) %>
                </td>
                <td>
                    <%= Html.Encode(item.Campo1) %>
                </td>
                 <td>
                    <% Dictionary<int,string> listado = (Dictionary<int,string>) ViewData["listaCompleta"]; %>
                    <%= Html.Encode(listado[item.IdCampo2]) %>     
                </td>
               </tr>
        
        <% } %>
    
        </table>
    
        <p>
            <%= Html.ActionLink("Create New", "Create") %>
        </p>
    
    </asp:Content>

    La cuestión es que de esta forma funciona y se impime correctamente lo que deseo. Sin embargo, lo veo como un apaño de andar por casa.

    Estoy convencido de que hay otras formas mejores de hacerlo y que, por desconocimiento mío, no sé realizarlo.

    ¿Algún consejo sobre cómo trabajar con situaciones como estas con ASP.Net MVC y EF?

    Un saludo y gracias por las respuestas!

    sábado, 3 de abril de 2010 17:53

Respuestas

  • Hola Jose Angel,

    Hablamos de EF1 verdad?

    EF1 no hace lazy loading automatico, tienes que cargarlos explicitamente que siguiendo tu ejemplo seria algo como:

     

       public ActionResult Index()
            {
                return View("Index", _data.TablaA.Include("TablaB").ToList();
            }

    Y en la vista te quedaría:
    <td>
                    <%= Html.Encode([item.TablaB.Campo2]) %>     
    </td>

    Afortunadamente con EF4 ya podemos configurar el lazy loading automático, entre otras muchas mejoras :)

     

    Un saludo,


    Colabora con el foro: Si este mensaje te es de utilidad márcalo como respuesta.

    Alfredo Fernández


    sábado, 3 de abril de 2010 20:18
  • Hola, José Angel.

    Una posibilidad que tienes es hacer la lectura de una sóla vez con EF, trayéndote los datos tanto de TablaA como de TablaB, algo así:

    var lista = 
         (from a in _data.Tabla1.Include("Tabla2") select a)
         .ToList();

    De esta forma, la variable "lista" contendrá una colección IEnumerable<Tabla1>, que podrás pasar a la vista sin problema (tipándola a esa clase), para iterar sobre ella. Y lo mejor, es que tendrás disponible las propiedades que necesitas de TablaA y TablaB, es decir, podrás hacer algo como:

        <% foreach (var item in Model) { %>
        
            <tr>
                <td>
                    <%= Html.ActionLink("Edit", "Edit", new { id=item.Id }) %> |
                    <%= Html.ActionLink("Details", "Details", new { id=item.Id })%>
                </td>
                <td>
                    <%= Html.Encode(item.Id) %>
                </td>
                <td>
                    <%= Html.Encode(item.Campo1) %>
                </td>
                <td>
                    <%= Html.Encode(item.TablaB.Campo2) %>
                </td>
               </tr>
        
        <% } %>
    

    Observa en la última columna cómo navegas desde el objeto de tipo TablaA hacia el objeto de TablaB asociado, de forma directa :-)

    Otra posibilidad que tienes es independizar la vista de tus clases del Modelo. Para ello se suelen utilizar clases intermedias que contienen exactamente los datos que necesita la vista para maquetarse.

    Por ejemplo, en tu caso, dado que sólo usas los campos TablaA.Id, TablaA.Campo1 y TablaB.Campo2, podrías crear una clase así:

    public class MiViewModel
    {
        public int Id { get; set; }
        public string Campo1 { get; set; }
        public string Campo2 { get; set; }
    }

    De esta forma la vista sería tipada con IEnumerable<MiViewModel>, que sería la colección que recorrerías en el bucle principal, y sólo tendrías que encargarte de enviar su información al cliente, sin ningún tipo de lógica de por medio.

    Ya desde el controlador deberías obtener los datos del Modelo y adaptarlos para que puedan "encajar" en la vista, algo parecido a:

        var lista = (from a in _data.Tabla1.Include("Tabla2")
                             select new MiViewModel
                             {
                                 Id = a.Id,
                                 Campo1 = a.Campo1,
                                 Campo2 = a.Tabla2.Campo2
                             }
    ).ToList(); return View(lista);

    Espero que te sea de ayuda.

    Saludos.


    José M. Aguilar
    Variable not found
    sábado, 3 de abril de 2010 20:24

Todas las respuestas

  • Hola Jose Angel,

    Hablamos de EF1 verdad?

    EF1 no hace lazy loading automatico, tienes que cargarlos explicitamente que siguiendo tu ejemplo seria algo como:

     

       public ActionResult Index()
            {
                return View("Index", _data.TablaA.Include("TablaB").ToList();
            }

    Y en la vista te quedaría:
    <td>
                    <%= Html.Encode([item.TablaB.Campo2]) %>     
    </td>

    Afortunadamente con EF4 ya podemos configurar el lazy loading automático, entre otras muchas mejoras :)

     

    Un saludo,


    Colabora con el foro: Si este mensaje te es de utilidad márcalo como respuesta.

    Alfredo Fernández


    sábado, 3 de abril de 2010 20:18
  • Hola, José Angel.

    Una posibilidad que tienes es hacer la lectura de una sóla vez con EF, trayéndote los datos tanto de TablaA como de TablaB, algo así:

    var lista = 
         (from a in _data.Tabla1.Include("Tabla2") select a)
         .ToList();

    De esta forma, la variable "lista" contendrá una colección IEnumerable<Tabla1>, que podrás pasar a la vista sin problema (tipándola a esa clase), para iterar sobre ella. Y lo mejor, es que tendrás disponible las propiedades que necesitas de TablaA y TablaB, es decir, podrás hacer algo como:

        <% foreach (var item in Model) { %>
        
            <tr>
                <td>
                    <%= Html.ActionLink("Edit", "Edit", new { id=item.Id }) %> |
                    <%= Html.ActionLink("Details", "Details", new { id=item.Id })%>
                </td>
                <td>
                    <%= Html.Encode(item.Id) %>
                </td>
                <td>
                    <%= Html.Encode(item.Campo1) %>
                </td>
                <td>
                    <%= Html.Encode(item.TablaB.Campo2) %>
                </td>
               </tr>
        
        <% } %>
    

    Observa en la última columna cómo navegas desde el objeto de tipo TablaA hacia el objeto de TablaB asociado, de forma directa :-)

    Otra posibilidad que tienes es independizar la vista de tus clases del Modelo. Para ello se suelen utilizar clases intermedias que contienen exactamente los datos que necesita la vista para maquetarse.

    Por ejemplo, en tu caso, dado que sólo usas los campos TablaA.Id, TablaA.Campo1 y TablaB.Campo2, podrías crear una clase así:

    public class MiViewModel
    {
        public int Id { get; set; }
        public string Campo1 { get; set; }
        public string Campo2 { get; set; }
    }

    De esta forma la vista sería tipada con IEnumerable<MiViewModel>, que sería la colección que recorrerías en el bucle principal, y sólo tendrías que encargarte de enviar su información al cliente, sin ningún tipo de lógica de por medio.

    Ya desde el controlador deberías obtener los datos del Modelo y adaptarlos para que puedan "encajar" en la vista, algo parecido a:

        var lista = (from a in _data.Tabla1.Include("Tabla2")
                             select new MiViewModel
                             {
                                 Id = a.Id,
                                 Campo1 = a.Campo1,
                                 Campo2 = a.Tabla2.Campo2
                             }
    ).ToList(); return View(lista);

    Espero que te sea de ayuda.

    Saludos.


    José M. Aguilar
    Variable not found
    sábado, 3 de abril de 2010 20:24
  • Hola Alfredo,

    estoy con el EF4, ASP.Net MVC 2 y VS 2010. Así que voy a ver en qué consiste lo que comentas de LazyLoading porque no lo conocía :)

    Gracias por la respuesta!

    sábado, 3 de abril de 2010 22:38
  • Hola José,

    lo que comentas en la segunda parte, sería algo como crear otro modelo a partir del modelo que tengo por debajo con el EF. Esta aproximación, además de la de Alfredo la veo también muy interesante de cómo afrontar el problema que tenía.

    Gracias también por tu respuesta!

    sábado, 3 de abril de 2010 22:41