Search code examples
c#entity-frameworkodataasp.net-web-api

Implementing OData with WebApi and Mapped Models


I am trying to implement OData in WebApi. I am using the repository pattern and EF5 (in the backend) which is still consistent with all the examples I have found. Here is where thing go wonky. I am trying to hide the EF generated classes behind models that are being mapped using AutoMapper in the controller. The examples I have seen seem to return whatever comes out of the repo

I don't want to apply the OData parameters (to the results that have been mapped) in the controller but in the repository to preserve the value from delayed execution. I can pass the ODataCriteria into the repository, but when I try to Appy, I get an error because it seems the options/results are typed to the IQueryable< Model > from the presentation layer not IQueryable< EF_Class >.

I saw someone else eluded to this in another post, however, it was a minor part of the post and it didn't seem to help.

Has anyone else dealt with this? I really don't want to expose the EF classes. Oh, I am using DB first.

Thanks in advance...


Solution

  • Here's some code that demonstrates your requirement.

    To achieve the result you need to ensure that the query has been executed (using ToList()). The most efficient way to do this is to add paging (bonus) and return a PageResult<> object.

    public PageResult<WebPoco> Get(ODataQueryOptions<WebPoco> queryOptions)
    {
        var data2 = DatabaseData();
    
        //Create a set of ODataQueryOptions for the internal class
        ODataModelBuilder modelBuilder = new ODataConventionModelBuilder();
        modelBuilder.EntitySet<DatabasePoco>("DatabasePoco"); 
        var context = new ODataQueryContext(
             modelBuilder.GetEdmModel(), typeof(DatabasePoco));
        var newOptions = new ODataQueryOptions<DatabasePoco>(context, Request);
    
        var t = new ODataValidationSettings() { MaxTop = 25 };
        var s = new ODataQuerySettings() { PageSize = 25 };
        newOptions.Validate(t);
        IEnumerable<DatabasePoco> results =
            (IEnumerable<DatabasePoco>)newOptions.ApplyTo(data2, s);
    
        int skip = newOptions.Skip == null ? 0 : newOptions.Skip.Value;
        int take = newOptions.Top == null ? 25 : newOptions.Top.Value;
    
        List<DatabasePoco> internalResults = results.Skip(skip).Take(take).ToList();
    
        // map from DatabasePoco to WebPoco here:
        List<WebPoco> webResults; 
    
        PageResult<WebPoco> page =
            new PageResult<WebPoco>(
                webResults, Request.GetNextPageLink(), Request.GetInlineCount());
    
        return page;
    }
    

    Here's the using statements

    using System.Web.Http;
    using System.Web.Http.OData;
    using System.Web.Http.OData.Builder;
    using System.Web.Http.OData.Query;
    

    test classes

    public class WebPoco
    {
        public int id { get; set; }
        public string name { get; set; }
        public string type { get; set; }
    }
    
    public class DatabasePoco
    {
        public int id { get; set; }
        public string name { get; set; }
        public string type { get; set; }
    }
    

    and some data for testing

    private IQueryable<DatabasePoco> DatabaseData()
    {
        return (
            new DatabasePoco[] { 
                new DatabasePoco() { id = 1, name = "one", type = "a" },
                new DatabasePoco() { id = 2, name = "two", type = "b" },
                new DatabasePoco() { id = 3, name = "three", type = "c" },
                new DatabasePoco() { id = 4, name = "four", type = "d" },
                new DatabasePoco() { id = 5, name = "five", type = "e" },
                new DatabasePoco() { id = 6, name = "six", type = "f" },
                new DatabasePoco() { id = 7, name = "seven", type = "g" },
                new DatabasePoco() { id = 8, name = "eight", type = "h" },
                new DatabasePoco() { id = 9, name = "nine", type = "i" }
            })
            .AsQueryable();
    }