AutoMapper无法将计算字段映射到标量。

3

我正在尝试将我的MVC3项目分离成适当的DAL/Domain/ViewModel架构,但是我在使用AutoMapper从域映射到视图模型时遇到了一个问题,即如何映射计算字段。

以下是我尝试做的示例:

接口

public interface IRequirement
{
    int Id { get; set; }
    ... bunch of others
    public decimal PlanOct { get; set; }
    public decimal PlanNov { get; set; }
    public decimal PlanDec { get; set; }
    ... and so on
    decimal PlanQ1 { get; }
    ... etc
    decimal PlanYear { get; }
    ... repeat for ActualOct, ActualNov ... ActualQ1 ... ActualYear...
}

Domain Model

public class Requirement : IRequirement
{
    public int Id { get; set; }
    ... bunch of others
    public decimal PlanOct { get; set; }
    public decimal PlanNov { get; set; }
    public decimal PlanDec { get; set; }
    ... and so on
    public decimal PlanQ1 { get { return PlanOct + PlanNov + PlanDec; } }
    ... etc
    public decimal PlanYear { get { return PlanQ1 + PlanQ2 + PlanQ3 + PlanQ4; } }
    ... repeat for ActualOct, ActualNov ... ActualQ1 ... ActualYear...
}

还有VarianceX属性,例如计算公式为(PlanOct - ActualOct)的VarianceOct等。

我的视图模型几乎与上述内容相同,只是使用默认的getter/setter语法而非计算字段,例如:

public decimal PlanQ1 { get; set; }

我的Global.asax中的AutoMapper配置如下:
Mapper.CreateMap<Domain.Abstract.IRequirement, Models.Requirement.Details>();

这在所有属性上运行良好,除了计算属性。我的所有计算字段(即*Q1、*Q2、*Q3、*Q4、*Year和所有Variance*字段)实际上都没有映射 - 它们都显示默认值0.00。
我对此感到困惑,并且我也是AutoMapper的新手,所以也许我错过了什么。我的直觉是,由于属性签名不相同(即域对象只有非默认getter而没有setter,而视图模型具有默认getter和setter),因此AutoMapper没有将其选中。但我还做了这个:
Mapper.CreateMap<Domain.Abstract.IRequirement, Models.Requirement.Details>()
            .ForMember(dest => dest.PlanQ1, opt => opt.MapFrom(src => src.PlanQ1);

并且计算结果仍然是0。我在调试器中进行了确认。

我做错了什么?

先谢谢你。

编辑1

在遵循Wal的建议后,我运行了测试,它可以正常工作,所以我一步一步地回溯,首先将Field1 / Field2 / Field3部分粘贴到接口/域/视图模型类中,并验证其在我的控制器中是否有效,然后逐一更改。我发现,由于我处理的是十进制类型,如果我硬编码为整数或双精度值,则会得到零,但如果我强制转换为十进制或使用十进制文字,则可以正常工作。但是,只有在我手动设置它们时才有效,而不是从数据库中提取值时。

换句话说,这个可以工作(即PlanQ1 = 6):

var D = new Requirement { PlanOct = (decimal) 1.0, PlanNov = (decimal) 2.0, PlanDec = (decimal) 3.0 };
var V = Mapper.Map<IRequirement, Details>(D);

这是可行的:

var D = new Requirement { PlanOct = 1M, PlanNov = 2M, PlanDec = 3M };
var V = Mapper.Map<IRequirement, Details>(D);

但这并不是(从存储库对象中提取单个域对象,进而使用Entity Framework从SQL Server中提取):
var D = requirementRepository.Requirement(5);
var V = Mapper.Map<IRequirement, Details>(D);

在上述情况下,我得到的计划Q1和计划年份都是0。我已经验证了域对象(D)中的PlanOct = 1、PlanNov = 2和PlanDec = 3。我还验证了所有对象的类型,包括EF生成的对象,都是十进制数,并且SQL Server类型也是十进制数。甚至我尝试将其映射到创建的视图模型,以排除错误,但仍然得到了计划Q1和计划年份为0的结果:

var D = requirementRepository.Requirement(5);
var V = new Details();
Mapper.Map<IRequirement, Details>(D, V);
3个回答

2

PlanQ1IRequirement的成员吗?根据您最后的代码片段,您已经暗示它是了,但如果不是,那么您将会得到完全符合您所描述的行为。

考虑一个简化的例子:

public interface IFoo
{
    string Field1 { get; set; }
    string Field2 { get; set; }
    //string Field3 { get; }
}

public class Foo1 : IFoo
{
    public string Field1 { get; set; }
    public string Field2 { get; set; }
    public string Field3 { get { return Field1 + Field2; } }
}
public class Foo2
{
    public string Field1 { get; set; }
    public string Field2 { get; set; }
    public string Field3 { get; set; }
}

在这个例子中,我从接口中省略了Field3。现在当我运行以下代码时,映射失败

[TestMethod]
public void Map()
{
    Mapper.CreateMap<IFoo, Foo2>();
    var foo1 = new Foo1() { Field1 = "field1", Field2 = "field2" };
    var foo2 = new Foo2();
    Mapper.Map(foo1, foo2);
    Assert.AreEqual("field1field2", foo2.Field3);//fails, not mapped
}

因此,如果我从IFoo中对Field3进行注释,则一切都能正常工作。使用您的代码检查此简化示例。


好的观点。不幸的是,是的PlanQ1和其他变量在接口中被定义为十进制PlanQ1 { get; }。它失败了,因为它只定义了一个getter,而视图模型有一个getter和setter? - Dave
不是的。我只是在接口中添加了一个默认的set{},所以现在领域模型接口、领域模型类和视图模型类都有setter和getter。结果仍然是0.00。 - Dave
你最近评论的内容描述了我的例子 - 你的例子与我给出的例子有何不同?一定存在导致错误行为的差异。 - wal
好的,我更新了我的帖子并加入了新代码。自动映射似乎可以工作,现在看起来问题出在它没有从数据库中获取到底层值。很奇怪。 - Dave
@Dave,我已经阅读了你的更新问题。你说:“我在域对象中验证了PlanOct = 1,PlanNov = 2和PlanDec = 3...但是我仍然得到PlanQ1的值为0”-好的,如果这是真的,那么你需要在“PlanQ1”属性上设置断点并查看发生了什么;这个属性在自动映射转换期间不应该被调用(或者可能是,但你需要确定)-其次,“typeof(D)”是什么?我不熟悉EF,但也许它返回一个代理对象,该对象具有一个虚假的“Field3”方法,始终返回0。 - wal

2
考虑@Wal的帖子,尝试使用这个Map。
Mapper.CreateMap<IFoo, Foo2>()
    .ForMember(destination => destination.Field3, options => options.MapFrom(source => source.Field1 + source.Field2));

And

[TestMethod]
public void Map()
{
    Mapper.CreateMap<IFoo, Foo2>()
        .ForMember(destination => destination.Field3, options => options.MapFrom(source => source.Field1 + source.Field2));
    var foo1 = new Foo1() { Field1 = "field1", Field2 = "field2" };
    var foo2 = new Foo2();
    Mapper.Map(foo1, foo2);
    Assert.AreEqual("field1field2", foo2.Field3); // True
}

是的,我昨天做了这个,但这不违反所有OO封装原则吗?一个字段的计算应该只对该字段可见,客户端不应该关心实现,对吧?如果这真的是解决问题的唯一方法,我可以这样做,但我觉得这很糟糕。除非我漏掉了什么? - Dave

1

刚刚意识到这个问题没有得到答复,所以我想结束它。从技术上讲,它没有得到答复,因为出于某种原因,我无法使Automapper在这种情况下正常工作。我最终做的是返回到我的存储库中创建一些映射方法,一个用于将DAL对象的单个实例映射到IRequirement对象,另一个用于映射集合。然后在存储库中,我不再调用Mapper.Map,而是调用我的自定义映射方法,它可以完美地工作。

我仍然不明白为什么这行不通,但我遇到了其他几个类,在那些情况下,Automapper仅处理其余部分,并且我必须手动映射至少一两个字段。

我相信我还没有看到其中的一些内容。但无论如何,退回到部分或完全手动映射是我的解决方法。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接