2011-08-08 14 views
6

Estoy buscando incorporar traducciones de valor de propiedad en mis consultas de QueryOver.Cómo incorporar la conversión de valor de propiedad en NHibernate QueryOver .SelectList?

Me gusta escribir consultas siguiendo el patrón de objeto de consulta, produciendo directamente modelos de vista MVC. En mi opinión, los modelos, trato de usar tipos de propiedades que sean lo más simples posible, manteniendo la complejidad de conversión fuera de las vistas y los controladores. Esto significa que, a veces, tendré que convertir un tipo en otro, como las fechas en cadenas.

Se podría argumentar que tales conversiones deberían realizarse en las vistas, pero como la mayoría de mis modelos de vista se traducen directamente a objetos JSON, la conversión sería mucho más engorrosa. Realizar la conversión de fecha a cadena en JavaScript es problemático en el mejor de los casos y mi convertidor JSON no es lo suficientemente flexible.

He aquí un ejemplo de lo que estoy haciendo:

// Entity. 
public class Customer 
{ 
    public int Id { get; set; } 
    public string Name { get; set; } 
    public DateTimeOffset DateCreated { get; set; } 
} 

// View model. 
public class CustomerViewModel 
{ 
    public string Name { get; set; } 
    public string DateCreated { get; set; } // Note the string type here. 
} 

// Query. 
CustomerViewModel model = null; 

List<CustomerViewModel> result = Session.QueryOver<Customer>() 
    .SelectList(list => list 
     .Select(n => n.Name).WithAlias(() => model.Name) 
     .Select(n => n.DateCreated).WithAlias(() => model.DateCreated)) 
    .TransformUsing(Transformers.AliasToBean<CustomerViewModel>()); 
    .Future<CustomerViewModel>() 
    .ToList(); 

Cuando se ejecuta el código de consulta, la excepción siguiente:

Object of type 'System.DateTimeOffset' cannot be converted to type 'System.String'. 

Obviamente, esto es debido a la siguiente línea:

.Select(n => n.DateCreated).WithAlias(() => model.DateCreated)) 

Así que la pregunta es: ¿cómo para incorporar la fecha a la conversión de cadena en la consulta?

No deseo realizar la conversión después de que se haya ejecutado la consulta porque necesitaría una clase intermedia adicional para almacenar los resultados antes de convertirlos.

Respuesta

9
List<CustomerViewModel> result = Session.QueryOver<Customer>(() => customerAlias) 
    .SelectList(list => list 
     .Select(n => customerAlias.Name).WithAlias(() => model.Name) 
     // I'm not sure if customerAlias works here or why you have declared it at all 
     .Select(Projections.Cast(NHibernateUtil.String, Projections.Property<Customer>(c => c.DateCreated))).WithAlias(() => model.DateCreated)) 
    .TransformUsing(Transformers.AliasToBean<CustomerViewModel>()); 
    .Future<CustomerViewModel>() 
    .ToList(); 

Debería funcionar, pero desafortunadamente no le da ningún control sobre el formato de la cadena. He manejado un problema similar mediante la definición de una propiedad privada en el modelo que contiene los datos como el tipo correcto y una propiedad de cadena para devolver el valor con formato, es decir:

public class CustomerViewModel 
{ 
    public string Name { get; set; } 
    private DateTime DateCreatedImpl { get; set; } 
    public string DateCreated { get { return DateCreatedImpl.ToString(); }} 
} 

Esto tiene varias ventajas, pero podría no funcionar bien con tu convertidor JSON. ¿Su convertidor tiene una configuración o atributo que le permitiría ignorar las propiedades privadas?

+0

Gracias. En cuanto al alias, es superfluo, así que lo eliminé de la pregunta. La conversión de cadenas en su primer ejemplo de hecho no deja ningún control sobre el formato de cadena, mientras que realmente lo necesito.Voy a investigar su segunda sugerencia, podría ayudarme aunque prefiera hacer la conversión en el objeto de consulta en lugar de la clase de modelo de vista. –

+0

Buena suerte, probé un montón de cosas, pero esto es lo mejor que se me ocurrió. Otras posibilidades son crear un transformador personalizado o usar Automapper. –

+0

Terriblemente feo, pero AFAIK sigue siendo la única solución que funciona. Es extraño que QueryOver tenga tantos problemas con 'ToString()', teniendo en cuenta que se traduce exactamente en lo mismo ... – Aaronaught

3

Me encontré con el mismo problema hoy, y vi esta publicación. Seguí adelante y creé mi propio transformador al que se le pueden asignar funciones de conversión para manejar conversiones de tipo por propiedad.

Aquí está la clase Transformer.

public class AliasToDTOTransformer<D> : IResultTransformer where D: class, new() 
{ 
    //Keep a dictionary of converts from Source -> Dest types... 
    private readonly IDictionary<Tuple<Type, Type>, Func<object, object>> _converters; 

    public AliasToDTOTransformer() 
    { 
     _converters = _converters = new Dictionary<Tuple<Type, Type>, Func<object, object>>(); 
    } 

    public void AddConverter<S,R>(Func<S,R> converter) 
    { 
     _converters[new Tuple<Type, Type>(typeof (S), typeof (R))] = s => (object) converter((S) s); 
    } 
    public object TransformTuple(object[] tuple, string[] aliases) 
    { 
     var dto = new D(); 
     for (var i = 0; i < aliases.Length; i++) 
     { 
      var propinfo = dto.GetType().GetProperty(aliases[i]); 
      if (propinfo == null) continue; 
      var valueToSet = ConvertValue(propinfo.PropertyType, tuple[i]); 
      propinfo.SetValue(dto, valueToSet, null); 
     } 
     return dto; 
    } 
    private object ConvertValue(Type destinationType, object sourceValue) 
    { 
     //Approximate default(T) here 
     if (sourceValue == null) 
      return destinationType.IsValueType ? Activator.CreateInstance(destinationType) : null; 

     var sourceType = sourceValue.GetType(); 
     var tuple = new Tuple<Type, Type>(sourceType, destinationType); 
     if (_converters.ContainsKey(tuple)) 
     { 
      var func = _converters[tuple]; 
      return Convert.ChangeType(func.Invoke(sourceValue), destinationType); 
     } 

     if (destinationType.IsAssignableFrom(sourceType)) 
      return sourceValue; 

     return Convert.ToString(sourceValue); // I dunno... maybe throw an exception here instead? 
    } 

    public IList TransformList(IList collection) 
    { 
     return collection; 
    } 

Y aquí es cómo lo uso, mi primera DTO:

public class EventDetailDTO : DescriptionDTO 
{ 
    public string Code { get; set; } 
    public string Start { get; set; } 
    public string End { get; set; } 
    public int Status { get; set; } 

    public string Comment { get; set; } 
    public int Client { get; set; } 
    public int BreakMinutes { get; set; } 
    public int CanBeViewedBy { get; set; } 
} 

Más tarde, cuando llamo a mi consulta, devuelve inicio y fin como valores de fecha y hora. Así que así es como realmente uso el convertidor.

var transformer = new AliasToDTOTransformer<EventDetailDTO>(); 
transformer.AddConverter((DateTime d) => d.ToString("g")); 

Espero que esto ayude.

+0

¡Parece una solución muy elegante! Probablemente lo probaré este domingo. –

+0

Me alegro de poder ayudar. Probablemente debería haber probado esto mejor antes de publicar. La versión original fue muy agresiva en la localización de un convertidor func. Ahora clave los convertidores en el diccionario como Tuple NYCChris

Cuestiones relacionadas