Скажем, у меня есть несколько классов, созданных с помощью Entity Framework, которые используют наследование. Например:

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

И у меня также есть те же имена классов в другом пространстве имен, используемых в качестве основы моделей представления:

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

Затем я могу делать забавные вещи, например использовать общий метод для создания модели представления известного типа:

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

Но что я не могу понять, так это сделать модель правильного типа в случае, если я не знаю тип заранее - я бы хотел иметь возможность сделать что-то вроде:

ViewModels.Person person = GetPersonById(24);

И пусть этот объект person относится к одному из базовых типов - скажем, WebCustomer.

Это позволило бы мне использовать шаблоны EditorFor и DisplayFor в MVC.net для отображения правильных элементов управления для нужных типов Person, даже если я просто взял один из них по идентификатору.

Я могу использовать шаблоны Editor и Display, если я разрешаю Entities всплывать на веб-сайт, но если я хочу иметь логику сопоставления на этом пути, я не могу правильно получить информацию о типе.

Я полагаю, что нужен такой метод, как:

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

Если возвращаемый объект person отображается по имени и приводится к правильному базовому типу. Есть ли способ добиться такого поведения? Или есть лучший способ справиться с передачей унаследованных структур через преобразователь моделей, который я должен делать вместо этого?

1
Kaine 22 Фев 2016 в 19:54

2 ответа

Лучший ответ

Вам просто нужна универсальная фабрика. Возьмем, например, следующий код:

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

Затем, чтобы использовать это, вы просто делаете:

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

Тогда viewModel будет набираться как CustomerViewModel. Однако важно понимать, что здесь вы играете с наименьшим общим знаменателем, поэтому, даже если вы возвращаете CustomerViewModel, единственные свойства, которые вы можете использовать, - это свойства, существующие на Person, а не Customer. Единственный способ получить доступ к свойствам Customer - это либо перегрузить метод, чтобы принять экземпляр Customer в качестве параметра, либо сделать параметр объекта также универсальным. Однако, если оба являются общими, им обоим потребуется реализовать общий интерфейс. Таким образом вы можете ограничить параметр типа интерфейсом и получить доступ к свойствам, которые определяет интерфейс.

Короче говоря, создание действительно универсального сопоставителя типов - это боль. Либо вы принимаете ограничения упомянутых мной подходов, либо просто используете существующую библиотеку для этого типа вещей, например AutoMapper. В конце концов, зачем изобретать велосипед?

2
Chris Pratt 22 Фев 2016 в 18:51

Судя по нашему обсуждению в комментариях, да, вы правы. Если у вас есть точное имя, вы МОЖЕТЕ заменить информацию о пространстве имен и создать экземпляр с помощью Activator, при условии, что ваше пространство имен всегда согласовано.

MyEntities entity
ViewModels.Person person = Activator.CreateInstance(
    Type.GetType("ViewModels" + "." + entity.GetType().Name));

Затем вы можете создавать динамические сопоставления или использовать библиотеку, такую как EmitMapper, для сопоставления между вашими объектами.

Имейте в виду, что такие подходы всегда приводят к снижению производительности. Тем не менее, если это достаточно маленький запас, который вам подходит, это даст вам динамический подход, который вы ищете.

0
David L 22 Фев 2016 в 17:37