0

I'm creating an ASP.NET Web API with Entity Framework following the Code First Approach. I have a very simple model, which consists of a one-to-many relationship between a House and a Room:

House.cs

public class House
{
    public int ID { get; set; }

    public string Address { get; set; }

    public virtual ICollection<Room> Rooms { get; set; }

    public House()
    {
        Rooms = new List<Room>();
    }
}

Room.cs

public class Room
{
    public int ID { get; set; }

    public string Name { get; set; }

    public int HouseID { get; set; }

    public virtual House House { get; set; }
}

I have purposely added the virtual keyword to the Rooms attribute in the House class and to the House attribute in the Room class because I want to be able to see all the Rooms that a House has when I consult a House, and I want to see the House of a Room when I consult said Room (Lazy Loading).

However, when I make a GET request to my controller, the serialization of the entities fails, and it returns a XML (or JSON) full of errors:

api/houses (with Lazy Loading)

<Error>
    <Message>An error has occurred.</Message>
    <ExceptionMessage>
    The 'ObjectContent`1' type failed to serialize the response body for content type 'application/xml; charset=utf-8'.
    </ExceptionMessage>
    <ExceptionType>System.InvalidOperationException</ExceptionType>
    <StackTrace/>
    <InnerException>
    <Message>An error has occurred.</Message>
    <ExceptionMessage>
        Type 'System.Data.Entity.DynamicProxies.House_49DC0BEAA9C67FACDA33CEE81852FA2D80C04F62C6838F92ACD2A490CECF86B5' with data contract name 'House_49DC0BEAA9C67FACDA33CEE81852FA2D80C04F62C6838F92ACD2A490CECF86B5:http://schemas.datacontract.org/2004/07/System.Data.Entity.DynamicProxies' is not expected. Consider using a DataContractResolver if you are using DataContractSerializer or add any types not known statically to the list of known types - for example, by using the KnownTypeAttribute attribute or by adding them to the list of known types passed to the serializer.
    </ExceptionMessage>
    ...
</Error>

If I "disable" Lazy Loading by removing the virtual keyword from the attributes, the entities are serialized correctly, but I no longer have access to the associated entities. If I make a GET request to fetch all the houses, I no longer have access to the House's Rooms:

api/houses (without Lazy Loading)

<ArrayOfHouse xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/REM.Models">
    <House>
        <Address>Boaty McBoatface Street 123</Address>
            <ID>1</ID>
            <Rooms/>
    </House>
</ArrayOfHouse>

I tried using Eager Loading to solve my problem by removing the virtual keywords and explicitly loading the Rooms in the GET method of the Houses controller like this:

HousesController.cs

public IQueryable<House> GetHouses()
{
    return db.Houses.Include(r => r.Rooms);
}

but it still failed to serialize the XML/JSON, showing me the exact same error message as when I tried to make a GET request with Lazy Loading.

I understand that all these problems may have something to do with circular dependency that is probably occurring between my two entities, but I have no idea how to solve it. So, to summarize all of this in one question:

Is there a way to make a request that will return all Houses with their respective Rooms, (without using auxiliary POCO (DTO))?

João Paiva
  • 1,937
  • 3
  • 19
  • 41
  • See fro instance [this](http://stackoverflow.com/questions/5588143/ef-4-1-code-first-json-circular-reference-serialization-error) thread. Basically you need to disable EF proxies with `db.Configuration.ProxyCreationEnabled = false;`. – Ivan Stoev Jul 23 '16 at 17:03
  • @IvanStoev if I disable EF proxies, I no longer have access to the Rooms when I make a request to get all Houses. – João Paiva Jul 23 '16 at 17:13
  • this is most likely because EF has to lazily load related entities, while they are meant to be serialized. turn off lazy loading and use eager loading (with .Include().ToList()) instead. – DevilSuichiro Jul 23 '16 at 17:35

1 Answers1

1

So to solve my problem I disabled lazy loading by disabling EF proxies:

ApplicationDbContext.cs

public ApplicationDbContext() : base("name=ApplicationDbContext")
{           
       Configuration.ProxyCreationEnabled = false;
}

And I eagerly loaded the Rooms in my GET method in the Houses controller:

HousesController.cs

public IQueryable<House> GetHouses()
{            
    return db.Houses.Include(r => r.Rooms);
}

Which returned the XML that I wanted:

<ArrayOfHouse xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/Test.Models">
    <House>
        <Address>Boaty McBoatface Street 123</Address>
        <ID>1</ID>
        <Rooms>
            <Room>
                <ID>1</ID>
                <Name>Room</Name>
            </Room>
            <Room>
                <ID>2</ID>
                <Name>Kitchen</Name>
            </Room>    
        </Rooms>
    </House>
</ArrayOfHouse>

Update:

I have found another solution that achieves my initial goal. Instead of disabling EF proxies you can remove the virtual keyword from your properties and then you only have to explicitly include the Entities that you want in the GET method, like this:

House.cs

public class House
{
    public int ID { get; set; }

    public string Address { get; set; }

    public ICollection<Room> Rooms { get; set; }

    public House()
    {
        Rooms = new List<Room>();
    }
}

Room.cs

public class Room
{
    public int ID { get; set; }

    public string Name { get; set; }
 }

Following this approach, I have removed the navigation property from Room because that was aggravating the serialization issue since it was causing a circular dependency between the two entities.

HouseController.cs

public IQueryable<House> GetHouses()
{
    return db.Houses.Include(r => r.Rooms);
}
João Paiva
  • 1,937
  • 3
  • 19
  • 41
  • Instead of disabling proxy generation, why not just disable lazy loading with Configuration.LazyLoadingEnabled = false? – Milos Mrdovic Jul 23 '16 at 18:02
  • @MilosMrdovic I tried that, but it didn't work, got the same exact error. – João Paiva Jul 23 '16 at 19:23
  • Have you tried to return the List instead of IQueryable by calling ToList() on your query? I suspect that the context is being disposed of before the collection gets to the serializer... – Milos Mrdovic Jul 23 '16 at 20:19
  • @MilosMrdovic Since you suggested it, I did give it a go, but I still got the same error as before. – João Paiva Jul 24 '16 at 11:50