我有一个像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();
}
答案 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并自己设置属性。) 问候。