如何将OData查询与DTO映射到另一个实体?

我的问题与此问题非常相似: 如何将针对DTO的OData查询映射到EF实体? 我有一个简单的设置来测试ASP.NET Web API OData V4 $filterfunction。 我想做的是“别名”ProductDTO的一些属性以匹配Product实体的属性。 用户将使用以下请求调用ProductsController:

GET产品?$ filter = DisplayName eq’test’

产品类:

public class Product { public int Id { get; set; } public string Name { get; set; } public int Level { get; set; } public Product() { } } 

ProductDTO类:

 public class ProductDTO { public int Id { get; set; } public string DisplayName { get; set; } public int DisplayLevel { get; set; } public ProductDTO(Product product) { this.DisplayName = product.Name; this.DisplayLevel = product.Level; } } 

ProductsController:

 public class ProductsController : ApiController { public IEnumerable Get(ODataQueryOptions q) { IQueryable products = this._products.AsQueryable(); if (q.Filter != null) products = q.Filter.ApplyTo(this._products.AsQueryable(), new ODataQuerySettings()) as IQueryable; return products.Select(p => new ProductDTO(p)); } } 

当然我得到以下exception:

无法在“TestAPI.Models.Product”类型上找到名为“DisplayName”的属性

我尝试通过将以下行添加到WebApiConfig.cs来使用新引入的别名function

 public static class WebApiConfig { public static void Register(HttpConfiguration config) { … IEdmModel model = GetModel(); config.MapODataServiceRoute("*", "*", model); } private static IEdmModel GetModel() { ODataModelBuilder builder = new ODataConventionModelBuilder(); EntitySetConfiguration products = builder.EntitySet("Product"); products.EntityType.Property(p => p.Name).Name = "DisplayName"; products.EntityType.Property(p => p.Level).Name = "DisplayLevel"; return builder.GetEdmModel(); } } 

我想我错误地使用了别名function,因为抛出了与上述相同的exception。 如果我调用以下请求它可以工作,但这不是我想要实现的:

GET产品?$ filter =名称eq’test’

更新:

我同意gdoron, Get端点应该如下所示:

 public IEnumerable Get(ODataQueryOptions q) 

但是如果没有AutoMapper,这应该是可以解决的吗?

我没有使用AutoMapper就找到了解决方案。

ProductsController现在看起来像这样:

 public class ProductsController : ApiController { public IEnumerable Get(ODataQueryOptions q) { IQueryable products = this._products.AsQueryable(); IEdmModel model = GetModel(); IEdmType type = model.FindDeclaredType("TestAPI.Models.Product"); IEdmNavigationSource source = model.FindDeclaredEntitySet("Products"); ODataQueryOptionParser parser = new ODataQueryOptionParser(model, type, source, new Dictionary { { "$filter", q.Filter.RawValue } }); ODataQueryContext context = new ODataQueryContext(model, typeof(Product), q.Context.Path); FilterQueryOption filter = new FilterQueryOption(q.Filter.RawValue, context, parser); if (filter != null) products = filter.ApplyTo(products, new ODataQuerySettings()) as IQueryable; return products.Select(p => new ProductDTO(p)); } } 

WebApiConfig:

 public static class WebApiConfig { public static void Register(HttpConfiguration config) { … IEdmModel model = GetModel(); config.MapODataServiceRoute("*", "*", model); } private static IEdmModel GetModel() { ODataModelBuilder builder = new ODataConventionModelBuilder(); EntitySetConfiguration product = builder.EntitySet("Products"); product.EntityType.Name = "Product"; product.EntityType.Namespace = "TestAPI.Models"; product.EntityType.Property(p => p.Name).Name = "DisplayName"; product.EntityType.Property(p => p.Level).Name = "DisplayLevel"; return builder.GetEdmModel(); } } 

如果您决定使用DTO(我认为这绝对是一个好主意),那么使用它……
$metadata应该反映DTO的属性名称而不是EF实体,因为这是客户端获得的,这是客户端应该发送的内容。
这意味着您应该将Get端点更改为以下内容:

 public IEnumerable Get(ODataQueryOptions q) 

为了避免ProductDTOProduct之间的耦合,您可以使用AutoMapper在类之间进行映射。 此外,如果您使用AutoMapper的Project方法,您可以将方法清理为:

 public IQueryable Get(ProductDTO dto) 

您可以查看Asp.net官方演示版的版本 ,它大量使用DTO和AutoMapper,它会给你一个很好的方向,如果你现在不感兴趣,就忽略版本控制。

尝试使用AutoMapper,您需要将这些引用添加到控制器

 using AutoMapper; using AutoMapper.QueryableExtensions; 

你的方法

 [EnableQuery(AllowedQueryOptions = AllowedQueryOptions.All)] public IQueryable Get() { return dbContext.Entities.ProjectTo(); } 

在你的全球

 protected void Application_Start() { //Usually in a diff class Mapping.ConfigureDataTransferObjects(); Mapper.CreateMap(); Mapper.CreateMap(); } 

Patrick,您可以从计算的sourceValue填充目标值,例如:

 Mapper.CreateMap() .ForMember(dest => dest.InvoiceCount, opt => opt.MapFrom(src => src.Invoices.Count())); 

我从这里得到了这个例子: http : //codethug.com/2015/02/13/web-api-deep-dive-dto-transformations-and-automapper-part-5-of-6/

Arturo,如果不是复杂的映射,你可以在CreateMap上使用reverseMap来做一个单行。