阻止AutoMapper将字符串视为LINQ ProjectTo中的集合

本文关键字:ProjectTo 集合 LINQ AutoMapper 字符串 阻止 | 更新日期: 2023-09-27 17:59:10

我有以下一组类要映射(仅在一个方向上,从Data*Api*):

// Top level
public class DataEntity
{
  public NestedDataEntity Nested { get; set; }
  // ... other primitive/complex properties
}
public class ApiEntity
{
  public NestedApiEntity Nested { get; set; }
  // ... other primitive/complex properties
}
// Nested level
public class NestedDataEntity
{
  public string Items { get; set; }
}
public class NestedApiEntity
{
  public IEnumerable<ApiSubItem> Items { get; set; }
}
public class ApiSubItem
{
  // there are properties here. Not needed for the sake of example
}

映射在Profile中配置,如下代码位所示:

// mapping profile 
public class MyCustomProfile : Profile
{
  public MyCustomProfile()
  {
    CreateMap<DataEntity, ApiEntity>();
    CreateMap<NestedDataEntity, NestedApiEntity>();
    CreateMap<string, IEnumerable<ApiSubItem>>()
      .ConvertUsing<TextToSubItemsConverter>();
  }
}
// type converter definition
public class TextToSubItemsConverter :
  ITypeConverter<string, IEnumerable<ApiSubItem>>
{
  public IEnumerable<ApiSubItem> Convert(
        string dataItems, IEnumerable<ApiSubItem> apiItems, ResolutionContext context)
  {
    // actually, deserialize & return an ApiSubItem[]
    // here just return some fixed array
    return new ApiSubItem[]
    {
      new ApiSubItem(),
      new ApiSubItem(),
      new ApiSubItem(),
    };
  }
}
// Main
public class Program
{
  public static void Main(string[] args)
  {
    Mapper.Initialize(cfg =>
    {
      cfg.AddProfile<MyCustomProfile>();
    });
    Mapper.AssertConfigurationIsValid();
    DataEntity dataEntity = new DataEntity()
    {
      Nested = new NestedDataEntity()
      {
        Items = "Ignored text",
      },
    };
    // This maps ok, no issues
    ApiEntity apiEntity = Mapper.Map<DataEntity, ApiEntity>(dataEntity);
    IQueryable<DataEntity> dataEntities = new[] { dataEntity }.AsQueryable();
    // This exposes the System.Char to ApiSubItem issue
    ApiEntity apiEntityProjected = dataEntities.ProjectTo<ApiEntity>().First();
  }
}

映射配置在启动时通过了初始验证,但当需要实际映射时,我会得到异常:

System.InvalidOperationException:从System.Char到my.whatever.namespace.ApiSubItem.缺少映射。使用Mapper.CreateMap.创建

如果我完全省略了stringIEnumerable<ApiSubItem>之间的配置,则初始验证会抱怨相同的上下文:

上下文:从类型System.Char到my.whatever.namespace.ApiSubItem 的映射

尽管添加时,它似乎没有被AutoMapper拾取。

映射发生在静态上下文中,通过DbSet<DataEntity>上的LINQ查询之上的ProjectTo<ApiEntity>()调用。我已经检查过转换器不需要任何依赖项,以防万一。

AutoMapper是5.0.2,运行在ASP.NetCore1RTM下的MVC API web应用程序中。

我在SO上检查过类似的问题,但没有运气。有人知道如何让这种场景发挥作用吗?我想我不是第一个尝试(自动)从string映射到集合的人。TA

EDIT在示例中添加了失败和非失败情况

EDIT 2经过进一步搜索,有人建议忽略该字段并在AfterMap()中执行映射。使用此配置文件,不会引发异常,但生成的Items字段为空:

CreateMap<DataEntity, ApiEntity>();
CreateMap<NestedDataEntity, NestedApiEntity>()
    .ForMember(api => api.Items, options => options.Ignore())
    .AfterMap((data, api) =>
    {
        api.Items = Mapper.Map<IEnumerable<ApiSubItem>>(data.Items);
    });
CreateMap<string, IEnumerable<ApiSubItem>>()
    .ConvertUsing<TextToSubItemsConverter>();

编辑3改写问题标题,使其更具体地用于投影

阻止AutoMapper将字符串视为LINQ ProjectTo中的集合

我想实际的答案是沿着"投影和转换器/解析器不能很好地一起工作";。来自AutoMapper Queryable.扩展wiki:

并非所有映射选项都可以支持,因为生成的表达式必须由LINQ提供程序进行解释。[…]

不支持:

  • […]
  • 在映射之前/之后
  • 自定义解析器
  • 自定义类型转换器

编辑解决方法:
在这个特定的场景中,我后来发现拥有一个中间DTO实体是有用的,它可以获取更多的字段,而最终API实体不需要这些字段,但业务逻辑仍然需要这些字段。

在这样的DTO实体中,我像在DataEntity中一样保留了string字段,所以我可以使用投影,并让AutoMapper/LinqToSQL从DB中只获取所需的字段。

然后,在DTO实体和内存中已经存在的API实体之间,我可以应用第二个映射,该映射还利用了string ==> IEnumerable<ApiSubItem>映射的自定义转换器。HTH