ASP.NET WebApi OData support for DTOs

本文关键字:for DTOs support OData NET WebApi ASP | 更新日期: 2023-09-27 18:17:08

我有Project实体和ProjectDTO。我正在尝试创建一个WebAPI控制器方法,可以接受和返回ProjectDTOs,并使其支持OData。

问题是我正在使用ORM,可以使用项目实体而不是项目DTO查询数据库。是否有任何方法,我可以应用过滤/排序/分页从OData基于ProjectDTO项目实体查询?

public ODataQueryResult<ProjectDTO> GetProjects(ODataQueryOptions<ProjectDTO> query)
{
    var context = new ORM_Context();
    var projects = context.Projects; // IQueryable<Project>
    var projectDtos = query.ApplyTo(projectDTOs)); // <-- I want to achieve something similar here
    var projectDTOs =
        projects.Select(
            x =>
            new ProjectDTO
                {
                    Id = x.Id,
                    Name = x.Name
                });
    var projectsQueriedList = projectDtos.ToList();
    var result = new ODataQueryResult<ProjectDTO>(projectsQueriedList, totalCount);
    return result;
}

ASP.NET WebApi OData support for DTOs

像这样的(我还没有尝试编译它)

using(var dataContext = new ORM_Context())
{
    var projects = dataContext.Projects; // IQueryable<Project>
    //Create a set of ODataQueryOptions for the internal class
    ODataModelBuilder modelBuilder = new ODataConventionModelBuilder();
    modelBuilder.EntitySet<Project>("Project"); 
    var context = new ODataQueryContext(
         modelBuilder.GetEdmModel(), typeof(Project));
    var newOptions = new ODataQueryOptions<Project>(context, Request);
    var t = new ODataValidationSettings() { MaxTop = 25 };
    var s = new ODataQuerySettings() { PageSize = 25 };
    newOptions.Validate(t);
    IEnumerable<Project> internalResults =
        (IEnumerable<Project>)newOptions.ApplyTo(projects, s);
    int skip = newOptions.Skip == null ? 0 : newOptions.Skip.Value;
    int take = newOptions.Top == null ? 25 : newOptions.Top.Value;
    var projectDTOs =
            internalResults.Skip(skip).Take(take).Select(x =>
                new ProjectDTO
                    {
                        Id = x.Id,
                        Name = x.Name
                    });
    var projectsQueriedList = projectDtos.ToList();
    var result = new ODataQueryResult<ProjectDTO>(
        projectsQueriedList, totalCount);
    return result;
}

我认为最简单的方法是使用AutoMapper。因此,对于DTO

    [DataContract(Name = "Products")]
    public class ProductDTO
    {
        [Key]
        [DataMember]
        public string MyProductMember1 { get; set; }
        [DataMember]
        public string MyProductMember2 { get; set; }
        ...
    }

你应该在AutoMapper配置的某个地方写:

Mapper.CreateMap<Product, ProductDTO>();

和在某处为OData构建IEdmModel:

builder.EntitySet<ProductDTO>("Products");

,控制器的代码看起来像

public class ProductsController : ODataController
{
    [EnableQuery]
    public IHttpActionResult Get()
    {
        var products = context.Products; // IQueryable<Product>
        return Ok(products.Project().To<ProductDTO>());
    }
}

通过这种方式,您不需要直接公开ORM实体,并且可以使用OData进行过滤、分页、计数,甚至扩展嵌套集合,对于EF,它将使用Product映射到的表转换为相应的SQL请求。但是要小心:对于更复杂的情况(例如嵌套集合),它可能会导致非最佳SQL请求。

试试这个:

    public object GetProjects(ODataQueryOptions<Project> query)
    {
        var context = new ORM_Context();
        var projects = query.ApplyTo(context.Projects);
        var projectDTOs = projects.Select(
                x =>
                new ProjectDTO
                    {
                        Id = x.Id,
                        Name = x.Name
                    });
        return new
        {
            TotalCount =  Request.GetInlineCount(), //before paging
            Results = projectDTOs.ToList()
        };
    }

显然,这里最重要的事情是将正确的类型传递给ODataQueryOptions<>,然后它就能很好地执行它的魔法。这是因为它使用特定类型来查询您的集合/数据库上下文,因此它必须是实际存储在集合/上下文中的类型,而不是您试图返回的类型。

显然,您的dto应该与您的ORM对象非常相似(在您的代码片段中确实如此),否则从用户/客户端的角度来看,这将不能很好地工作。

我没有尝试编译上面的代码,因为我没有你的类和其他基础设施,但它应该足以传达这个想法。