【问题标题】:How to incorporate property value conversion into NHibernate QueryOver .SelectList?如何将属性值转换合并到 NHibernate QueryOver .SelectList 中?
【发布时间】:2011-08-08 09:55:22
【问题描述】:

我希望将属性值转换合并到我的 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))

所以问题是:如何将日期到字符串的转换合并到查询中?

我不想在查询执行后执行转换,因为在转换结果之前我需要一个额外的中间类来存储结果。

【问题讨论】:

    标签: c# asp.net-mvc nhibernate queryover


    【解决方案1】:
    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 转换器。您的转换器是否具有允许它忽略私有属性的设置或属性?

    【讨论】:

    • 谢谢。至于别名,确实是多余的,所以我将其从问题中删除。您的第一个示例中的字符串转换确实无法控制字符串格式,而我确实需要它。我将研究您的第二个建议,尽管我仍然更喜欢在查询对象而不是视图模型类中进行转换,但它可能会对我有所帮助。
    • 祝你好运,我尝试了很多东西,但这是我想出的最好的。其他可能性是创建自定义转换器或使用 Automapper。
    • 非常丑陋,但 AFAIK 仍然是唯一有效的解决方案。奇怪的是 QueryOver 对ToString() 有这么多麻烦,考虑到它翻译成完全相同的东西......
    • 我知道这是 OP 发布的方式,但为了确定:最后添加 ToList() 不会使 Future&lt;CustomerViewModel&gt;() 无用吗?
    • @kopranb 你可能是对的,ToList 否定了使用 Future 的好处。
    【解决方案2】:

    我今天遇到了同样的问题,并看到了这个帖子。我继续创建了自己的转换器,可以赋予转换器函数来处理每个属性的类型转换。

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

    这是我的使用方法,首先是我的 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"));
    

    希望这会有所帮助。

    【讨论】:

    • 这看起来是一个非常优雅的解决方案!这个星期天我可能会试一试。
    • 很高兴我能帮上忙。我可能应该在发布之前更好地测试一下。原始版本在定位转换器功能方面非常激进。现在我将字典中的转换器键入为 Tuple
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2023-04-02
    • 1970-01-01
    • 1970-01-01
    • 2020-11-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多