2

尝试从子对象映射时,是否有其他替代方法使用 AutoMapper 可查询扩展来避免空引用异常?

背景

使用 AutoMapper Queryable 扩展投影到 CustomerViewModel 时,映射 FullAddress 属性失败并出现空引用异常。我向 AutoMapper 团队https://github.com/AutoMapper/AutoMapper/issues/351提出了一个问题,并使用测试工具重现了该问题。名为can_map_AsQuerable_with_projection_this_FAILS的测试是失败的测试。

希望继续使用 AutoMapper 和 Queryable Extensions,因为代码富有表现力且易于阅读;但是,计算 FullAddress 会引发空引用异常。我知道是 FullAddress 映射导致了这个问题,因为如果我将它更改为 Ignore(),那么映射就会成功。当然,测试仍然失败,因为我正在检查以确保 FullAddress 具有值。

我想出了一些替代方案,但它们不使用 AutoMapper 映射。这些方法中的每一种都在以下测试用例中进行了概述。

**can_map_AsQuerable_with_expression**
**can_map_AsQuerable_with_custom_mapping**  

测试夹具如下。

namespace Test.AutoMapper
{
    public class Customer
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }

        public Address Address { get; set; }
    }

    public class Address
    {
        public string Street { get; set; }
        public string City { get; set; }
        public string State { get; set; }
    }

    public class CustomerViewModel
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }

        public string FullAddress { get; set; }
    }


    [TestFixture]
    public class AutoMapperQueryableExtensionsThrowsNullReferenceExceptionSpec
    {
        protected List<Customer> Customers { get; set; }

        [SetUp]
        public void Setup()
        {
            Mapper.CreateMap<Customer, CustomerViewModel>()
                  .ForMember(x => x.FullAddress,
                             o => o.MapFrom(s => String.Format("{0}, {1} {2}", 
                                                        s.Address.Street, 
                                                        s.Address.City, 
                                                        s.Address.State))); 

            Mapper.AssertConfigurationIsValid();

            Customers = new List<Customer>()
                {
                    new Customer() {
                            FirstName = "Mickey", LastName = "Mouse", 
                            Address = new Address() { Street = "My Street", City = "My City", State = "my state" }
                        }, 

                        new Customer() {
                            FirstName = "Donald", LastName = "Duck", 
                            Address = new Address() { Street = "My Street", City = "My City", State = "my state" }
                        }
                };
        }

        [Test]
        public void can_map_single()
        {
            var vm = Mapper.Map<CustomerViewModel>(Customers[0]);
            Assert.IsNotNullOrEmpty(vm.FullAddress);
        }

        [Test]
        public void can_map_multiple()
        {
            var customerVms = Mapper.Map<List<CustomerViewModel>>(Customers);
            customerVms.ForEach(x => Assert.IsNotNullOrEmpty(x.FullAddress));
        }

        /// <summary>
        /// This does NOT work, throws NullReferenceException.
        /// </summary>
        /// <remarks>
        /// System.NullReferenceException : Object reference not set to an instance of an object.
        /// at AutoMapper.MappingEngine.CreateMapExpression(Type typeIn, Type typeOut)
        /// at AutoMapper.MappingEngine.CreateMapExpression(Type typeIn, Type typeOut)
        /// at AutoMapper.MappingEngine.<CreateMapExpression>b__9<TSource,TDestination>(TypePair tp)
        /// at System.Collections.Concurrent.ConcurrentDictionary`2.GetOrAdd(TKey key, Func`2 valueFactory)
        /// at AutoMapper.MappingEngine.CreateMapExpression()
        /// at AutoMapper.QueryableExtensions.ProjectionExpression`1.To()
        /// </remarks>
        [Test]
        public void can_map_AsQuerable_with_projection_this_FAILS()
        {
            var customerVms = Customers.AsQueryable().Project().To<CustomerViewModel>().ToList();
            customerVms.ForEach(x => Assert.IsNotNullOrEmpty(x.FullAddress));
        }

        [Test]
        public void can_map_AsQuerable_with_expression()
        {
            var customerVms = Customers.AsQueryable().Select(ToVM.ToCustomerViewModelExpression()).ToList();
            customerVms.ForEach(x => Assert.IsNotNullOrEmpty(x.FullAddress));
        }

        [Test]
        public void can_map_AsQuerable_with_custom_mapping()
        {
            var customerVms = Customers.AsQueryable().Select(ToVM.ToCustomerViewModel).ToList();
            customerVms.ForEach(x => Assert.IsNotNullOrEmpty(x.FullAddress));
        }
    }

    public static class ToVM
    {
        public static CustomerViewModel ToCustomerViewModel(this Customer source)
        {
            return new CustomerViewModel()
                {
                    FirstName = source.FirstName, 
                    LastName = source.LastName,
                    FullAddress = String.Format("{0}, {1} {2}",
                                                        source.Address.Street,
                                                        source.Address.City,
                                                        source.Address.State)
                }; 
        }

        public static Expression<Func<Customer, CustomerViewModel>> ToCustomerViewModelExpression()
        {
            return source => source.ToCustomerViewModel(); 
        }
    }
}
4

3 回答 3

1

仅供参考,对于将来从 Google 获得此功能的任何人:AutoMapper 团队最近已修复此问题。此外,从 AutoMapper 3.3.0 开始,QueryableExtensions 将通过内部调用自动处理目标是字符串的简单情况ToString()- 请参阅发行说明

我在 QueryableExtensions 上遇到了一些与 OP 类似的问题(尽管情况更简单),并且 v3.3.0 中的修复效果很好。

于 2014-12-23T19:29:49.783 回答
1

我面临同样的问题 - 我以同样的方式解决了它。

我找到了“规范函数”,一组 LINQ 提供者应该实现的函数(http://msdn.microsoft.com/en-us/library/bb399302.aspx)。它们被映射到 CLR 函数。例如,如果您使用 String.Concate(Street,City,State) 而不是 string.Format(...) 它应该可以工作......但不幸的是它也不是!

我希望 Automapper 团队能够尽快解决这个问题。

于 2013-07-09T08:33:19.320 回答
1

我找到了一个使用 AutoMapper 和 Queryable Extensions 的工作解决方案。问题在于在投影中使用 String.Format 。解决方案是将所有必要的属性(街道、城市和州)添加到 CustomViewModel,然后添加属性 (FullAddress) 以在 CustomerViewModel 中进行计算。

public class CustomerViewModel
{
    public string FirstName { get; set; }
    public string LastName { get; set; }

    public string Street { get; set; }
    public string City { get; set; }
    public string State { get; set; }

    public string FullAddress
    {
        get
        {
            return String.Format("{0}, {1} {2}",
                          Street,
                          City,
                          State);
        }
    }
}

更新后的映射如下所示。请注意 FullAddress 被忽略,因为这是一个包含引用 toe String.Format 的计算字段。

        Mapper.CreateMap<Customer, CustomerViewModel>()
            .ForMember(x => x.FirstName, o => o.MapFrom(s => s.FirstName))
            .ForMember(x => x.LastName, o => o.MapFrom(s => s.LastName))
            .ForMember(x => x.Street, o => o.MapFrom(s => s.Address.Street))
            .ForMember(x => x.City, o => o.MapFrom(s => s.Address.City))
            .ForMember(x => x.State, o => o.MapFrom(s => s.Address.State))
            .ForMember(x => x.FullAddress, o => o.Ignore())
        ;

并且,通过这些修改,这个测试现在通过了。

    [Test]
    public void can_map_AsQuerable_with_projection_this_FAILS()
    {
        var customerVms = Customers.AsQueryable().Project().To<CustomerViewModel>().ToList();
        customerVms.ForEach(x => Assert.IsNotNullOrEmpty(x.FullAddress));
    }
于 2013-06-30T16:35:01.023 回答