AutoMapper: Карта объекта графа с несколькими точками входа?
Скажем, у меня есть следующие 3 типа: человек, компания и актив.
- Человек имеет коллекцию активов.
- Компания имеет коллекцию активов.
- Активы не имеет ссылок.
Используя эти определения, я создаю следующие экземпляры объектов:
- Человек
P
имеет ссылки на активыAa
а такжеAb
- Компания
C
имеет ссылки на активыAb
а такжеAc
,
Обратите внимание, что тот же экземпляр объекта Ab
упоминается как P
а также C
,
Теперь у меня есть 2 определения каждого из этих типов, и я определил сопоставления между ними, например:
Mapper.CreateMap<Db.Person, Schema.Person>();
Mapper.CreateMap<Db.Company, Schema.Company>();
Mapper.CreateMap<Db.Assets, Schema.Assets>();
Если я карту P
а также C
позвонив Mapper.Map
дважды отдельно (как в коде ниже), я получаю два отдельных экземпляра объекта Ab
один как часть P
и один как часть C
,
var mappedP = Mapper.Map<Schema.Person>(P);
var mappedC = Mapper.Map<Schema.Company>(C);
Как мне получить AutoMapper для сопоставления обоих P
а также C
"в то же время" или с использованием того же "контекста" сопоставляемых объектов, чтобы он не создавал сопоставленную версию Ab
дважды, а точнее повторно использует один и тот же экземпляр?
Редактировать Чтобы уточнить, я использую Entity Framework для получения моего графа объектов из базы данных. Из-за формы данных (т.е. нескольких "корневых" объектов) мне нужно несколько запросов к базе данных, но это не проблема, если эти запросы выполняются к одному и тому же DbContext, Entity Framework материализует каждый объект только один раз.
Следовательно, это позволяет извлекать данные в форме, описанной выше, с Person, Company и Asset - это работает довольно хорошо.
Мне все еще нужно массировать этот граф объектов в формате, подходящем для возврата из моего сервисного слоя, но прежде чем я это сделаю, я хочу, чтобы AutoMapper отобразил весь граф объектов из сущностей "Db" в сущности "Schema", чтобы я мог массировать данные с использованием объектов типа "Схема"- отсюда и мой вопрос.
Я знаю, что одним из обходных путей является введение искусственного контейнерного объекта (в приведенном выше примере со свойствами Person и Company), который служит новым единым корневым объектом для отображения.
Однако для этого требуется дополнительное определение класса для каждой комбинации нескольких "корневых" объектов, которые я хочу отобразить, что не очень элегантно - мне было интересно, есть ли лучший способ отображения такого рода графов объектов?
Редактировать 2 Самое смешное, что в рамках вызова Mapper.Map()
AutoMapper уже имеет поведение только сопоставления объекта один раз и повторного использования того же сопоставленного экземпляра всякий раз, когда исходный объект встречается снова.
Я подозреваю, что он сохраняет контекст сопоставления (или "кеш") уже отображенных экземпляров (либо явно, либо где-то в стеке вызовов), и что он располагается перед вызовом Mapper.Map()
возвращается.
Если бы был способ сделать этот контекст отображения явным (например, предоставив его в качестве аргумента Mapper.Map()
), это решило бы мою проблему, не рискуя утечек памяти (неиспользуемые кэшированные экземпляры будут удалены, когда контекст отображения теряет область видимости).
2 ответа
Проблема, с которой вы столкнулись, заключается в том, что, хотя у вас есть один и тот же исходный актив, вы создаете новый целевой объект при отображении. Как именно AutoMapper должен знать, что вы уже сопоставили этот актив и что он должен а) каким-то образом найти этот актив где-нибудь и б) повторно использовать его?
Проблема не столько в идентификации того, что у вас есть один и тот же актив (т. Е. Использование идентификатора или чего-то еще), а в хранении кэша этих активов. Когда вы кэшируете объекты, то фреймворк сохраняет ссылки на них, и тогда сборщик мусора не будет их собирать, и у вас будут утечки памяти.
Итак, чтобы сделать это, вам нужно создать Слабые ссылки в вашем кэше, чтобы вы могли искать объекты, чтобы увидеть, существуют ли они, и повторно использовать их... если они не создают их. Это не то, что делает AutoMapper сам по себе, поскольку это обычно не требуется функциональность.
Честно говоря, это подверженный ошибкам код, который, вероятно, потребует много работы, чтобы получить права. Я бы предложил найти альтернативные решения, если вы можете. Что плохого в том, чтобы жить с несколькими копиями вашего исходного объекта?
Звучит немного психически!:-) Почему ты хочешь добиться этого?
Если бы вы должны были создать свой собственный AssestsValueResolver
public class AssestsValueResolver: IValueResolver
{
private Assests _assets;
...
}
Затем создайте карту каждый раз, прежде чем переходить к Assests (не идеально для производительности!)
var resolver = new AssestsValueResolver();
Mapper.CreateMap<Db.Assets, Schema.Assets>()
.ForAllMembers(
m => m.ResolveUsing<AssestsValueResolver>().ConstructedBy(() => resolver ));
Вы можете кэшировать ресурс, чтобы один и тот же экземпляр повторно использовался, когда вы отображаете как Person, так и Customer, и должен использоваться один и тот же ресурс (вы бы связали их по ID?).
Хотя это потенциально решение (я не пробовал), могу ли я спросить еще раз, почему вы хотите достичь этого?
Разве вы не можете логически связать один и тот же актив по идентификатору?