Мне наконец удалось сделать это в общем виде. Это потребовало некоторой настройки моих объектов, но вот что я сделал:
С тех пор большинство моих сущностей наследуется от Entity<TId>
(обычно Entity<int>
, но идентификатор может потенциально быть другого типа данных, например, GUID), который, в свою очередь, реализует интерфейс IEntity<TId>
(с одним свойство public TId ID {get;}
). Те немногие объекты, которые не наследуют Entity<TId>
, по крайней мере, реализуют IEntity<TId>
.
Я создал новый класс, который переопределяет Equals
и GetHashcode
:
public class ViewModel<TEntity, TId> : IEntity<TId>
where TEntity : IEntity<TId>
{
public TId ID { get; set; }
public override bool Equals(object obj)
{
var viewModel = obj as ViewModel<TEntity, TId>;
return viewModel != null && Equals(viewModel);
}
public bool Equals(ViewModel<TEntity, TId> other)
{
return ID.Equals(other.ID);
}
public override int GetHashCode()
{
// Not only returning ID.GetHashCode() in case I want to add more
// properties later...
var hash = 7;
hash = (hash * 17) + ID.GetHashCode();
return hash;
}
}
Теперь все мои view-модели (и editmodels) наследуются от этого класса:
public class EntityViewModel : ViewModel<EntityType, int>
{
// data properties
}
Затем я могу расширить свои сущности и модели представления следующими методами расширения:
public static TViewModel To<TViewModel>(this IEntity entity) where TViewModel : class
{
return Mapper.Map(entity, entity.GetType(), typeof(TViewModel)) as TViewModel;
}
public static TEntity ToEntity<TEntity, TId>(this ViewModel<TEntity, TId> viewmodel) where TEntity : class, IEntity<TId>
{
return Mapper.Map(viewmodel, viewmodel.GetType(), typeof(TEntity)) as TEntity;
}
Ключевой концепцией этой работы было отказаться от общих перегрузок .Map
, поскольку я не знал точных типов, которые я хотел отображать во время компиляции и из нее.
Теперь я могу перемещаться между типами, используя следующий синтаксис:
var entity = new EntityType();
var viewmodel = entity.To<EntityViewModel>();
var backagain = viewmodel.ToEntity();
, что меня вполне устраивает, поскольку моя первоначальная цель абстрагирования AutoMapper от реализации в контроллерах была хорошо достигнута.
Вы заметите, что существует не универсальная версия IEntity - это просто пустой интерфейс, от которого наследуется IEntity<T>
. Это может показаться скучным, но есть простая причина не использовать универсальную версию: если вы это сделаете, вам также придется указать ее в качестве аргумента типа для метода расширения. В итоге вы получите entity.To<EntityViewModel,int>()
вместо приведенного выше синтаксиса, поскольку вывод аргументов типа является делом "все или ничего".