Kaine Kaine - 17 days ago 5
C# Question

How to map inherited data objects to corresponding view models without losing their type?

Say I have some classes created using Entity Framework which use inheritance. For example:

namespace MyEntities
{
Person
Employee : Person
Customer : Person
WebCustomer : Customer
}


And I also have the same class names in another namespace used as the basis of view models:

namespace ViewModels
{
Person
Employee : Person
Customer : Person
WebCustomer : Customer
}


I can then do fun things like using a generic method to create a viewmodel of a known type:

public T Make<T>(Entities.Person entity) where T : ViewModels.Person, new()
{
T model = (T)new T().InjectFrom(entity);
return model;
}


But what I can't figure out how to do is make a model of the correct type in the case that I don't know the type in advance - I'd like to be able to do something like:

ViewModels.Person person = GetPersonById(24);


And have that person object be of one of the underlying types - say a WebCustomer.

This would allow me to use the EditorFor and DisplayFor templates in MVC.net to display the right controls for the right types of Person, even when just grabbing one by Id.

I can use the Editor and Display templates if I allow the Entities to bubble up to the website, but if I want to have mapping logic along the way I can't seem to get the type information correct.

I suppose what's needed is a method like:

ViewModels.Person person Get(MyEntities entity)
{
// ??
}


Where the person object returned is mapped by name and cast to the correct underlying type. Is there a way to achieve this behavior? Or is there better way to handle passing inherited structures through a model mapper that I should be doing instead?

Answer

You just need a generic factory. Take the following code for example:

public static PersonViewModelFactory
{
    public static TViewModel CreateFrom<TViewModel>(TEntity entity)
        where TViewModel : PersonViewModel, new()
    {
        return new TViewModel { // map properties from Person }
    }
}

Then, to use this you just do:

var viewModel = PersonViewModelFactory.CreateFrom<CustomerViewModel>(person);

Then, viewModel will be typed as CustomerViewModel. However, it's important to realize that you're playing to a least common denominator here, so even though you're returning a CustomerViewModel, the only properties you can utilize are properties that exist on Person, not Customer. The only way you could access Customer properties would be either overload the method to take an instance of Customer as a param or make the entity param generic as well. However, if both are generic, then they'll both need to implement a common interface. That way you can constrain the type param to the interface and access the properties that interface defines.

Long and short, creating a truly generic type mapper is a pain. Either you accept the limitations of the approaches I mention, or just use an existing library for this type of thing, like AutoMapper. After all, why reinvent the wheel?