Копирование NHibernate POCO в DTO без ленивой или активной загрузки
Мне нужно создать DTO из объектов NHibernate POCO. Проблема заключается в том, что объекты POCO содержат динамические прокси, которые не следует копировать в DTO. Я хочу загрузить все коллекции и ссылки, которые мне нужно перенести заранее, я не хочу, чтобы NHibernate начинал загрузку ссылочных коллекций, которые я не загружал заранее.
Несколько похожих вопросов по SO получили ответы, которые либо:
- Предложить Session.GetSessionImplementation().PersistenceContext.Unproxy();
- Предложить отключение Lazy Loading.
В моем случае первое предложение не имеет значения, так как, насколько я понимаю, оно вызывает стремительную загрузку, чтобы заменить прокси. На самом деле, он даже не работает - он не удаляет прокси в моих объектах. (Любое объяснение почему?)
Второе предложение - отключение отложенной загрузки, похоже, приводит к активной загрузке всех ссылок и коллекций, в основном к загрузке всей БД. Я ожидал, что если отложенная загрузка отключена и я не запросил коллекцию, она не будет загружена. (Я прав, что NHibernate не предлагает такой вариант?)
Я использую NHibernate 3.3.1 с быстрой конфигурацией.
Чтобы повторить мой основной вопрос, мне нужно создать DTO, чистые от прокси, скопированные из POCO, которые содержат прокси, и я не хочу загружать данные за этими прокси.
Любое полезное предложение, которое включает пример кода и автоматизирует процесс с помощью ValueInjecter / AutoMapper, будет чрезвычайно полезным.
Редактировать № 1:
Следуя предложению Роджера Алсинга использовать проекции, я понял, что на самом деле я ищу отображение, основанное на ValueInjecter. Вот почему. Первоначально мои DTO будут определены так же, как POCO модели. Это связано с большой кодовой базой, которая зависит от существующих POCO, передаваемых в клиентском проекте.
Используя проекции, мне нужно будет указать, какое подмножество полей должно быть скопировано, и это подмножество может отличаться в каждом контексте (так как в идеале DTO будет отличаться). Это будет означать много нового кода, представленного на стороне сервера, когда должен быть второй вариант.
Используя ValueInjecter, я смогу заполнить DTO по соглашению за один вызов, без написания конкретных прогнозов или необходимости поддерживать их в будущем. То есть, если я могу иметь ValueInjecter игнорировать объекты Proxy.
Учитывая, что использование проекций является хорошим, но не идеальным решением в моей ситуации, есть ли способ настроить что-то вроде ValueInjecter для копирования POCO без копирования прокси-серверов или безошибочной / отложенной загрузки при копировании?
4 ответа
Для решения ValueInjecter я рекомендую использовать SmartConventionInjection (необходимо скопировать код со связанной страницы в ваше решение)
и после укажите соглашение, которое не будет касаться свойств прокси
вот начало:
public class MapPoco: SmartConventionInjection
{
protected override bool Match(SmartConventionInfo c)
{
return c.SourceProp.Name == c.TargetProp.Name;
}
}
Я решаю это, выбирая DTO в качестве проекций, используя Linq или любой другой язык запросов, который может иметь O/R Mapper.
например
return from c in customers
select new CustomerDTO
{
Name = c.Name ,
Orders = c.Orders.Select (o => new OrderDTO {...} )
};
Таким образом, вам не нужно прибегать к магии отражения или другим причудливым вещам. И запрос выбирает именно то, что вам нужно за один раз, таким образом, это, как правило, гораздо более эффективно, чем выборка сущностей и их преобразование в DTO в mem. (в некоторых случаях это может быть менее эффективно, если результирующий запрос SQL содержит дополнительные объединения по любой причине..)
Я использую следующий ValueResolver с AutoMapper:
/// <summary>
/// ValueResolver that will set NHibernate proxy objects to null, instead of triggering a lazy load of the object
/// </summary>
public class IgnoreNHibernateProxyValueResolver : IValueResolver
{
public ResolutionResult Resolve(ResolutionResult source)
{
var prop = source.Type.GetProperty(source.Context.MemberName).GetValue(source.Value, null);
var proxy = prop as INHibernateProxy;
if (proxy != null && proxy.HibernateLazyInitializer.IsUninitialized)
{
return source.Ignore();
}
return source.New(prop);
}
}
Посмотрите на проекции в Введение в QueryOver в NH 3.0
CatSummary summaryDto = null;
IList<CatSummary> catReport =
session.QueryOver<Cat>()
.SelectList(list => list
.SelectGroup(c => c.Name).WithAlias(() => summaryDto.Name)
.SelectAvg(c => c.Age).WithAlias(() => summaryDto.AverageAge))
.TransformUsing(Transformers.AliasToBean<CatSummary>())
.List<CatSummary>();