Search code examples
c#asp.net-mvcentity-frameworkasp.net-mvc-viewmodel

ASP.NET MVC Parent-Child with ViewModel and Entity Framework


This is my code.

Parent:

public class Parent
{
    [Key]
    public int Id { get; set; }

    [Required]
    [MaxLength(50), MinLength(3)]
    [Display(Name = "Parent Name")]
    public string Name { get; set; }

    [Required]
    [MaxLength(50), MinLength(3)]
    [Display(Name = "Parent Surname")]
    public string Surname { get; set; }

    public List<Child> Children { get; set; } = new List<Child>();
}

Child:

public class Child
{
    [Key]
    public int Id { get; set; }

    [Required]
    [MaxLength(50), MinLength(3)]
    [Display(Name = "Child Name")]
    public string Name { get; set; }

    [Required]
    [MaxLength(50), MinLength(3)]
    [Display(Name = "Child Surname")]
    public string Surname { get; set; }

    [Required]
    [Display(Name = "Parent")]
    public int ParentId { get; set; }

    [ForeignKey("ParentId")]
    public virtual Parent Parent { get; set; }
}

ViewModel:

public class ParentChildViewModel
{
    public Parent Parent { get; set; }
    public IEnumerable<Child> Children { get; set; }
}

Controller:

public class DemoController : Controller
{
    private readonly ApplicationDbContext _db;
    public DemoController(ApplicationDbContext db)
    {
        _db = db;
    }
    public async Task<IActionResult> Index(int id)
    {
        var parent = await _db.Parents.AsNoTracking().FirstOrDefaultAsync(i => i.Id == id);

        var child = await _db.Children.AsNoTracking().Where(i => i.ParentId == id).ToListAsync();
        
        var model = new ParentChildViewModel
        {
            Parent = parent,
            Children = child
        };

        return View(model);
    }
}

View:

@model TwoModels.Models.ParentChildViewModel
<h2> Demo Table Parent</h2>
<table style="border:solid 2px;">
    <tr>
        <td>
            Parent Name:
        </td>
        <td>
            @Html.DisplayFor(m => m.Parent.Name) <br />
        </td>
    </tr>
    <tr>
        <td>
            Parent Surname:
        </td>
        <td>
            @Html.DisplayFor(m => m.Parent.Surname) <br />
        </td>
    </tr>
</table>
<h2>Demo Table Child</h2>
<table style="border:solid 2px;">
    @foreach (var item in Model.Children)
    {
        <tr>
            <td>
                Child Name:
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Name) <br />
            </td>
        </tr>
        <tr>
            <td>
                Child Surname:
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Surname) <br />
            </td>
        </tr>
    }
</table>

As is easily understood by the code, I use: _db.Parents.AsNoTracking().FirstOrDefaultAsync(i => i.Id == id) for Parent (I don't want a list, but only First) and: _db.Children.AsNoTracking().Where(i => i.ParentId == id).ToListAsync() (This is a List)

But in this way the request to the database is made twice. So, I would like to modify the code, keeping the use of the ViewModel, but accessing the database only once. I thought of something:

_db.Parents.AsNoTracking().Include(c=>c.Children).Where(i => i.Id == id).ToListAsync();

How can I change my Controller code and View?


Solution

  • your action could be like this:

    public async Task<IActionResult> Index(int id)
        {
            var item = await _db.Parents.AsNoTracking().
    .Include(c=>c.Children)
    .Where(i => i.Id == id).
    FirstOrDefaultAsync();
    
               var model = new ParentChildViewModel
            {
                Parent = item,
                Children = item.Children
            };
             item.Children=null; // only if you want to use view model alone.
            return View(model);
        }
    

    and remove new List() from Children property. It should be like this:

    public List<Child> Children { get; set; }