2

My goal here is to use EF7 with MVC6 [BETA2] to list a number of bookshelves and the number of books on each shelf.

The database schema is created correctly with the correct table relationships. I can successfully add shelves and books to the database including the foreign key relationships (see code below).

When I test the index page that should show the book count on each shelf, I receive no book count data and no errors. In the Shelf entity the property Books remains unpopulated with Book entities thus the count is null (see code below).

In EF7 is there somewhere where I need to write code to populate Shelf.Books or should this happen automatically in EF7?

BookShelf.cs

namespace MyApp.Models
{
    public class Shelf
    {
        public int ShelfId { get; set; }
        public string Name { get; set; }
        public virtual List<Books> Books { get; set; }
    }

    public class Book
    {
        public int BookId { get; set; }
        public string Name { get; set; }
        public int ShelfId { get; set; }
        public Shelf Shelf{ get; set; }
    }
}

ApplicationDbContext.cs

namespace MyApp
{
    public class ApplicationDBContext
    {
        public DbSet<Shelf> Shelf { get; set; }
        public DbSet<Book> Book { get; set; }
    }

    protected override void OnModelCreating(ModelBuilder builder)
    {
        builder.Entity<Shelf>().Key(s => s.ShelfId);
        builder.Entity<Book>().Key(b => b.BookId);

        builder.Entity<Shelf>()                
            .OneToMany(s => s.Book)
            .ForeignKey(k => k.ShelfId);

        base.OnModelCreating(builder);
    }
}

ShelfController.cs

namespace MyApp
{
    private ApplicationDBContext db;

    public BuildingsController(ApplicationDBContext context)
    {
        db = context;
    }

    // GET: Shelves
    public async Task<IActionResult> Index()
    {
        return View(await db.Shelves.ToListAsync());
    }
}

Index.cshtml

...
@foreach (var item in Model) {
    <tr>
        <td>
            @Html.DisplayFor(modelItem => item.Name)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.Books.Count)
        </td>
    </tr>
}
....
ArunPratap
  • 4,816
  • 7
  • 25
  • 43
Will
  • 426
  • 3
  • 13
  • Probably because `db.Shelves.ToListAysnc()` creates a list of Shelves that is used outside the scope of the controller so `Books` is never populated. Secondly, unless things have changed since 6.1, your `Books` property should really be `ICollection`. Lastly, you should be creating a View model that actually represents the data the view needs, and not relying on methods in the view to do logic (like counting, which is most likely why this doesn't work). – Erik Philips Feb 08 '15 at 21:08
  • `ICollection` has been changed in EF7 to `List`. From your comments I am unsure if you are saying it doesn't populate because I am not using a view model or because `db.Shelves.ToListAsync()` is out of scope. How would you suggest it be changed? – Will Feb 08 '15 at 21:19

2 Answers2

0

Take a look at ICollection Vs List in Entity Framework. I have a feeling the minor examples of EF7 using List<> are just incorrect (hard to imagine that with EF7 the best practice is changed from ICollection<> to List<>, it's generally very poor practice to expose a concrete collection type as a property.)

Per your comment I would change:

Create View Models

public class IndexViewModel
{
    public IEnumerable<ShelveModel> Shelves { get; set; }
}

public class ShelveModel
{
    public string Name { get; set; }
    public int BookCount { get ; set; }
}

Update the logic

// GET: Shelves
public async Task<IActionResult> Index()
{
    var model = new IndexViewModel();
    model.Shelves = db.Shelves
        .Select(s => new 
        {
            Name = s.Name,
            BookCount = s.Books.Count()
        })
        .ToListAsync()
        .Select(s => new ShelveModel()
        {
            Name = s.Name,
            BookCount = s.Books.Count()
        })
        .ToList();

    return View(model);
}
Community
  • 1
  • 1
Erik Philips
  • 53,428
  • 11
  • 128
  • 150
  • Index() is giving me a funky error `An unhandled exception occurred while processing the request. InvalidOperationException: When called from 'VisitMethodCallExpression', expressions of type 'Expression' can only be replaced with other non-null expressions of type 'Expression'. Remotion.Linq.Parsing.ExpressionTreeVisitor.VisitAndConvert[T](T expression, String methodName) ` Any ideas? – Will Feb 08 '15 at 22:57
  • The problem seems to be related to the `await` in `shelfIndexViewModel.ShelfModels = await db.Select()`..... so it is probably off topic. – Will Feb 08 '15 at 23:06
  • The code is unhappy about a missing `await` inside of the `async` function. – Will Feb 09 '15 at 14:39
  • Ok, thats a really minor bug, you can just add the await. – Erik Philips Feb 10 '15 at 06:42
0

What I have discovered is that EF does not populate the parent object with related children objects out of the box. Example, myShelf.Books will be empty until populated in the controller action function.

Will
  • 426
  • 3
  • 13