【问题标题】:AutoMapper Project().To() and sorting a child collectionAutoMapper Project().To() 并对子集合进行排序
【发布时间】:2013-01-30 06:06:35
【问题描述】:

我有一个使用 EF CodeFirst 和 AutoMapper 从数据库加载到 DTO 的对象图:-

public class Foo
{
  public int Id { get; set; }
  public virtual ICollection<Bar> Bars { get; set; }
}

public class Bar
{
  public int Id { get; set; }
  public int FooId { get; set; }
  public virtual Foo Foo { get; set; }

  public string Name { get; set; }
  public int SortOrder { get; set; }
}

public class FooDto
{
  public IEnumerable<BarDto> Bars { get; set; }
}

public class BarDto
{
  public string Name { get; set; }
  public int SortOrder { get; set; }
}

我的映射看起来像:-

mapper.CreateMap<Foo, FooDto>();
mapper.CreateMap<Bar, BarDto>();

到目前为止,一切都很好。我可以从我的上下文中抓取实体并很好地投射到 DTO:-

var foos = context.Foos.Project().To<FooDto>();

但是,我不能用这种方法对 Bars 进行排序,按 IQueryable 中的 SortOrder

如果我尝试:-

mapper.CreateMap<Foo, FooDto>()
  .ForMember(
    x => x.Bars
    opt => opt.MapFrom(src => src.Bars.OrderBy(x => x.SortOrder)));
mapper.CreateMap<Bar, BarDto>();
var foos = context.Foos.Project().To<FooDto>();

我得到一个例外:-

System.InvalidOperationException: Sequence contains no elements
  at System.Linq.Enumerable.First[TSource](IEnumerable`1 source)
  at AutoMapper.MappingEngine.CreateMapExpression(Type typeIn, Type typeOut)
  ...

似乎这与https://github.com/AutoMapper/AutoMapper/issues/159 有关-尽管我已经为子集合使用了复杂类型。我猜 CreateMapExpression 不支持子集合上的 OrderBy?

如果我不使用 .Project().To() 那么我可以轻松地对子集合进行排序:-

var model = context.Foos.Select(x => new FooDto()
{
  Bars = x.Bars.OrderBy(y => y.SortOrder)
});

但是我必须在任何我想使用它的地方重复映射,这违背了使用 AutoMapper 的目的。

奇怪的是:-

1) 我可以对子集合执行其他(更复杂的?)操作并将它们展平到我的父 DTO 中没问题:-

mapper.CreateMap<Foo, FooDto>()
  .ForMember(
    x => x.AllBarsHaveAName,
    opt => opt.MapFrom(src =>
      src.Bars.All(x => x.Name != null)));

2)我可以Mapper.Map&lt;FooDto&gt;(foo);在内存中就好了,它会排序条没有问题。

是否可以在 IQueryable 级别对子集合进行排序,同时仍使用 .Project().To()?

【问题讨论】:

    标签: c# automapper iqueryable


    【解决方案1】:

    最终修改了 AutoMapper 源代码以支持这种情况。希望提议的修复将被接受,但与此同时,您可以在以下位置查看详细信息:-

    https://github.com/AutoMapper/AutoMapper/pull/327

    【讨论】:

    • 拉取请求已被接受,更改现在在 automapper 3 中。
    • 我使用的是 AutoMapper 3.3.1,它允许在映射器配置中进行排序,但实际上并没有排序。数据未排序,sql profiler 在生成的 sql 中不显示任何 order by。我错过了什么?
    • 可能是自 3.0 以来引入的错误,或者可能是您的配置有问题。将MCVE 发布为新问题,在此处链接到它,我会看看?
    • 我发现了我的问题,我有几个地图配置,并且我在一个 ViewModel 地图配置上添加了 order by,但我的测试是针对不同的 ViewModel 运行的
    【解决方案2】:

    我想知道在映射期间对 Queryable 中的 Bars 进行排序是不是最好的方法?将排序放在地图中是否意味着它在不适当的时间排序?

    例如,如果您只想知道 Bars 集合是否包含 1 项,该怎么办?打电话 FooDto.Bars.Any() 仍会在数据库中产生排序。

    也许最好在 FooDTO 中有另一个属性(在映射中被忽略),如下所示:

        public IEnumerable<BarDto> SortedBars
        {
            get
            {
                if (Bars == null)
                    return null;
    
                return Bars.OrderBy(x => x.SortOrder).ToList();
            }
        }
    

    【讨论】:

    • 我认为您可能会利用 AutoMapper 映射配置中的“AfterMap”方法进行排序?
    • Colin:我不太可能拉整个 DTO 只是为了查看 FooDto.Bars.Any() - 它是针对特定视图的特定查询。
    • Richard B:我查看了 AfterMap,它会在一段时间后完成这项工作(就像 Colin 的“SortedBars”属性一样),但经过分析后,我更希望排序发生在数据库就像我做的那样 .Select(x => new FooDto...)
    • 我认为当您访问 Bars 属性时 - 例如在 Bars.OrderBy(x => x.SortOrder) 调用中 - 然后排序确实发生在数据库中。如果属性被标记为虚拟并且启用了延迟加载,至少它应该在第一次加载?
    • FooDto 不是延迟加载的实体。这是我试图将我的实体映射到的数据传输对象 - 因此希望在映射期间执行排序,以便当我调用 Project().To() 时,排序会在数据库上执行。
    猜你喜欢
    • 1970-01-01
    • 2011-04-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-10-20
    • 1970-01-01
    • 2011-03-29
    • 2015-05-27
    相关资源
    最近更新 更多