How to custom cast a list to a different type?

.net-core c# entity-framework-core

Question

Working with Entity Framework and Razor pages, I've currently got a system going where I wrap my models inside of view models and display them to the screen, which I think is what I'm supposed to be doing in the MVVM architecture.

However, I'm having some trouble casting a list of type 'Foo' to type 'FooView', but casting a variable of type 'Foo' to type 'FooView' is fine. Here is the code:

Student Class

public class Student
{
    public Student()
    {
        Id = null;
        FirstName = "";
        Surname = "";
        Age = 0;
        DateOfBirth = new DateTime();
        Results = new HashSet<Result>();
    }

    public Student(string firstName, string surname, DateTime dateOfBirth)
    {
        Id = null;
        FirstName = firstName;
        Surname = surname;
        DateOfBirth = dateOfBirth;
        Results = new HashSet<Result>();
    }

    public int? Id { get; set; }

    public string FirstName { get; set; }

    public string Surname { get; set; }

    public int Age { get; private set; }

    // Implement backing field
    private DateTime _dateOfBirth;
    public DateTime DateOfBirth
    {
        get => _dateOfBirth;

        set
        {
            // Set Age when date of birth is provided
            _dateOfBirth = value;
            Age = (DateTime.Today.Year - this._dateOfBirth.Year);
            if (_dateOfBirth.Date > DateTime.Today.AddYears(-this.Age)) this.Age--;
        }
    }

    public HashSet<Result> Results { get; set; }

    public static implicit operator StudentView(Student student)
    {
        return new StudentView(student);
    }

}

Successfully casting student item to student view item on razor page WORKS

@foreach (var item in Model.Result)
{
    <tr>
        <td>
            @Html.DisplayFor(modelItem => ((StudentView)item.Student).FullName)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.Score)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.Grade)
        </td>
        <td>
            <a asp-page="./Edit" asp-route-id="@item.Id">Edit</a> |
            <a asp-page="./Details" asp-route-id="@item.Id">Details</a> |
            <a asp-page="./Delete" asp-route-id="@item.Id">Delete</a>
        </td>
    </tr>
}

Trying to cast list to type 'Student View' FAILS

public async Task OnGetAsync()
{
    Student = await _context.Student.Cast<StudentView>().ToListAsync();
}

InvalidCastException: Unable to cast object of type 'StudentManagerDemoCore.Models.Student' to type 'StudentManagerDemoCore.ViewModels.StudentView'.

StudentManagerDemoCore.Pages.Teacher.ManageStudent.IndexModel.OnGetAsync() in Index.cshtml.cs

Student = await _context.Student.Cast<StudentView>().ToListAsync();

I have a feeling there is just something little I'm missing here, but I'm not sure what it is. The error implies there is an issue with my casting method, but that doesn't explain why it works sometimes.

Anyone know where I'm going wrong?

1
2
9/17/2019 8:43:27 AM

Accepted Answer

I managed to solve the problem by also implementing a conversion in the 'StudentView' class from 'StudentView' to 'Student' and then using Magnetron's solution.

So I implemented a conversion in the 'View' class. Why this is needed I have no idea, seems magical to me considering this conversion shouldn't be necessary for the conversion of 'Student' to 'StudentView'. Perhaps there is something written somewhere that a two way cast is required for some reason.

public class StudentView : ViewBase<Student>
{
    // Removed for brevity

    public static implicit operator Student(StudentView student)
    {
        return new Student(student);
    }

}

Then updated the casting to be performed like so as highlighted by Magnetron:

    public async Task OnGetAsync()
    {
        Student = await _context.Student.Select(x => (StudentView)x).ToListAsync();
    }

I'd also like to highlight billybob's answer as he provided another correct way to solve the solution, I just chose a different way.

0
9/17/2019 9:04:49 AM

Popular Answer

You have several options to solve your issue.

The first solution would be to manually do something like this:

await _context.Student.Select(student => new StudentView()
{
    Id = student.Id,
    //....
}

Another solution would be to have a manual mapper that will perform the mapping. A static function that takes a Student and converts it to a StudentView and returns it. You can reuse it elsewhere.

The best solution that I would suggest is to use AutoMapper.



Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow