如何将针对 DTO 的 OData 查询映射到另一个实体?
Posted
技术标签:
【中文标题】如何将针对 DTO 的 OData 查询映射到另一个实体?【英文标题】:How do I map an OData query against a DTO to another entity? 【发布时间】:2014-12-25 01:11:05 【问题描述】:我的问题和这个问题很相似:How do I map an OData query against a DTO to an EF entity? 我有一个简单的设置来测试 ASP.NET Web API OData V4 $filter 功能。我想做的是给 ProductDTO 的一些属性“别名”以匹配 Product 实体的属性。例如,用户将通过以下请求调用 ProductsController:
GET products?$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;
产品控制器:
public class ProductsController : ApiController
public IEnumerable<ProductDTO> Get(ODataQueryOptions<Product> q)
IQueryable<Product> products = this._products.AsQueryable();
if (q.Filter != null) products = q.Filter.ApplyTo(this._products.AsQueryable(), new ODataQuerySettings()) as IQueryable<Product>;
return products.Select(p => new ProductDTO(p));
当然,我遇到了以下异常:
在“TestAPI.Models.Product”类型上找不到名为“DisplayName”的属性
我尝试通过将以下行添加到 WebApiConfig.cs 来使用新引入的别名功能
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> products = builder.EntitySet<Product>("Product");
products.EntityType.Property(p => p.Name).Name = "DisplayName";
products.EntityType.Property(p => p.Level).Name = "DisplayLevel";
return builder.GetEdmModel();
我想我错误地使用了别名功能,因为引发了与上述相同的异常。如果我调用以下请求,它会起作用,但这不是我想要实现的目标:
GET products?$filter=Name eq ‘test’
更新:
我同意 gdoron 的观点,Get
端点应该是这样的:
public IEnumerable<ProductDTO> Get(ODataQueryOptions<ProductDTO> q)
但这应该可以在没有 AutoMapper 的情况下解决吗?
【问题讨论】:
【参考方案1】:我找到了一个不使用 AutoMapper 的解决方案。
ProductsController 现在看起来像这样:
public class ProductsController : ApiController
public IEnumerable<ProductDTO> Get(ODataQueryOptions<ProductDTO> q)
IQueryable<Product> 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<string, string> "$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<Product>;
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> product = builder.EntitySet<Product>("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();
【讨论】:
聪明!为什么使用自动映射器,而你可以用 C# 完全做到这一点。也可以使用可查询和表达式树来完成。 ODataQueryOptions 仅支持 $filter, $orderby, $top, $skip 。 如果有人需要new ODataQueryContext(model, typeof(TypeName), q.Context.Path)
也可以new ODataQueryContext(model, typeof(TypeName), null)
嗨@niklr,你能回答这个问题吗?谢谢,***.com/questions/62650392/…【参考方案2】:
如果您决定要使用 DTO(我认为这绝对是个好主意),那么就使用它...$metadata
应该反映 DTO 的属性名称,而不是 EF 实体的名称,因为这是客户端得到的,也是客户端应该发送的。
这意味着您应该将Get
端点更改为如下内容:
public IEnumerable<ProductDTO> Get(ODataQueryOptions<ProductDTO> q)
为了避免ProductDTO
和Product
之间的耦合,您可以使用AutoMapper 为您在类之间进行映射。此外,如果您使用 AutoMapper 的 Project
方法,您可以将您的方法清理为类似:
public IQueryable<ProductDTO> Get(ProductDTO dto)
您可以查看Asp.net official demo for versioning,它大量使用 DTO 和 AutoMapper,它会给您一个很好的方向,如果您现在不感兴趣,请忽略版本控制。
【讨论】:
这些示例很好,但遗憾的是我看不到转换计算字段(未存储在数据库中但在调用时在内存中计算的字段)。我宁愿不在视图模型中复制我的代码来处理业务状态逻辑。不过,我想向用户公开其中一些计算字段。有什么提示可以让我使用 AutoMapper?【参考方案3】:尝试使用 AutoMapper,您需要将这些引用添加到您的控制器
using AutoMapper;
using AutoMapper.QueryableExtensions;
你的方法
[EnableQuery(AllowedQueryOptions = AllowedQueryOptions.All)]
public IQueryable<ObjectDTO> Get()
return dbContext.Entities.ProjectTo<ObjectDTO>();
在你的全球
protected void Application_Start()
//Usually in a diff class Mapping.ConfigureDataTransferObjects();
Mapper.CreateMap<MyEntity, ObjectDTO>();
Mapper.CreateMap<ObjectDTO, MyEntity>();
【讨论】:
【参考方案4】:对我来说,解决方案只是将 DTO 添加到 EDM 配置 (v4):
edmBuilder.EntitySet<Contact>("Contacts");
edmBuilder.EntityType<ContactDto>();
【讨论】:
我希望只公开 $metadata 端点中的 DTO。使用 EntityTypePatrick,您可以从计算的 sourceValue 中填充目标值,例如:
Mapper.CreateMap<Customer, CustomerDTO>()
.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 来做单线。
【讨论】:
以上是关于如何将针对 DTO 的 OData 查询映射到另一个实体?的主要内容,如果未能解决你的问题,请参考以下文章
如何获取 OData 可查询 Web API 端点过滤器并从 DTO 对象映射它?
如何使用 AutoMapper 将 json 请求 dto 中的 OData 枚举字符串映射到实体枚举属性
如何使用dto和C#窗口形式将信息从一种形式传输到另一种形式?