¿AutoMapper es compatible con Linq?

Estoy muy interesado en Linq a SQL con la característica de carga diferida. Y en mi proyecto utilicé AutoMapper para mapear el Modelo de BD al Modelo de Dominio (desde DB_RoleInfo a DO_RoleInfo ). En mi código de repository como a continuación:

  public DO_RoleInfo SelectByKey(Guid Key) { return SelectAll().Where(x => x.Id == Key).SingleOrDefault(); } public IQueryable SelectAll() { Mapper.CreateMap(); return from role in _ctx.DB_RoleInfo select Mapper.Map(role); } 

SelectAll método SelectAll se ejecuta bien, pero cuando llamo a SelectByKey , SelectByKey el error:

El método “RealMVC.Data.DO_RoleInfo MapDB_RoleInfo, DO_RoleInfo” no se pudo traducir a SQL.

¿Es que Automapper no es compatible con Linq por completo?

En lugar de Automapper, probé el código de mapeo manual a continuación:

 public IQueryable SelectAll() { return from role in _ctx.DB_RoleInfo select new DO_RoleInfo { Id = role.id, name = role.name, code = role.code }; } 

Este método funciona como yo quiero.

Si bien la respuesta de @Aaronaught era correcta al momento de escribir, con tanta frecuencia el mundo ha cambiado y AutoMapper con él. Mientras tanto, QueryableExtensions se agregaron a la base de código que agregó soporte para proyecciones que se traducen en expresiones y, finalmente, SQL.

El método de extensión principal es ProjectTo 1 . Así es como se vería tu código:

 using AutoMapper.QueryableExtensions; public IQueryable SelectAll() { Mapper.CreateMap(); return _ctx.DB_RoleInfo.ProjectTo(); } 

y se comportaría como el mapeo manual. (La instrucción CreateMap está aquí para fines de demostración. Normalmente, definirías las asignaciones una vez al inicio de la aplicación).

Por lo tanto, solo se consultan las columnas que se requieren para la asignación y el resultado es una IQueryable que todavía tiene el proveedor de consulta original (linq-to-sql, linq-to-entities, whatever). Por lo tanto, todavía es composable y esto se traducirá en una cláusula WHERE en SQL:

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

1 Project().To() antes de v. 4.1.0

Cambie su segunda función a esto:

 public IEnumerable SelectAll() { Mapper.CreateMap(); return from role in _ctx.DB_RoleInfo.ToList() select Mapper.Map(role); } 

AutoMapper funciona bien con Linq a SQL, pero no se puede ejecutar como parte de la consulta diferida. Agregar ToList() al final de su consulta Linq hace que evalúe inmediatamente los resultados, en lugar de tratar de traducir el segmento AutoMapper como parte de la consulta.


Aclaración

La noción de ejecución diferida ( no “carga lenta”) no tiene ningún sentido una vez que ha cambiado el tipo resultante a algo que no es una entidad de datos. Considera estas dos clases:

 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 } 

Ahora considere la siguiente asignación:

 Mapper.CreateMap .ForMember(dest => dest.Role, opt => opt.MapFrom(src => (Role)Enum.Parse(typeof(Role), src.Name))); 

Este mapeo está completamente bien (a menos que haya cometido un error tipográfico), pero digamos que escribes el método SelectAll en tu publicación original en lugar de la versión revisada:

 public IQueryable SelectAll() { Mapper.CreateMap(); return from role in _ctx.DB_RoleInfo select Mapper.Map(role); } 

Esto realmente funciona, pero al llamarse a sí mismo un “cuestionable”, miente. ¿Qué ocurre si trato de escribir esto en su contra?

 public IEnumerable SelectSome() { return from ri in SelectAll() where (ri.Role == Role.Administrator) || (ri.Role == Role.Executive) select ri; } 

Piensa mucho sobre esto. ¿Cómo es posible que Linq to SQL sea capaz de convertir con éxito su where en una consulta de base de datos real?

Linq no sabe nada sobre la clase DO_RoleInfo . No sabe cómo hacer el mapeo hacia atrás , en algunos casos, que ni siquiera es posible. Claro, puedes mirar este código e ir “Oh, eso es fácil, solo busca ‘Administrador’ o ‘Ejecutivo’ en la columna Name , pero eres el único que sabe eso. En lo que se refiere a Linq a SQL, la consulta es pura tontería.

Imagina que alguien te dio estas instrucciones:

Ve al supermercado y trae los ingredientes para hacer Morton Thompson Turkey.

A menos que lo hayas hecho antes, y la mayoría de las personas no, tu respuesta a esa instrucción probablemente será:

  • ¿Qué demonios es eso?

Puedes ir al mercado, y puedes obtener ingredientes específicos por nombre, pero no puedes evaluar la condición que te he dado mientras estás allí . Tengo que “des-mapear” los criterios primero . Tengo que decirte, aquí están los ingredientes que necesitamos para esta receta: ahora ve a buscarlos.


Para resumir, esto no es una simple incompatibilidad entre Linq a SQL y AutoMapper. No es exclusivo de ninguna de esas dos bibliotecas. No importa cómo se realice la asignación a un tipo que no sea de entidad: se podría hacer la asignación de la forma más fácil manualmente, y aún así se obtendría el mismo error, porque ahora se le está dando a Linq un conjunto de instrucciones SQL que ya no son comprensibles, que tratan con clases misteriosas que no tienen un mapeo intrínseco a ningún tipo de entidad particular.

Este problema es fundamental para el concepto de Asignación O / R y la ejecución diferida de consultas. Una proyección es una operación unidireccional . Una vez que haya proyectado, ya no podrá regresar al motor de búsqueda y decir, por cierto, aquí hay algunas condiciones más para usted . Es demasiado tarde. Lo mejor que puede hacer es tomar lo que ya le dio y evaluar las condiciones adicionales usted mismo.


Por último, pero no menos importante, te dejo con una solución alternativa. Si lo único que desea hacer desde su asignación es filtrar las filas, puede escribir esto:

 public IEnumerable SelectRoles(Func selector) { Mapper.CreateMap(); return _ctx.DB_RoleInfo .Where(selector) .Select(dbr => Mapper.Map(dbr)); } 

Este es un método de utilidad que maneja el mapeo por usted y acepta un filtro en la entidad original , y no en la entidad mapeada. Puede ser útil si tiene muchos tipos diferentes de filtros, pero siempre debe hacer el mismo mapeo.

Personalmente, creo que será mejor que solo escriba las consultas correctamente, primero determinando lo que necesita recuperar de la base de datos , luego haciendo proyecciones / asignaciones, y luego, finalmente, si necesita filtrar más (lo cual no debería), luego materialice los resultados con ToList() o ToArray() y escriba más condiciones en la lista local.

No intente utilizar AutoMapper ni ninguna otra herramienta para ocultar las entidades reales expuestas por Linq a SQL. El modelo de dominio es tu interfaz pública . Las consultas que escribe son un aspecto de su implementación privada . Es importante entender la diferencia y mantener una buena separación de preocupaciones.