Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I map an OData query against a DTO to another entity?

My question is very similar to this one: How do I map an OData query against a DTO to an EF entity? I have a simple setup to test the ASP.NET Web API OData V4 $filter functionality. What I would like to do is to “alias” some properties of the ProductDTO to match the properties of Product entity. The user will call the ProductsController for example with the following request:

GET products?$filter=DisplayName eq ‘test’

The Product class:

public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int Level { get; set; }
    public Product()
    { }
}

The ProductDTO class:

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

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

Of course I’m getting the following exception:

Could not find a property named 'DisplayName' on type 'TestAPI.Models.Product'

I tried to use the newly introduced aliasing feature by adding the following lines to the 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();
    }
}

I suppose that I'm using the aliasing feature incorrectly, because the same exception as described above is thrown. If I invoke the following request it works, but this is not what I'm trying to achieve:

GET products?$filter=Name eq ‘test’

Update:

I agree with gdoron, the Get endpoint should look like this:

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

But this should be solvable without AutoMapper?

like image 406
niklr Avatar asked Oct 29 '14 10:10

niklr


People also ask

How do I enable OData query option globally?

To use OData query options, you must enable them explicitly. You can enable them globally for the entire application, or enable them for specific controllers or specific actions. public static void Register(HttpConfiguration config) { // ... config.

What is $select in OData?

The $select option specifies a subset of properties to include in the response body. For example, to get only the name and price of each product, use the following query: Console Copy. GET http://localhost/odata/Products?$select=Price,Name.

What is DTO in Entity Framework?

To accomplish this, you can define a data transfer object (DTO). A DTO is an object that defines how the data will be sent over the network. Let's see how that works with the Book entity. In the Models folder, add two DTO classes: C# Copy.


1 Answers

I found a solution without using AutoMapper.

The ProductsController now looks like this:

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

The 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();
    }
}
like image 119
niklr Avatar answered Oct 24 '22 20:10

niklr