2

In my ASP.NET MVC Core app, if I pass a LINQ Query result of either Query 1 and 2 below, I get following error. On the other hand if I pass result of LINQ Query 3, I get the view correctly displayed without any error. But query 3 is sending all the columns of the Blogs table and I want to send only the columns that I want displayed in the view. Question: How can I avoid the below error while sending only the columns to the view that I want to be displayed in the View?

Note: I want to avoid using a ViewModel since I'm using only one table in the LINQ Queries. Also, obviously the query is sent from an Action method that I've not included for here for brevity:

Error:

InvalidOperationException: The model item passed into the ViewDataDictionary is of type 'System.Collections.Generic.List'1[<>f__AnonymousType3`3[System.Int32,System.String,System.Boolean]]', but this ViewDataDictionary instance requires a model item of type 'System.Collections.Generic.List'1[myProj.Models.Blog]'

LINQ Query 1:

var qry = from b in _context.Blogs
                        select new {b.BlogId, b.BlogName, b.IsNotified};
            return View(await qry.ToListAsync());

LINQ Query 2:

var qry = from b in _context.Blogs
         select new { BlogId= b.BlogId, BlogName= b.BlogName, IsNotified = b.IsNotified};

          return View(await qry.ToListAsync());

LINQ Query 3:

var qry = from b in _context.Blogs
          select b;
          return View(await qry.ToListAsync());

View:

@model List<myProj.Models.Blogs>
<form asp-controller="DbRelated" asp-action="BlogsReview" method="post">
    <table class="table">
        <thead>
            <tr>
                <th></th>
                <th>
                    Blog Name
                </th>
                <th>
                    Is Notified
                </th>
            </tr>
        </thead>
        <tbody>
            @for (int i = 0; i < Model.Count(); i++)
            {
            <tr>
                <td>@Html.HiddenFor(r => r[i].BlogId)</td>
                <td>
                    @Html.EditorFor(r => r[i].BlogName)
                </td>
                <td>
                    @Html.CheckBoxFor(r => r[i].IsNotified)
                </td>
            </tr>
            }
        </tbody>
    </table>
    <button type="submit" class="btn btn-default">Save</button>
</form>
nam
  • 21,967
  • 37
  • 158
  • 332
  • 3
    The error is self explanatory. Your view is `@model List` so you controller needs to pass `List` to the view, not a collection of anonymous objects. If you do not want to do the right thing and use a view model, then use option 3 (and writing a view model with your 3 properties would have taken 1/10 of the time that it took you to write this question) –  Oct 03 '16 at 22:23
  • 1
    your view expects List model so q1 and q2 dont match – DanielVorph Oct 03 '16 at 22:23
  • 1
    @StephenMuecke Agreed. But needed to know the cause of the error so I could learn. – nam Oct 03 '16 at 22:40

2 Answers2

2

The error is because the type that you are returning to the view is not the same type the view is expecting.

I would recommend you to create a ViewModels folder and then a ViewModel class inside the folder, which is basically a class with only the properties you are gonna use in the view and then change the model type on the view.

The ViewModel could look like this

public class BlogViewModel 
{
   public int Id {get; set;}
   public string Name {get; set;}
   public bool IsNotified {get; set;}
}

The linq query

var viewModelList = new List<BlogViewModel>();
_context.Blogs.ForEach(b => 
{
  viewModelList.Add(new BlogViewModel
   {
     Id = b.BlogId,
     Name = b.BlogName,
     IsNotified = b.IsNotified
   });
});
return View(viewModelList);

The View

@model IEnumerable<MyProject.ViewModels.BlogViewModel>
<form asp-controller="DbRelated" asp-action="BlogsReview" method="post">
<table class="table">
    <thead>
        <tr>
            <th></th>
            <th>
                Blog Name
            </th>
            <th>
                Is Notified
            </th>
        </tr>
    </thead>
    <tbody>
        @foreach (var blog in Model)
        {
        <tr>
            <td>@Html.HiddenFor(r => r.Id)</td>
            <td>
                @Html.EditorFor(r => r.Name)
            </td>
            <td>
                @Html.CheckBoxFor(r => r.IsNotified)
            </td>
        </tr>
        }
    </tbody>
</table>
<button type="submit" class="btn btn-default">Save</button>

Community
  • 1
  • 1
William Bello
  • 292
  • 1
  • 3
  • 13
  • 1
    Thank you for providing an another way of using LINQ Query while populating a VM - and also organizing the View Models in a project. – nam Oct 04 '16 at 15:16
0

The reason you an get the error for queries 1 and 2 is because the linq returns and IEnumerable of an anonymous object:

select new {b.BlogId, b.BlogName, b.IsNotified}; 

This is not the same type as the Blog object that is expected by the model, but a new type (that is created at compile time) and has 3 fields.

In your third query your select returns b which is of type Blog meaning the result will be List<Blog> -> as the model expects.


In order to send only the columns you want do: Create a new class type that has only these fields and do:

public class BlogDTO 
{
   public int Id {get; set;}
   public string Name {get; set;}
   public bool IsNotified {get; set;}
}

var qry = from b in _context.Blogs
          select new BlogDTO { Id = b.BlogId, Name = b.BlogName, IsNotified = b.IsNotified}; 

return View(await qry.ToListAsync());

and for the view:

@model List<myProj.Models.BlogDTO>
Gilad Green
  • 36,708
  • 7
  • 61
  • 95
  • The 2nd one will throw an exception unless you first materialize the query. –  Oct 03 '16 at 22:37
  • @StephenMuecke - Can you please elaborate? – Gilad Green Oct 03 '16 at 22:38
  • @GiladGreen From the comments from other users and from your post I understood the reason of the error and that probably using a View Model will be the way to go. But I'm not clear on your point # 2. – nam Oct 03 '16 at 22:46
  • @nam - What I mean there is that if you don't want to create a different class for this slimmer definition of `Blog` then you can still use it the way I described above. (I would go for creating another class "`BlogMetadata`" or something but option 2 is still possible). – Gilad Green Oct 03 '16 at 22:50
  • @GiladGreen In #1 you are using a ViewModel. But, in #2, as probably `@StephenMuecke` had suspected, I'm getting the error: `Cannot initialize type 'Blog' with a collection initializer because it does not implement 'System.Collections.IEnumerable'` – nam Oct 03 '16 at 23:04
  • @nam - I had typos with the last `=` in each. Can you check it now? – Gilad Green Oct 03 '16 at 23:16
  • @GiladGreen, Refer [these answers](http://stackoverflow.com/questions/5325797/the-entity-cannot-be-constructed-in-a-linq-to-entities-query) for an explanation –  Oct 03 '16 at 23:29
  • @StephenMuecke - Ahh ok :) thanks. I missed the point with it being mapped. Many thanks for the explanation – Gilad Green Oct 04 '16 at 08:25