1

I want to fetch data from two EF tables (Menus and MenuItems). Then I would like to place the result into a ViewModel. So I can easily loop through the data in the Razor View.

I've got it working. But I'm new to .Net MVC so I was wondering if my code could be improved. Maybe the queries can be merged and a lot shorter. And I'm using AsEnumerable(), I'm not sure if that is the best approach.

I think my attempt is okay. But I would like to hear what you think. Any suggestion for code improvement is welcome. Thanks.

The controller class:

   public ActionResult Index(int? id)
        {
            if (id == null) return RedirectToAction("Index", new { controller = "Menu"      });

        var menu =
            (from m in _db.Menus
             join mi in _db.MenuItems on m.Id equals mi.MenuId
             where m.Id == id
             select m).First();

        var menuItems =
            (from mi in _db.MenuItems.AsEnumerable()
             join m in _db.Menus on mi.MenuId equals m.Id
             where m.Id == id
             select new MenuItem
             {
                 Id = mi.Id,
                 Name = mi.Name,
                 Href = mi.Href,
                 CssClass = mi.CssClass,
                 CssId = mi.CssId,
                 Title = mi.Title,
                 Weight = mi.Weight
             });

        var model = new MenuModelView
        {
            Id = menu.Id,
            Name = menu.Name,
            CssClass = menu.CssClass,
            CssId = menu.CssId,
            Deleted = menu.Deleted,
            MenuItems = menuItems
        };

        return View(model);
    }

The ViewModel class:

using System.Collections.Generic;

namespace DesignCrew.Areas.Admin.Models
{
    public class MenuModelView
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string CssClass { get; set; }
        public string CssId { get; set; }
        public bool Deleted { get; set; }
        public IEnumerable<MenuItem> MenuItems { get; set; }
    }
}

The Razor View:

@model DesignCrew.Areas.Admin.Models.MenuModelView

@{
    ViewBag.Title = "Index";
}

<h2>Menu - @Html.DisplayFor(model => model.Name, new { @class = "control-label col-md-2" })</h2>

<p>
    @Html.ActionLink("Create New Item", "Create")
</p>

<table class="table">
    <tr>
        <th>
            @Html.DisplayNameFor(model => model.MenuItems.First().Id)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.MenuItems.First().Name)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.MenuItems.First().Href)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.MenuItems.First().Title)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.MenuItems.First().CssClass)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.MenuItems.First().CssId)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.MenuItems.First().ParentId)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.MenuItems.First().Weight)
        </th>
        <th>Options</th>
    </tr>

    @foreach (var item in Model.MenuItems)
    {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => item.Name)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Name)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Href)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Title)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.CssClass)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.CssId)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.ParentId)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Weight)
            </td>
            <td>
                @Html.ActionLink("Edit", "Edit", new { id = item.Id }, new { @class = "link-menu" }) |
                @Html.ActionLink("Delete", "Delete", new { id = item.Id }, new { @class = "link-menu" })
            </td>
        </tr>
    }
</table>
Dan Dumitru
  • 5,348
  • 1
  • 33
  • 43
Floris
  • 2,727
  • 2
  • 27
  • 47
  • Given that you seem to be re-using your EF entities in the Viewmodel, you don't need to explicitly `new` and `map` (`select new MenuItem {}...`) a clone of the entities into the `ViewModel` - simply use the EF referenced ones. Also, even if you did have similar dedicated `ViewModels`, a tool like `AutoMapper` would take the pain out of the manual mapping. – StuartLC Jun 04 '14 at 08:25
  • Also, you probably don't need [`AsEnumerable()`](http://stackoverflow.com/a/2895178/314291) – StuartLC Jun 04 '14 at 08:42
  • Can you provide some examples? – Floris Jun 04 '14 at 08:46
  • Not sure if this helps but I answered similar menu related question. Here is the [Link](http://stackoverflow.com/questions/22498255/how-to-creating-a-dynamic-sub-menus-by-sub-categories/22503615#22503615) – Nilesh Jun 04 '14 at 09:31

1 Answers1

1

If your EF model has a navigable foreign key between Menu and MenuItem, you won't need to explicitly join the two tables, and you can eager load the child MenuItems at the same time you fetch the parent Menu, and simply navigate from parent to child:

var menu = _db.Menus
              .Include("MenuItems") // Or, use the typed version on newer EF's
              .First(m => m.id == id); // Many LINQ expressions allow predicates

return new MenuModelView
 {
    Menu = menu.Name,
    CssClass = menu.CssClass,
    CssId = menu.CssId,
    Deleted = menu.Deleted,
    MenuItems = menu.MenuItems
 }

And, since there seems to be much commonality in the MenuModelView vs the Menu EF entity, you could look at using AutoMapper. Once configured, this would allow you to replace the manual mapping step with something like:

return Mapper.Map<Menu, MenuModelView>(menu);

Edit

Here's a cheap and nasty ViewModel, which wraps your EF entities, and augments it with presentation tier data. Purists would probably note that you should create new classes for the wrapped EF Model, although then again, your EF model seems to model Html :)

public class MenuModelView
{
   // Presentation tier stuff
   public string PageTitle { get; set; }
   public string MetaTagsForSEO { get; set; }
   public bool IsThisARegisteredUserSoSkipTheAdverts { get; set; }

   // Your EF / Domain Model
   public Menu Menu { get; set; }
}

Your razor @Model is the MenuModelView.

StuartLC
  • 104,537
  • 17
  • 209
  • 285
  • Thanks, this works for me. This way the ViewModel class isn't even needed: var model = _db.Menus .Include("MenuItems") .First(m => m.MenuId == id); return View(model); – Floris Jun 04 '14 at 09:53
  • No, keep the viewmodel. You may need to add extra presentation-tier-specific goodies for the view. In this case here, your EF model seems to model a bunch of UI fields, so the distinction between presentation/ domain / data tiers isn't really clear. – StuartLC Jun 04 '14 at 10:26
  • Can you give me a simple exmaple of a presentation-tier-specific goodie in the MenuModelView class? Again, I'm new to .NET MVC :o) – Floris Jun 04 '14 at 11:42
  • I've added a hybrid `ViewModel` example – StuartLC Jun 04 '14 at 12:16