AutoMapper ProjectTo继承问题

5

如何使ProjectTo正确地映射不同的派生类型,而不将它们向下转换为基础类型?

使用Mapper.Map可以正常工作,但使用ProjectTo无法正常工作。

源类:(EntityFramework模型)

public class FailureAlertEntity : AlertEntity
{
    public FailureAlertEntity(int id, string description) : base(id)
    {
        Description = description;
    }

    public string Description { get; set; }
}

public class AlertEntity
{
    public AlertEntity(int id)
    {
        ID = id;
    }

    public int ID { get; set; }
}

目标类(DTO模型):

public class FailureAlert : Alert
{
    public FailureAlert(int id, string description) : base(id)
    {
        Description = description;
    }

    public string Description { get; set; }
}

public class Alert
{
    public Alert(int id)
    {
        ID = id;
    }

    public int ID { get; set; }
}

有多个从AlertEntity和Alert派生的类,我想在不将派生类型降为其基础类型的情况下映射这两种类型之间。

配置:

Mapper.Initialize(cfg => {
    cfg.CreateMap<AlertEntity, Alert>()
          .Include<FailureAlertEntity, FailureAlert>();
    cfg.CreateMap<FailureAlertEntity, FailureAlert>();
});

测试代码:

var entities = new List<AlertEntity>()
{
    new FailureAlertEntity(1, "foo"),
    new FailureAlertEntity(2, "bar")
};

var alerts = entities.AsQueryable().ProjectTo<Alert>();

结果:

nope

ProjectTo似乎不考虑列表中项目的类型,而是将它们强制转换为列表本身的类型。如果列表的类型是FailureAlertEntity,则显然可以工作,但列表可能包含其他派生自AlertEntity的类型。

如果我想使用Mapper.Map对对象执行相同的操作,那么完全没问题:

var faEntity = new FailureAlertEntity(1, "asd");
var dto = Mapper.Map<Alert>(faEntity);

结果:

yep

我该如何使用ProjectTo映射类型,就像Mapper.Map一样?我以为它们都会使用相同的配置。

1个回答

9
你无法这样做,但有一个很好的原因。 ProjectToIQueryable 的扩展,而不是 IEnumerable。那么什么是 IQueryable

提供针对特定数据源评估查询的功能。

IQueryable 接口旨在由查询提供程序实现。

因此,IQueryable 代表通过查询提供程序(如 Entity Framework)查询数据源(如 SQL Server 数据库)。ProjectTo 将构建一个 Select 表达式,它实际上不会在内存中投影任何内容。因此,它将大致执行以下操作:
entities.AsQueryable().Select(c => new Alert() {Property = c.Property})

Select 中的内容是表达式(而不是函数),查询提供程序将其转换为数据源查询(例如,将 SELECT Property from ... 转换为 SQL Server)。此时,查询尚未执行,甚至不能确定它将返回哪些子类型的元素(如果有的话)。您只能选择一组列,无法说“如果 Alert 是 FailureAlert,则给我这些列,但如果是 AnotherAlert,则给我另一组列”,这种表达式无法转换为数据存储查询。例如,Entity Framework 可以处理其实体的继承(如表格类型继承),但是它具有更多信息(例如用于处理继承的表格)比 Automapper 更强大。Automapper 只有要映射的类型和要映射到的类型。

因此,考虑到上述情况,Automapper 并没有其他可行的方法。

现在,在您的示例中,您正在使用“假”的 IQueryable。如果在实际应用程序中是这样的情况,请使用 IEnumerable 并进行以下操作:

var alerts = Mapper.Map<List<Alert>>(entities);

或者

var alerts = entities.Select(Mapper.Map<Alert>).ToList();

如果你有真正的IQueryable,例如可以真正返回AlertEntity及其子类型列表的Entity Framework查询 - 你需要在使用automapper映射之前将查询实体化(当然这会将具有所有属性的实体实现):

var alerts = yourQuery.AsEnumerable().Select(Mapper.Map<Alert>).ToList();

谢谢Evk,这很有道理,其实我应该知道的...我想我以为AutoMapper的ProjectTo有魔力。我会把这个标记为答案! - Shahin Dohan
@ShahinDohan 我也遇到了完全相同的问题。但这似乎并不是所有情况的解决方案。我尝试使用AutoMapper在业务层和持久层之间进行映射。业务层代码只知道业务层对象。一个简单的.Select(Mapper.Map<Alert>).可以工作,但没有办法进行过滤而不收集数据库中的所有项。我想使用类似于yourQuery.ProjectTo<Alert>().Where(x => x.Prop == ...).ToList()的东西。它在SQL中完美地转换为WHERE,但不会创建正确的实例。如何解决这个要求? - Sebastian Schumann

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