Как включить преобразование значения свойства в NHibernate QueryOver .SelectList?

Я хочу включить переводы значений свойств в свои запросы QueryOver.

Мне нравится писать запросы в соответствии с шаблоном объекта запроса, напрямую создавая модели представления MVC. В своих моделях представлений я стараюсь использовать как можно более простые типы свойств, не допуская сложности преобразования представлений и контроллеров. Это означает, что иногда мне нужно преобразовать один тип в другой, например даты в строки.

Можно возразить, что такие преобразования должны выполняться в представлениях, но, поскольку большинство моих моделей представлений напрямую транслируются в объекты JSON, это сделало бы преобразование гораздо более громоздким. Выполнение преобразования даты в строку в JavaScript в лучшем случае проблематично, а мой преобразователь JSON недостаточно гибок.

Вот пример того, что я делаю:

// 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();

При выполнении кода запроса возникает следующее исключение:

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

Очевидно, это из-за следующей строки:

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

Итак, возникает вопрос: как включить преобразование даты в строку в запрос?

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


person Sandor Drieënhuizen    schedule 08.08.2011    source источник


Ответы (2)


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

Должно работать, но, к сожалению, не дает вам никакого контроля над форматом строки. Я решил аналогичную проблему, определив частное свойство модели, которое содержит данные как правильный тип, и строковое свойство для возврата отформатированного значения, т.е.:

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

Это имеет несколько преимуществ, но может не работать с вашим конвертером JSON. Есть ли в вашем конвертере настройка или атрибут, который позволяет игнорировать частные свойства?

person Jamie Ide    schedule 08.08.2011
comment
Спасибо. Что касается псевдонима, то он действительно лишний, поэтому я убрал его из вопроса. Преобразование строки в вашем первом примере действительно не оставляет контроля над форматом строки, хотя мне это действительно нужно. Я собираюсь изучить ваше второе предложение, оно может мне помочь, хотя я по-прежнему предпочитаю выполнять преобразование в объекте запроса, а не в классе модели представления. - person Sandor Drieënhuizen; 08.08.2011
comment
Удачи, я пробовал кучу вещей, но это лучшее, что я придумал. Другие возможности — создание собственного преобразователя или использование Automapper. - person Jamie Ide; 09.08.2011
comment
Ужасно некрасиво, но, насколько мне известно, это единственное работающее решение. Странно, что у QueryOver столько проблем с ToString(), учитывая, что он переводится точно так же... - person Aaronaught; 23.01.2012
comment
Я знаю, что так написал ОП, но просто чтобы быть уверенным: добавление ToList() в конце не делает Future<CustomerViewModel>() бесполезным? - person Samir Aguiar; 31.05.2016
comment
@kopranb Возможно, вы правы, ToList сводит на нет преимущества использования Future. - person Jamie Ide; 01.06.2016

Сегодня я столкнулся с той же проблемой и увидел это сообщение. Я пошел дальше и создал свой собственный преобразователь, которому можно дать функции преобразователя для обработки преобразований типов для каждого свойства.

Вот класс Трансформер.

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

И вот как я его использую, сначала мой 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; } 
}

Позже, когда я вызываю свой запрос, он возвращает Start и End как значения DateTime. Вот как я на самом деле использую конвертер.

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

Надеюсь это поможет.

person NYCChris    schedule 02.12.2011
comment
Это кажется очень элегантным решением! В это воскресенье, пожалуй, попробую. - person Sandor Drieënhuizen; 03.12.2011
comment
Рад, что смог помочь. Я, вероятно, должен был проверить это лучше, прежде чем публиковать. Первоначальная версия была далеко не агрессивной в поиске функции конвертера. Теперь я помечаю преобразователи в словаре как Tuple‹SourceType,ReturnType› - person NYCChris; 03.12.2011