2

I thought I was starting to understand how to use DDL in MVC but I guess I'm still not really getting their proper setup.

I have a DropDownList that I'm populating from Entity Framework and using a repository. I can debug through the code and see that the ViewModel is getting populated with the values but once I get to the View I get and error "The operation cannot be completed because the DbContext has been disposed". I thought by the time I got to the View the DbContext was no longer needed anyway?

I've tried different combinations of Html.DropDownList and Html.DropDownListFor and each time I'm coming up with the same message. I'm sure this is something simple that I'm just not understanding but I've been looking over different examples and trying a couple different combinations without any luck.

This is my ViewModel

public class AssignCounselorViewModel
{
    public IEnumerable<SelectListItem> listMerits { get; set; }

    public int MeritSelectedId { get; set; }
}

This is my Controller:

public ActionResult AssignCounselor()
{
    var viewModel = new AssignCounselorViewModel();
    viewModel.listMerits = MeritCounselorRepository.GetAllMerits();

    return View(viewModel);
}

This is my Repository:

public static IEnumerable<SelectListItem> GetAllMerits()
{
    using (DataContext ctx = new DataContext())
    {
        var results = (from m in ctx.MeritBadges
                       select new SelectListItem
                       {
                           Value = m.ID.ToString(),
                           Text = m.MeritBadgeName
                        }
                       ).OrderBy(o => o.Text);

        return new SelectList(results, "Value", "Text");
    }
}

This is my View:

<div class="row">
    <div class="col-md-12">
        @Html.DropDownListFor(m => m.listMerits,
                new SelectList(Model.listMerits, "Text", "Value"))
    </div>
</div>

Here are some other combinations I've tried.

@Html.DropDownList("ddlMerit", new SelectList(Model.listMerits, "Value", "Text"), new { style = "width:100px;height:31px" })


@Html.DropDownListFor(m => Model.listMerits, Model.listMerits, "--Select--");
Caverman
  • 3,371
  • 9
  • 59
  • 115

2 Answers2

3

Try this

public static IEnumerable<SelectListItem> GetAllMerits()
{
    using (DataContext ctx = new DataContext())
    {
        var results = (from m in ctx.MeritBadges
                       select new SelectListItem
                       {
                           Value = m.ID.ToString(),
                           Text = m.MeritBadgeName
                       }
                       ).OrderBy(o => o.Text).ToArray(); // .ToArray() => materialize collection. After this data will be returned from database.

        return new SelectList(results, "Value", "Text"); 
    }
}
tym32167
  • 4,741
  • 2
  • 28
  • 32
0

To expand on tym32167's answer, which should resolve the issue...

using (DataContext ctx = new DataContext())
{
    var results = (from m in ctx.MeritBadges
                   select new SelectListItem
                   {
                       Value = m.ID.ToString(),
                       Text = m.MeritBadgeName
                    }
                   ).OrderBy(o => o.Text);

    return new SelectList(results, "Value", "Text");
}

This will return an IQueryable<SelectListItem>, which is a query that is not yet executed against the database, but contains the instructions to call when the command to execute is sent. This is returned within a scoped DbContext that will be disposed once the call is complete. Once the IQueryable is executed, the results will be turned into an IEnumerable of some sort (.ToList becomes a List<T>, .ToArray() becomes an Array, etc), and the DbContext is disposed.

So when you call the IQueryable<MeritBadge> twice, like this:

<div class="row">
    <div class="col-md-12">
        @Html.DropDownListFor(m => m.listMerits,
                new SelectList(Model.listMerits, "Text", "Value"))
    </div>
</div>

The first instance m => m.listMerits will execute the query and dispose the context; then model.ListMerits is called, which will attempt to execute the IQueryable again to enumerate through the data, but the context is already disposed.

Edit: tym32167 pointed out that I had the type of the IQueryable wrong.

Max
  • 849
  • 9
  • 24
  • 1
    Just clerify, repo method will return ```SelectList```. All other ok. :) – tym32167 Sep 06 '16 at 13:54
  • 1
    Thanks for the explanation. That makes more sense as to why I was getting the context error. – Caverman Sep 06 '16 at 13:59
  • Just to clarify, the way I was originally calling to populate the DDL was running the query twice, once with the use of {m => m.listMerits} and again with the SelectList(Model.listMerits) and it was the second call that was causing the error because the context was already disposed. However, using the .ToArray() or probably the .ToList() causes EF to immediately excute the query and populate the ViewModel. By not calling the .ToArray the code was waiting to populate until it was actually called in the View? – Caverman Sep 06 '16 at 14:05