Подтвердить что ты не робот

Поддержка ASP.NET WebApi OData для DTO

У меня есть объект проекта и ProjectDTO. Я пытаюсь создать метод контроллера WebAPI, который может принимать и возвращать ProjectDTO и поддерживать его OData.

Проблема в том, что я использую ORM, который может запрашивать базу данных с использованием объекта Project, а не DTO проекта. Есть ли способ, с помощью которого я могу применить фильтрацию/сортировку/подкачку из OData на основе ProjectDTO для запроса объекта Project?

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;
}
4b9b3361

Ответ 1

Что-то вроде этого (я не пытался его скомпилировать)

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;
}

Ответ 2

Я думаю, что самый простой способ сделать это - использовать 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>();

и где-то в здании IEdmModel для OData:

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 он переводится в соответствующие запросы SQL, используя таблицу, к которой относится продукт нанесены на карту. Но будьте осторожны: для более сложных случаев (например, вложенных коллекций) это может привести к неоптимальному запросу SQL.

Ответ 3

Попробуйте следующее:

    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 < > , а затем он отлично выполняет свою магию. Это связано с тем, что он использует этот конкретный тип для запроса вашего контекста коллекции /db, поэтому он должен иметь тип, который фактически хранится в коллекции/контексте, а не то, что вы пытаетесь вернуть.

Очевидно, что ваши DTO должны близко напоминать ваши объекты ORM (и они выполняются в вашем фрагменте), или это не будет работать очень хорошо с точки зрения пользователя/клиента.

Я не пытался скомпилировать вышеуказанный код, потому что у меня нет ваших классов и другой инфраструктуры, но этого должно быть достаточно, чтобы передать идею.