I'm looking to incorporate property value translations into my QueryOver queries.
I like writing queries following the Query Object Pattern, directly producing MVC view models. In my view models, I try to use property types that are as simple as possible, keeping conversion complexity out of the views and controllers. This means that sometimes, I'll need to convert one type into another, such as dates into strings.
One could argue that such conversions should be performed in views but since most of my view models are directly translated to JSON objects, that would cause the conversion to become much more cumbersome. Performing date to string conversion in JavaScript is problematic at best and my JSON convertor is not flexible enough.
Here's an example of what I'm doing:
// 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();
When running the query code, the following exception is thrown:
Object of type 'System.DateTimeOffset' cannot be converted to type 'System.String'.
Obviously, this is because of the following line:
.Select(n => n.DateCreated).WithAlias(() => model.DateCreated))
So the question is: how to incorporate the date to string conversion into the query?
I don't want to perform the conversion after the query has executed because I would need an additional intermediate class to store the results before converting them.
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();
Should work but unfortunately does not give you any control over the format of the string. I've handled a similar problem by defining a private property on the model that holds the data as the correct type and a string property to return the formatted value, i.e.:
public class CustomerViewModel
{
public string Name { get; set; }
private DateTime DateCreatedImpl { get; set; }
public string DateCreated { get { return DateCreatedImpl.ToString(); }}
}
This has several advantages but might not work well with your JSON converter. Does your converter have a setting or attribute that would allow it to ignore private properties?
I came across the same problem today, and saw this posting. I went ahead and created my own transformer that can be given Converter functions to handle type conversions per property.
Here is the Transformer class.
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;
}
And here is how I use it, first my 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; }
}
Later on when I call my query, it returns Start and End as DateTime values. So this is how I actually use the converter.
var transformer = new AliasToDTOTransformer<EventDetailDTO>();
transformer.AddConverter((DateTime d) => d.ToString("g"));
Hope this helps.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With