nam
nam

Reputation: 23749

Entity Framework Eager Loading - pass data to ViewModel

In my ASP.NET MVC Core app, from an action method shown below, I'm passing Blogs data and its related data from Posts table to a view as return View(await _context.Blogs.Include(p => p.Posts).ToListAsync()); Since I'm passing data from two tables, I need to use a ViewModel shown below. Question: How can I use ViewModel to pass the related data from my Controller Action method Test() to view shown below?

In the code below I'm getting the obvious error:

InvalidOperationException: The model item passed into the ViewDataDictionary is of type 'System.Collections.Generic.List'1[ASP_Core_Blogs.Models.Blog]', but this ViewDataDictionary instance requires a model item of type 'System.Collections.Generic.IList'1[ASP_Core_Blogs.Models.BlogPostViewModels.BlogsWithRelatedPostsViewModel]'.

Model:

public class BloggingContext : DbContext
{
    public BloggingContext(DbContextOptions<BloggingContext> options)
        : base(options)
    { }

    public DbSet<Blog> Blogs { get; set; }
    public DbSet<Post> Posts { get; set; }
}

public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }

    public List<Post> Posts { get; set; }
}

public class Post
{
    public int PostId { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }
    public int PostYear { get; set; }
    public int BlogId { get; set; }
    public Blog Blog { get; set; }
}

Controller:

[HttpGet]
public async Task<IActionResult> Test(string returnUrl = null)
{
    ViewData["ReturnUrl"] = returnUrl;
    return View(await _context.Blogs.Include(p => p.Posts).ToListAsync());
}

ViewModel:

public class BlogsWithRelatedPostsViewModel
{
    public int BlogID { get; set; }
    public int PostID { get; set; }
    public string Url { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }
    public int PostYear { get; set; }
}

View:

@model IList<ASP_Core_Blogs.Models.BlogPostViewModels.BlogsWithRelatedPostsViewModel>

<div class="row">
    <div class="col-md-12">
        <form asp-controller="DbRelated" asp-action="EnterGrantNumbers" asp-route-returnurl="@ViewData["ReturnUrl"]" method="post">
            <table class="table">
                <thead>
                    <tr>
                        <th></th>
                        <th></th>
                        <th>Url</th>
                        <th>Title</th>
                        <th>Content</th>
                    </tr>
                </thead>
                <tbody>
                    @for (int t = 0; t < Model.Count; t++)
                    {
                        <tr>
                            <td><input type="hidden" asp-for="@Model[t].BlogID" /></td>
                            <td><input type="hidden" asp-for="@Model[t].PostID" /></td>
                            <td>
                                <input type="text" asp-for="@Model[t].Url" style="border:0;" readonly /> <!--Not using /*Html.DisplayFor(modelItem => Model[t].Url)*/ since it does not submit stateName on Post. Not using <label asp-for=.....> since Bootstrap bold the text of <label> tag-->
                            </td>
                            <td>
                                <input asp-for="@Model[t].Title" />
                            </td>
                            <td>
                                <input asp-for="@Model[t].Content" />
                            </td>
                        </tr>
                    }
                </tbody>
            </table>
            <button type="submit" class="btn btn-default">Save</button>
        </form>
    </div>
</div>

Upvotes: 2

Views: 1481

Answers (3)

ocuenca
ocuenca

Reputation: 39326

You need to project your query using your BlogsWithRelatedPostsViewModel class:

     return View( _context.Blogs
                          .Include(p => p.Posts)
                          .SelectMany(e=> e.Posts.Select(p=> new BlogsWithRelatedPostsViewModel
                                                             {
                                                              BlogId= e.BlogId,
                                                              PostId=p.PostId,
                                                              Url=e.Url,
                                                              ...
                                                             })
                          .ToList()); 

SelectMany extension method allows you flatten each projection from e.Posts into one sequence, so at the end you will get a List<BlogsWithRelatedPostsViewModel>

Upvotes: 2

Rajput
Rajput

Reputation: 2607

For passing data from Action to view as ViewModel. Create a new instance of your View Model first and assign value to each propery by calling your context query(whatever your Linq query is) and return the list of view as your View model variable.

var blogWithRelatedPost = new BolblogWithRelatedPost();
// your logic here for assigning value to property or LINQ query
return View(blogWithRelatedPost);

Upvotes: 0

Peter Dempsey
Peter Dempsey

Reputation: 495

On top of Octavioccl's, answer there is a nice little extension method I have been using (I don't know of the author to this but if anyone else knows, I will happily update my answer to give credit). This way, you don't have to write out each property.

public static T Cast<T>(this object myobj)
{
    var target = typeof(T);
    var x = Activator.CreateInstance(target, false);
    var d = from source in target.GetMembers().ToList()
            where source.MemberType == MemberTypes.Property
            select source;
    var memberInfos = d as MemberInfo[] ?? d.ToArray();
    var members = memberInfos.Where(memberInfo => memberInfos.Select(c => c.Name)
       .ToList().Contains(memberInfo.Name)).ToList();
    foreach (var memberInfo in members)
    {
        var propertyInfo = typeof(T).GetProperty(memberInfo.Name);
        if (myobj.GetType().GetProperty(memberInfo.Name) == null) continue;
        var value = myobj.GetType().GetProperty(memberInfo.Name).GetValue(myobj, null);
        propertyInfo.SetValue(x, value, null);
    }
    return (T)x;
}

Usage:

var ViewModelList = ModelList.Select(model => model.Cast<ViewModel>()).ToList();

There is also a well supported framework built for this specific problem. Called AutoMapper (http://automapper.org/).

Upvotes: 1

Related Questions