我的问题与此问题非常相似:How do I map an OData query against a DTO to an EF entity? 我有一个简单的设置来测试ASP.NET Web API OData V4 $过滤器功能。我想做的是“别名”ProductDTO的一些属性以匹配Product实体的属性。用户将使用以下请求调用ProductsController:
获取产品?$ 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<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));
}
}
我当然得到以下例外:
无法找到名为&#39; DisplayName&#39;的属性在类型&#39; TestAPI.Models.Product&#39;
我尝试使用新引入的别名功能,将以下行添加到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产品?$ filter =名称eq'test'
更新
我同意gdoron,Get
端点应如下所示:
public IEnumerable<ProductDTO> Get(ODataQueryOptions<ProductDTO> q)
但是如果没有AutoMapper,这应该是可以解决的吗?
答案 0 :(得分:14)
我找到了一个不使用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();
}
}
答案 1 :(得分:5)
如果您决定使用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,它会为你提供一个好的方向,如果你现在不感兴趣,就忽略版本控制。
答案 2 :(得分:2)
尝试使用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>();
}
答案 3 :(得分:0)
Patrick,您可以从计算的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来做一个单行。
答案 4 :(得分:0)
对我来说,解决方案只是将DTO添加到EDM配置(v4):
safeAreaLayoutGuide