Подтвердить что ты не робот

Поддерживает ли AutoMapper Linq?

Мне очень нравится Linq to SQL с функцией Lazy load. И в моем проекте я использовал AutoMapper для сопоставления модели DB Model с моделью домена (от 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
    };
}

Этот метод работает так, как я хочу.

4b9b3361

Ответ 1

В то время как @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>();
}

и он будет вести себя как ручное сопоставление. (Оператор CreateMap приведен здесь для демонстрационных целей. Обычно вы определяете сопоставления один раз при запуске приложения).

Таким образом, запрашиваются только те столбцы, которые требуются для сопоставления, и результатом является IQueryable, у которого все еще есть исходный поставщик запросов (linq-to-sql, linq-to-entities, что угодно). Таким образом, он по-прежнему является составным, и это будет переведен в предложение WHERE в SQL:

SelectAll().Where(x => x.Id == Key).SingleOrDefault();

1Project().To<T>() до v. 4.1.0

Ответ 2

Измените вторую функцию следующим образом:

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 в фактический запрос к базе данных?

Linq ничего не знает о классе DO_RoleInfo. Он не знает, как сделать отображение назад - в некоторых случаях это возможно даже невозможно. Конечно, вы можете посмотреть на этот код и пойти "О, это просто, просто найдите" Администратор "или" Исполнительный "в столбце Name, но вы единственный, кто это знает. Что касается Linq to SQL, запрос является чисто ерундой.

Представьте, что кто-то дал вам следующие инструкции:

Перейдите в супермаркет и верните ингредиенты для приготовления Morton Thompson Turkey.

Если вы не сделали этого раньше, и большинство людей этого не сделали, ваш ответ на эту инструкцию, скорее всего, будет:

  • Что, черт возьми, это?

Вы можете пойти на рынок, и вы можете получить конкретные ингредиенты по имени, но вы не можете оценить состояние, которое я вам дал, пока вы там. Я должен "отобразить" критерии сначала. Я должен сказать вам, вот ингредиенты, которые нам нужны для этого рецепта - теперь идите и получите их.


Подводя итог, это не простая несовместимость между Linq to SQL и AutoMapper. Это не уникально ни для одной из этих двух библиотек. Неважно, как вы на самом деле выполняете сопоставление с типом не-сущности - вы можете так же легко выполнить сопоставление вручную, и вы все равно получите ту же ошибку, потому что теперь вы предоставляете Linq SQL для набора инструкций которые уже не понятны, имеют дело с таинственными классами, которые не имеют внутреннего отображения для какого-либо определенного типа сущности.

Эта проблема имеет фундаментальное значение для концепции отображения O/R и отложенного выполнения запроса. A проекция - это односторонняя операция. После того, как вы проецируете проект, вы больше не сможете вернуться к движку запросов и сказать, кстати, вот еще несколько условий для вас. Слишком поздно. Лучшее, что вы можете сделать, это взять то, что уже вам дали, и самостоятельно оценить дополнительные условия.


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

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. Модель домена - это открытый интерфейс. Запросы, которые вы пишете, являются аспектом вашей частной реализации. Важно понимать разницу и поддерживать хорошее разделение проблем.