尝试从子对象映射时,是否有其他替代方法使用 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();
}
}
}