AutoMapper官方文档(十六)【可查询扩展】

当使用带有AutoMapper标准Mapper.Map函数的ORM(如NHibernateEntity Framework)时,您可能会注意到,当AutoMapper尝试将结果映射到目标类型时,ORM将查询图中所有对象的所有字段。

如果你的ORM暴露IQueryables,你可以使用AutoMapperQueryableExtensions辅助方法来解决这个关键的问题。

使用Entity Framework作为例子,说你有一个与实体Item有关系的实体OrderLine。 如果要使用ItemName属性将其映射到OrderLineDTO,则标准的Mapper.Map调用将导致Entity Framework查询整个OrderLineItem表。

改用这种方法。

鉴于以下实体

public class OrderLine
{
  public int Id { get; set; }
  public int OrderId { get; set; }
  public Item Item { get; set; }
  public decimal Quantity { get; set; }
}

public class Item
{
  public int Id { get; set; }
  public string Name { get; set; }
}

和下面的DTO:

public class OrderLineDTO
{
  public int Id { get; set; }
  public int OrderId { get; set; }
  public string Item { get; set; }
  public decimal Quantity { get; set; }
}

你可以像这样使用Queryable Extensions:

Mapper.Initialize(cfg =>
    cfg.CreateMap<OrderLine, OrderLineDTO>()
    .ForMember(dto => dto.Item, conf => conf.MapFrom(ol => ol.Item.Name)));

public List<OrderLineDTO> GetLinesForOrder(int orderId)
{
  using (var context = new orderEntities())
  {
    return context.OrderLines.Where(ol => ol.OrderId == orderId)
             .ProjectTo<OrderLineDTO>().ToList();
  }
}

.ProjectTo<OrderLineDTO>()将告诉AutoMapper的映射引擎向IQueryable发出一个select子句,它将通知实体框架它只需要查询Item表的Name列,就像手动将IQueryable投影到带有Select子句的OrderLineDTO

请注意,要使此功能有效,必须在Mapping中明确处理所有类型的转换。例如,您不能依赖Item类的ToString()覆盖来通知实体框架只能从Name列中进行选择,而任何数据类型的更改(如DoubleDecimal)也必须明确处理。

防止延迟加载/SELECT N + 1问题

由于由AutoMapper构建的LINQ投影由查询提供程序直接转换为SQL查询,因此映射发生在SQL/ADO.NET级别,而不会触及您的实体。 所有的数据都被急切地提取并加载到DTO中。

嵌套集合使用Select来投影子DTO

from i in db.Instructors
orderby i.LastName
select new InstructorIndexData.InstructorModel
{
    ID = i.ID,
    FirstMidName = i.FirstMidName,
    LastName = i.LastName,
    HireDate = i.HireDate,
    OfficeAssignmentLocation = i.OfficeAssignment.Location,
    Courses = i.Courses.Select(c => new InstructorIndexData.InstructorCourseModel
    {
        CourseID = c.CourseID,
        CourseTitle = c.Title
    }).ToList()
};

通过AutoMapper的这个映射将导致一个SELECT N+1的问题,因为每个子成员(课程)将被一次一个地查询,除非通过ORM指定来急切地获取。 使用LINQ投影,您的ORM不需要特殊的配置或规范。 ORM使用LINQ投影来构建所需的确切SQL查询。

自定义投影

在成员名称不对齐的情况下,或者您想要创建计算属性的情况下,可以使用MapFrom(而不是ResolveUsing)为目标成员提供自定义表达式:

Mapper.Initialize(cfg => cfg.CreateMap<Customer, CustomerDto>()
    .ForMember(d => d.FullName, opt => opt.MapFrom(c => c.FirstName + " " + c.LastName))
    .ForMember(d => d.TotalContacts, opt => opt.MapFrom(c => c.Contacts.Count()));

AutoMapper将所提供的表达式与构建的投影进行传递。 只要你的查询提供者可以解释提供的表达式,所有东西都会一直传递到数据库。

如果表达式被查询提供者(Entity FrameworkNHibernate等)拒绝,你可能需要调整你的表达式,直到找到一个被接受的表达式。

自定义类型转换

有时,您需要完全替换从源到目标类型的类型转换。 在正常的运行时映射中,这是通过ConvertUsing方法完成的。 要在LINQ投影中执行模拟,请使用ProjectUsing方法:

cfg.CreateMap<Source, Dest>().ProjectUsing(src => new Dest { Value = 10 });

ProjectUsing稍微比ConvertUsing更有限,因为只有在Expression和底层的LINQ提供程序中才能使用。

自定义目标类型构造函数

如果您的目标类型具有自定义构造函数,但不想覆盖整个映射,请使用ConstructProjectionUsing方法:

cfg.CreateMap<Source, Dest>()
    .ConstructProjectionUsing(src => new Dest(src.Value + 10));

AutoMapper将根据匹配的名称自动匹配目标构造函数参数到源成员,所以只有当AutoMapper无法正确匹配目标构造函数,或者如果在构造过程中需要额外的自定义时才使用此方法。

字符串转换

当目标成员类型是一个字符串而源成员类型不是时,AutoMapper会自动添加ToString()

public class Order {
    public OrderTypeEnum OrderType { get; set; }
}
public class OrderDto {
    public string OrderType { get; set; }
}
var orders = dbContext.Orders.ProjectTo<OrderDto>().ToList();
orders[0].OrderType.ShouldEqual("Online");

显式扩展

在一些场景中,如OData,通过一个IQueryable控制器操作返回一个通用的DTO。 没有明确的指示,AutoMapper将展开结果中的所有成员。 要控制在投影过程中展开的成员,请在配置中设置ExplicitExpansion,然后传入要显式展开的成员:

dbContext.Orders.ProjectTo<OrderDto>(
    dest => dest.Customer,
    dest => dest.LineItems);
// or string-based
dbContext.Orders.ProjectTo<OrderDto>(
    null,
    "Customer",
    "LineItems");

聚合

LINQ可以支持聚合查询,而AutoMapper支持LINQ扩展方法。 在自定义投影示例中,如果我们将TotalContacts属性重命名为ContactsCount,则AutoMapper将与Count()扩展方法匹配,并且LINQ提供程序会将计数转换为相关的子查询来聚合子记录。

如果LINQ提供者支持,AutoMapper也可以支持复杂的聚合和嵌套的限制:

cfg.CreateMap<Course, CourseModel>()
    .ForMember(m => m.EnrollmentsStartingWithA,
          opt => opt.MapFrom(c => c.Enrollments.Where(e => e.Student.LastName.StartsWith("A")).Count()));

此查询返回每个课程的学生总数,其姓氏以字母“A”开头。

参数

有时,投影需要运行时参数的值。 考虑一个需要将当前用户名作为其数据的一部分的投影。 我们可以使用MapFrom配置参数来代替使用后置映射代码:

string currentUserName = null;
cfg.CreateMap<Course, CourseModel>()
    .ForMember(m => m.CurrentUserName, opt => opt.MapFrom(src => currentUserName));

当我们投影时,我们将在运行时替换我们的参数:

dbContext.Courses.ProjectTo<CourseModel>(Config, new { currentUserName = Request.User.Name });

这是通过捕获原始表达式中闭包字段名称的名称,然后使用匿名对象/字典将值应用于参数值,然后将查询发送到查询提供程序。

支持的映射选项

并非所有的映射选项都可以被支持,因为生成的表达式必须由LINQ提供者来解释。 AutoMapper只支持LINQ提供程序支持的内容:

    MapFrom
    Ignore
    UseValue
    NullSubstitute

不支持:

    Condition
    DoNotUseDestinationValue
    SetMappingOrder
    UseDestinationValue
    ResolveUsing
    Before/AfterMap
    Custom resolvers
    Custom type converters
    您的领域对象上的任何计算属性

此外,递归或自引用目标类型不支持,因为LINQ提供程序不支持这种类型。 通常,分层关系数据模型需要通用表表达式(CTE)来正确解析递归连接。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值