Web API Queryable - 如何应用AutoMapper?

时间:2013-01-23 16:37:37

标签: asp.net .net asp.net-mvc asp.net-web-api

我有一个像OData可查询属性一样的简单WebApi方法。

    [Queryable]
    public virtual IQueryable<PersonDto> Get()
    {
        return uow.Person().GetAll()); // Currently returns Person instead of PersonD
    }

我想要做的是在WebAPI将结果转换为JSON之前,使用AutoMapper将查询结果从Person类型转换为PersonDto类型。

有人知道我怎么做吗?我知道,我可以在GetAll()调用之后应用Mapper.Map,然后转换回IQueryable,但这会导致在应用OData过滤器之前返回并映射整个表(不好!)。

看起来这个问题ASP.NET Web API return queryable DTOs?涵盖了相同的问题(请参阅第二个响应以获得更好的答案),其中建议使用自定义MediaTypeFormatter在链的末尾使用AutoMapper,但是我没有想法如何根据我见过的例子做到这一点。

感谢您的任何帮助!

- 更多信息

我已经查看了IQueryable的源代码,但不幸的是,我看不到为此目的使用代码的任何方法。我已经设法编写了一个看起来有效的附加过滤器,但它并不一定不优雅。

public class PersonToPersonDtoConvertAttribute : ActionFilterAttribute
{
    public override void OnActionExecuted(System.Web.Http.Filters.HttpActionExecutedContext actionExecutedContext)
    {
        HttpResponseMessage response = actionExecutedContext.Response;

        if (response != null)
        {
            ObjectContent responseContent = response.Content as ObjectContent;
            var query = (responseContent.Value as IQueryable<Student>).ToList();
            response.Content = new ObjectContent<IEnumerable<StudentResource>>(query.ToList().Select(Mapper.Map<Person, PersonDto>), responseContent.Formatter);
        }
    }
}

然后我装饰了像

这样的动作
    [Queryable]
    [PersonToPersonDtoConvert]
    public IQueryable<Person> Get()
    {
        return uow.GetRepo<IRepository<Person>>().GetAll();
    }

4 个答案:

答案 0 :(得分:26)

有一个更好的解决方案。试试这个:

public virtual IQueryable<PersonDto> Get(ODataQueryOptions<Person> query)
{
    var people = query.ApplyTo(uow.Person().GetAll());
    return ConvertToDtos(people);
}

这将确保查询在Person而不是PersonDTO上运行。如果您希望通过属性而不是代码进行转换,您仍然希望实现类似于您提供的操作过滤器。

答案 1 :(得分:24)

使用AutoMapper&#39; Queryable Extensions

首先,定义映射。

// Old AutoMapper API
// Mapper.CreateMap<Person, PersonDto>();

// Current AutoMapper API
Mapper.Initialize(cfg => 
   cfg.CreateMap<Person, PersonDto>()
);

然后你可以使用这样的东西:

[EnableQuery]
public IQueryable<PersonDto> Get() {
    // Old AutoMapper API
    // return this.dbContext.Persons.Project().To<PersonDto>();

    // New AutoMapper API
    return this.dbContext.Persons.ProjectTo<PersonDto>();
}

编辑04/2019:已更新以反映当前的AutoMapper API。

答案 2 :(得分:16)

恕我直言,接受的解决方案是不正确的。一般来说,如果您的服务使用的是DTO,则您不希望将基础实体(Person)公开给该服务。为什么要查询Person模型并返回PersonDTO个对象?

由于您已经在使用它,因此Automapper有Queryable Extensions,它允许您仅公开您的DTO并将过滤应用于数据源的基础类型。例如:

public IQueryable<PersonDto> Get(ODataQueryOptions<PersonDto> options) {
    Mapper.CreateMap<Person, PersonDto>();
    var persons = _personRepository.GetPersonsAsQueryable();
    var personsDTOs = persons.Project().To<PersonDto>();  // magic happens here...

    return options.ApplyTo(personsDTOs);
}

关于急切加载导航属性......

@philreed:我无法在评论中做出正确的回应,所以我在这里添加了它。有一篇关于如何做到这一点的帖子here但我今天得到了403。希望这是暂时的。

基本上,您检查导航属性的Select和Expand子句。如果它存在,那么您告诉EF通过IQueryable<T> Include扩展方法急切加载。

控制器

public IQueryable<MyDto> GetMyDtos(ODataQueryOptions<MyDto> options)
{   
  var eagerlyLoad = options.IsNavigationPropertyExpected(t => t.MyNavProperty);
  var queryable = _myDtoService.GetMyDtos(eagerlyLoad);

  // _myDtoService will eagerly load to prevent select N+1 problems
  // return (eagerlyLoad) ? efResults.Include(t => t.MyNavProperty) : efResults;

  return queryable;
}

扩展方法

public static class ODataQueryOptionsExtensions
{
  public static bool IsNavigationPropertyExpected<TSource, TKey>(this ODataQueryOptions<TSource> source, Expression<Func<TSource, TKey>> keySelector)
  {
    if (source == null) { throw new ArgumentNullException("source"); }
    if (keySelector == null) { throw new ArgumentNullException("keySelector"); }

    var returnValue = false;
    var propertyName = (keySelector.Body as MemberExpression ?? ((UnaryExpression)keySelector.Body).Operand as MemberExpression).Member.Name;
    var expandProperties = source.SelectExpand == null || string.IsNullOrWhiteSpace(source.SelectExpand.RawExpand) ? new List<string>().ToArray() : source.SelectExpand.RawExpand.Split(',');
    var selectProperties = source.SelectExpand == null || string.IsNullOrWhiteSpace(source.SelectExpand.RawSelect) ? new List<string>().ToArray() : source.SelectExpand.RawSelect.Split(',');

    returnValue = returnValue ^ expandProperties.Contains<string>(propertyName);
    returnValue = returnValue ^ selectProperties.Contains<string>(propertyName);

    return returnValue;
  }
}

答案 3 :(得分:-2)

我对拟议的解决方案有几点意见/评论 1.如果在查询上下文中使用Person并返回PersonDTO,那么MVVM的整个概念就会消失。我猜它变得紧密耦合了。我宁愿一直使用Person,或者如果你不想返回子记录,那么在我看来Automapper有一个扩展“Project”,尝试使用它。 2.使用这种方法,$ inlinepagecount查询选项将无法按预期运行,因为它将获得PersonDTO而非Person的计数。 (解决这个问题的一种方法是使用PageResult并自己设置属性。) 问候。