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

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何将OData查询与DTO映射到另一个实体?相关的知识,希望对你有一定的参考价值。

我的问题非常类似于:How do I map an OData query against a DTO to an EF entity?我有一个简单的设置来测试ASP.NET Web API OData V4 $过滤器功能。我想做的是“别名”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<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产品?$ filter =名称eq'test'

更新:

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

public IEnumerable<ProductDTO> Get(ODataQueryOptions<ProductDTO> q)

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

答案

我找到了一个没有使用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();
    }
}
另一答案

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

public IEnumerable<ProductDTO> Get(ODataQueryOptions<ProductDTO> q)

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

public IQueryable<ProductDTO> Get(ProductDTO dto)

你可以检查Asp.net official demo for versioning,它大量使用DTO和AutoMapper,它会给你一个很好的方向,如果你现在不感兴趣,只需忽略版本控制。

另一答案

尝试使用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>();
}
另一答案

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来做一个单行。

以上是关于如何将OData查询与DTO映射到另一个实体?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 AutoMapper 将 json 请求 dto 中的 OData 枚举字符串映射到实体枚举属性

如何获取 OData 可查询 Web API 端点过滤器并从 DTO 对象映射它?

将 IQueryable where 子句从 DTO 映射到实体

如何将DTO映射到多个实体?

对 DTO 的 ASP.NET WebApi OData 支持

将 DTO 映射到后端实体