user5326354
user5326354

Reputation:

Query with OData without exposing ORM models?

In my web api 2 project, if I want to use OData library (which looks awesome and very tempting) for queries over some properties, that would force the client side to know the exact properties of my database models. Is this a good practice? Is there a way to avoid this decouple?

For the following models:

public class LetterEntity
    {
        public int Id {get; set;}

        public string Title {get; set;}

        public string Content {get; set;}

        public string Source {get; set;}

        public DateTime SendingTime {get; set;} 

        public string AnotherWierdString {get; set;
        ...
    }

    public class LetterDTO
    {
        public int Id {get; set;}

        public string Title {get; set;}

        public string LetterContent {get; set;}

        public string Source {get; set;}

        public DateTime SendingTime {get; set;} 
    }

    public class LetterInsideFolderDTO 
    {
        public string Title {get; set;}

        public string Source {get; set;}
    }


public class LettersController : ApiController
{
    // In this approach method, I hate the fact that a LetterEntity must be used for the query.
    [HttpGet]
    [Route("api/letters")]
    [EnableQuery]
    public IQueryable<LetterInsideFolderDTO> Get(ODataQueryOptions<LetterEntity> query) 
    { 
        IQueryable<Letter> letters = db.Letters;

        var afterQuery = query.ApplyTo(letters)

        IQueryable<LetterInsideFolderDTO> dtos = afterQuery.ProjectTo<LetterInsideFolderDTO>(afterQuery)

        return dtos;
    }


    // Is there a way to do something like the following?:  
    [HttpGet]
    [Route("api/letters")]
    [EnableQuery]
    public IQueryable<LetterInsideFolderDTO> Get(ODataQueryOptions<LetterDTO> query) 
    { 
        IQueryable<Letter> letters = db.Letters;

        // Convert the query to work on the entities somehow? Should I use a mapping between LetterDTO to LetterEntity?
        // I only have a map from LetterEntity to LetterDTO
        var afterQuery = query.ApplyTo(letters)

        IQueryable<LetterInsideFolderDTO> dtos = afterQuery.ProjectTo<LetterInsideFolderDTO>(afterQuery)

        return dtos;
    }
}

Because of the fact that at the moment I take Entity model directly in the clients query, there is a strong coupling between clients and server. For example if i want to query and get all the letters that has "abc" inside the Content field, I need to route to the following:

api/letters/?$filter=contains(Content,'abc')

If tomorrow I decide to change that property from "Content" to "LetterContent" all clients code will be broken.

How can I surpass it?

Thanks!

EDIT:

Please give me a concrete example, I don't understand yet what HATEOAS are (if that helps me solve this issue), How can documentation service help me? It'll still force clients to change their code if I decide to change my EDM models?

Upvotes: 6

Views: 1971

Answers (2)

nikita
nikita

Reputation: 2817

if I want to use OData library (which looks awesome and very tempting) for queries over some properties, that would force the client side to know the exact properties of my database models. Is this a good practice?

It depends on what do you mean by "would force the client side to know the exact properties". There are two ways:

  1. Use auto-generated proxies made by datasvcutil. Indeed, that would force the client side to know the exact properties since they are hard-coded in client-side. When server side is changed client would be broken - client/server are tightly coupled. In general it's bad, but usually if you need smth to get done quickly - datasvcutil is your tool.

  2. Learn your client to read service document, so that he could dynamically decide what resources he may query. You have everything for it - service document, metadata.

Remember that OData is built on REST architecture which has a set of advantages that are achieved via set of constraints. Several of constraints are Addressability and HATEOAS.

Addressability means that each resource must have its address.

HATEOAS means that in any given moment, client, based on hypermedia in representation of current resource, must have all the information he needs to decide where to transit next.

In order to know where to transit client must

  1. Know how to find resources(URL's) in the stream of data where he may transit. Like you get data with text and URL's - client must know how to find URL's. URL may have different meanings - like several URL's for CRUD operations.

  2. Get that data with resources. In general client must know how to start querying service

First point is resolved via profiles. Profile - allows clients to learn about additional semantic associated with the resource representation (https://www.rfc-editor.org/rfc/rfc6906). Consider it OData documentation. In case of OData your client must know that data in OData is represented via Atom or Json formats. Client must know principles of constructing queries to OData service, of getting specific record and so forth.

If client calls OData root address - smth like ...OData.svc, he would get list of all resources that he can query(service document). That is how second point is resolved.

You may come further and get metadata via $metadata suffix. That would give you all properties of resources.

Upvotes: 0

Stilgar
Stilgar

Reputation: 23551

I do believe that exposing your entities directly is a bad practice in most cases. I'd recommend DTOs in almost every case. It allows you to evolve your database and business logic without breaking the API. There are some great use cases for OData for example open data initiatives where the government publishes data as it is.

I had to built an app that was essentially grids over data with all the filtering and sorting options. I wanted to use OData but I did not find a way to do queries over entities but project to DTOs so I built my own library to convert jqgrid filters to IQueryable queries - https://github.com/KodarLtd/WebApiJQGridFilters Note that I do not recommend using this code as it is not full featured library and is not documented at all. I just provide it as evidence how firmly I believe in the DTO approach.

I would like to be proven wrong so I can use OData but return DTOs for my next project.

Upvotes: 0

Related Questions