AutoMapper поддерживает Linq?
Я очень заинтересован в Linq to SQL с функцией отложенной загрузки. И в моем проекте я использовал AutoMapper для сопоставления модели БД с моделью домена (из DB_RoleInfo
в DO_RoleInfo
). В моем коде репозитория, как показано ниже:
public DO_RoleInfo SelectByKey(Guid Key)
{
return SelectAll().Where(x => x.Id == Key).SingleOrDefault();
}
public IQueryable<DO_RoleInfo> SelectAll()
{
Mapper.CreateMap<DB_RoleInfo, DO_RoleInfo>();
return from role in _ctx.DB_RoleInfo
select Mapper.Map<DB_RoleInfo, DO_RoleInfo>(role);
}
SelectAll
метод работает хорошо, но когда я звоню SelectByKey
Я получаю ошибку:
Метод "RealMVC.Data.DO_RoleInfo MapDB_RoleInfo,DO_RoleInfo" не может быть переведен в SQL.
Неужели Automapper не полностью поддерживает Linq?
Вместо Automapper я попробовал код ручного отображения ниже:
public IQueryable<DO_RoleInfo> SelectAll()
{
return from role in _ctx.DB_RoleInfo
select new DO_RoleInfo
{
Id = role.id,
name = role.name,
code = role.code
};
}
Этот метод работает так, как я хочу.
2 ответа
Хотя ответ @Aaronaught был верным на момент написания, как часто мир менялся и AutoMapper с ним. В это время, QueryableExtensions
были добавлены в базу кода, которая добавила поддержку проекций, которые переводятся в выражения и, наконец, SQL.
Основной метод расширения ProjectTo
1 Вот как может выглядеть ваш код:
using AutoMapper.QueryableExtensions;
public IQueryable<DO_RoleInfo> SelectAll()
{
Mapper.CreateMap<DB_RoleInfo, DO_RoleInfo>();
return _ctx.DB_RoleInfo.ProjectTo<DO_RoleInfo>();
}
и это будет вести себя как ручное отображение. (The CreateMap
Заявление здесь для демонстрационных целей. Обычно вы определяете сопоставления один раз при запуске приложения).
Таким образом, запрашиваются только те столбцы, которые требуются для сопоставления, и результатом является IQueryable
у него все еще есть исходный поставщик запросов (linq-to-sql, linq-to-entity, что угодно). Так что это по-прежнему составляется, и это будет переводить в WHERE
предложение в SQL:
SelectAll().Where(x => x.Id == Key).SingleOrDefault();
1 Project().To<T>()
до версии 4.1.0
Измените свою вторую функцию на это:
public IEnumerable<DO_RoleInfo> SelectAll()
{
Mapper.CreateMap<DB_RoleInfo, DO_RoleInfo>();
return from role in _ctx.DB_RoleInfo.ToList()
select Mapper.Map<DB_RoleInfo, DO_RoleInfo>(role);
}
AutoMapper прекрасно работает с Linq to SQL, но не может быть выполнен как часть отложенного запроса. Добавление ToList()
в конце ваш запрос Linq заставляет его немедленно оценить результаты, вместо того, чтобы пытаться перевести сегмент AutoMapper как часть запроса.
осветление
Понятие отложенного выполнения (а не " отложенная загрузка") не имеет никакого смысла после того, как вы изменили результирующий тип на нечто, не являющееся объектом данных. Рассмотрим эти два класса:
public class DB_RoleInfo
{
public int ID { get; set; }
public string Name { get; set; }
}
public class DO_RoleInfo
{
public Role Role { get; set; } // Enumeration type
}
Теперь рассмотрим следующее отображение:
Mapper.CreateMap<DB_RoleInfo, DO_RoleInfo>
.ForMember(dest => dest.Role, opt => opt.MapFrom(src =>
(Role)Enum.Parse(typeof(Role), src.Name)));
Это отображение вполне нормально (если я не сделал опечатку), но допустим, вы пишете SelectAll
метод в вашем исходном посте вместо моего исправленного:
public IQueryable<DO_RoleInfo> SelectAll()
{
Mapper.CreateMap<DB_RoleInfo, DO_RoleInfo>();
return from role in _ctx.DB_RoleInfo
select Mapper.Map<DB_RoleInfo, DO_RoleInfo>(role);
}
На самом деле это работает, но, называя себя "запрашиваемым", это ложь. Что произойдет, если я попытаюсь написать это против:
public IEnumerable<DO_RoleInfo> SelectSome()
{
return from ri in SelectAll()
where (ri.Role == Role.Administrator) ||
(ri.Role == Role.Executive)
select ri;
}
Подумай об этом очень серьезно. Как Linq to SQL может быть в состоянии успешно превратить ваш where
в фактический запрос к базе данных?
Линк ничего не знает о DO_RoleInfo
учебный класс. Он не знает, как сделать сопоставление в обратном направлении - в некоторых случаях это может быть даже невозможно. Конечно, вы можете посмотреть на этот код и сказать "О, это просто, просто найдите" Администратор "или" Администратор "в Name
column ", но вы единственный, кто знает это. Что касается Linq to SQL, то запрос - это полная чушь.
Представьте, что кто-то дал вам эти инструкции:
Отправляйся в супермаркет и принеси ингредиенты для приготовления индейки Мортон Томпсон.
Если вы не сделали это раньше, а большинство людей этого не сделали, ваш ответ на эту инструкцию, скорее всего, будет следующим:
- Что это за фигня?
Вы можете пойти на рынок, и вы можете получить конкретные ингредиенты по названию, но вы не можете оценить состояние, которое я вам дал, пока вы там. Я должен сначала "удалить карту" критериев. Я должен сказать вам, вот ингредиенты, которые нам нужны для этого рецепта - теперь иди и возьми их.
Подводя итог, это не просто несовместимость между Linq to SQL и AutoMapper. Он не уникален ни для одной из этих двух библиотек. Неважно, как вы на самом деле выполняете сопоставление с не-сущностным типом - вы можете так же легко выполнить сопоставление вручную, и вы все равно получите ту же ошибку, потому что вы сейчас даете Linq для SQL набор инструкций которые больше не являются понятными, имея дело с таинственными классами, которые не имеют встроенного сопоставления с каким-либо конкретным типом сущности.
Эта проблема является фундаментальной для концепции O/R Mapping и отложенного выполнения запроса. Проекция - это односторонняя операция. После того, как вы спроецировали проект, вы больше не можете возвращаться к механизму запросов и говорить " о, кстати, вот еще несколько условий для вас". Слишком поздно. Лучшее, что вы можете сделать, это взять то, что вам уже дали, и оценить дополнительные условия самостоятельно.
Не в последнюю очередь, я оставлю вас с обходным путем. Если единственное, что вы хотите сделать из своего отображения, это отфильтровать строки, вы можете написать это:
public IEnumerable<DO_RoleInfo> SelectRoles(Func<DB_RoleInfo, bool> selector)
{
Mapper.CreateMap<DB_RoleInfo, DO_RoleInfo>();
return _ctx.DB_RoleInfo
.Where(selector)
.Select(dbr => Mapper.Map<DB_RoleInfo, DO_RoleInfo>(dbr));
}
Это служебный метод, который обрабатывает сопоставление для вас и принимает фильтр исходной сущности, а не сопоставленной сущности. Это может быть полезно, если у вас много разных видов фильтров, но всегда нужно выполнять одно и то же отображение.
Лично я думаю, что вам будет лучше просто правильно выписать запросы, сначала определив, что вам нужно извлечь из базы данных, затем выполнив любые проекции / сопоставления, а затем, наконец, если вам понадобится дополнительная фильтрация (которую вы не следует), а затем материализовать результаты с ToList()
или же ToArray()
и напишите больше условий против местного списка.
Не пытайтесь использовать AutoMapper или любой другой инструмент, чтобы скрыть реальные сущности, предоставляемые Linq, в SQL. Модель предметной области - это ваш публичный интерфейс. Запросы, которые вы пишете, являются аспектом вашей частной реализации. Важно понимать разницу и поддерживать хорошее разделение интересов.