2

I am using EF 5, and ASP.NET Web API to return JSON from my controllers. My example is for a pet service. For this question the entities are Client, Pet, and Veterinarian. The relationships are:

Client => one-to-many => Pet => one-to-one => Veterinarian

and, reversing the hierarchy:

Veterinarian => one-to-many => Pet => one-to-one => Client

In my PetsController, the GetPets action looks like this:

var pets= db.Pets
    .Where(p => p.IsActive == true)
    .Include(p => p.Client)
    .Include(p => p.Veterinarian);

return Mapper.Map<IEnumerable<Pet>, IEnumerable<PetDTO>>(pets); // Using Automapper to map to a DTO

The resulting JSON has Pets, for each Pet a Client, for each Pet a Veterinarian AND for each Veterinarian, a collection of Pets.

I understand why the collection of pets is showing up in the JSON, but it's not relevant (at least not now) to my list of Pets from the controller action. I'm looking for an elegant way to remove the collection of pets. Right now I'm using what feels like a hack:

var pets= db.Pets
    .Where(p => p.IsActive == true)
    .Include(p => p.Client)
    .Include(p => p.Veterinarian);

foreach (Pet pet in pets) {
    pet.Veterinarian.Pets = null; // Remove the Pets collection before serializing
}

return Mapper.Map<IEnumerable<Pet>, IEnumerable<PetDTO>>(pets); // Using Automapper to map 

I've tried some things with the serializer and with some IQuerable methods (many of which I am not familiar) but to no avail.

1 Answer 1

1

You can disable lazy loading to prune the graph. But that still doesn't solve the problem always. If you have a bidirectional relationship (like pet <=> Veterinarian), fetching one side of the relationship using Include will automatically build the other side too. So, after your query pet.Veterinarian.Pets will not be null even though you haven't asked it explicitly in the Include.

Using DTO's is the best way that I could think of. You have full control of the object graph.

Also, OData protocol has this mechanism already defined through $select & $expand and gives control to the client to specify the depth and breadth of the object graph that it needs. For example, for your scenario the OData url would look like, ~/Pets?$expand=Client,Veterinarian. Have you considered OData for your needs?

If you don't want to create DTO's every time, you can do it on the fly using anonymous objects. For example, for your scenario, you can do,

db.Pets.Select(p => new 
{ 
    Id = p.ID, 

    .... other pet properties you want, 

    Veterinarian = new 
    { 
        ID = p.Veterinarian.ID 
        ... other veterinarian properties you want
    },
    Client = 
    {
        ... client properties you need.
    }

});

This is just moving the mapping into the select clause though.

Sign up to request clarification or add additional context in comments.

3 Comments

Yes, but the OData $expand option is not currently supported (Returning child elements in ASP.NET WebAPI OData) which is why I'm trying a different approach. There may be times when I want a list of the pets under a particular veterinarian's care - just not when I'm asking for a list of pets as the root of the graph. And I don't want to create different DTOs for different "root object" situations..
You can do the mapping in the select query. I have updated the answer with a sample.
Also, we have just started doing $select and $expand support in web API OData. It should light up in our nightly builds very soon.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.