2010-02-06 13 views
20

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 DB al modelo de dominio (desde DB_RoleInfo hasta DO_RoleInfo). En mi repositorio de código de la siguiente manera:¿AutoMapper es compatible con Linq?

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 método se ejecuta bien, pero cuando llamo SelectByKey, me sale el error:

Method “RealMVC.Data.DO_RoleInfo MapDB_RoleInfo,DO_RoleInfo” could not translate to SQL.

Es que AutoMapper no es compatible con LINQ completo?

En lugar de AutoMapper, he intentado por debajo del código de asignación manual de:

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 
    }; 
} 

Este método funciona de la manera que quiero que haga.

+0

Sólo una nota en su actualización, además de mi respuesta: la segunda versión funciona porque Linq to SQL ya * sabe * que ha realizado una proyección irreversible; el 'SelectByKey' en realidad solo usa Linq para objetos. Si examina la consulta real que se está generando, creo que encontrará que todavía está seleccionando todas las entidades de la base de datos, lo que equivale a usar 'ToList()' y luego filtrar la lista resultante. – Aaronaught

+2

No es que AutoMapper admita LINQ. Es que LINQ to SQL no es compatible con AutoMapper. El proveedor de consultas de LINQ a SQL observa el árbol de expresiones para determinar cómo generar la consulta SQL. Cuando se trata de la pieza Mapper.Map, no tiene idea de cómo generar el SQL. –

Respuesta

23

Cambiar la segunda función a esto:

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 funciona bien con LINQ to 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 perezosa") no tiene ningún sentido una vez que haya cambiado el tipo resultante a algo que no es una entidad de datos. Tenga en cuenta 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 
} 

Consideremos ahora la siguiente asignación:

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

Esta asignación es completamente bien (a menos que hiciera un error tipográfico), pero digamos que escribe el método SelectAll en su puesto original en lugar de mi revisó uno:

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); 
} 

en realidad, esto tipo de obras, pero llamando a sí mismo un "consultable", que se encuentra. ¿Qué sucede si trato de escribir esto en su contra:

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

Piensa mucho en esto. ¿Cómo podría Linq to SQL posiblemente ser 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 la asignación hacia atrás - en algunos casos, eso puede que ni siquiera sea posible. Claro, puedes mirar este código e ir al "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 to SQL, la consulta es pura tontería.

Imagine que alguien le dio estas instrucciones:

Go to the supermarket and bring back the ingredients for making Morton Thompson Turkey.

A menos que usted lo ha hecho antes, y la mayoría de la gente no tiene, su respuesta a la instrucción es muy probable que va a ser:

  • ¿Qué diablos es eso?

Puede ir al mercado, y se puede obtener ingredientes específicos por su nombre, pero no se puede evaluar la condición que le he dado mientras estás allí. Tengo que "quitar el mapa" de los criterios primero. Tengo que decirte, aquí están los ingredientes que necesitamos para esta receta - ahora ve a buscarlos.


En resumen, esto no es una sencilla incompatibilidad entre LINQ to SQL y AutoMapper. No es exclusivo de ninguna de esas dos bibliotecas. No importa cómo realizas la asignación a un tipo que no sea de entidad; podrías hacer la asignación de la forma más fácil manualmente, y aún obtendrías el mismo error, porque ahora le estás dando a Linux un conjunto de instrucciones 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 de O/R y la ejecución diferida de consultas. A proyección es operación unidireccional. Una vez que haya proyectado, ya no podrá regresar al motor de consultas 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 por eso menos importante, lo dejo con una solución. Si la cosa única desea ser capaz de hacer de su asignación es filtro las filas, puede escribir lo siguiente:

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)); 
} 

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

En lo personal, creo que será mejor simplemente escribir las consultas correctamente, determinando en primer lugar lo que necesita para recuperar de la base de datos de, a continuación, hacer cualquier proyecciones/asignaciones, y luego, por último, si es necesario do filtro adicional (que no debe), luego materializar los resultados con ToList() o ToArray() y escribir más condiciones en la lista local.

No intente utilizar AutoMapper o cualquier otra herramienta para ocultar las entidades reales expuestas por Linq a SQL.El modelo de dominio es su 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.

+0

¡Gracias por tu respuesta! Pero en la forma en que dio, SelectAll recuperará todos los registros de DB. Me gustaría disfrutar de los beneficios de la carga diferida, significa que la aplicación seleccionará un registro de DB en SelectByKey, pero no todos los registros en SelectAll. –

+2

La respuesta de Gert Arnold a continuación es LA solución. Como referencia, aquí está la API relevante https://github.com/AutoMapper/AutoMapper/blob/master/src/AutoMapper/QueryableExtensions.cs –

54

Si bien la respuesta de @Aaronaught era correcta al momento de escribir, con frecuencia el mundo ha cambiado y AutoMapper con él. Mientras tanto, se agregaron QueryableExtensions 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 del núcleo es ProjectTo . Esto es lo que el código podría ser:

using AutoMapper.QueryableExtensions; 

public IQueryable<DO_RoleInfo> SelectAll() 
{ 
    Mapper.CreateMap<DB_RoleInfo, DO_RoleInfo>(); 
    return _ctx.DB_RoleInfo.ProjectTo<DO_RoleInfo>(); 
} 

y se comportaría como el mapeo manual. (La declaración CreateMap está aquí para fines de demostración. Normalmente, definiría 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 un IQueryable que todavía tiene el proveedor de consulta original (linq-to-sql, linq-to-entities, whatever). Por lo que todavía es componibles y esto se traducirá en una cláusula WHERE en SQL:.

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

Project().To<T>() antes de la v 4.1.0

+2

Esta respuesta debe convertirse en la nueva respuesta. –

+3

Solo agregue usando AutoMapper.QueryableExtensions; – Colin

+0

Recibo el siguiente error "El método 'Dónde' no puede seguir el método 'Seleccionar' o no es compatible". Por favor vea la pregunta aquí (http://stackoverflow.com/q/16174351/1133338). –

Cuestiones relacionadas