如何序列化与Entity Framework具有一对多关系的实体?

How to serialize entities that have a one-to-many relationship with Entity Framework?

我正在使用 Entity Framework 创建一个 ASP.NET Web API,遵循 Code First 方法。我有一个非常简单的模型,它由一个房子和一个房间之间的一对多关系组成:

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

我特意将 virtual 关键字添加到 House class 的 Rooms 属性和 Room class 的 House 属性中,因为我希望能够看到所有的房间当我咨询一个House时House有,而当我咨询said Room时我想看到一个房间的House(延迟加载)。

但是,当我向我的控制器发出 GET 请求时,实体的序列化失败,并且 return 是一个充满错误的 XML(或 JSON):

api/houses(延迟加载)

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

如果我通过从属性中删除 virtual 关键字来 "disable" 延迟加载,则实体会正确序列化,但我无法再访问关联的实体。如果我发出获取所有房屋的 GET 请求,我将无法再访问房屋的房间:

api/houses(没有延迟加载)

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

我尝试使用预加载来解决我的问题,方法是删除虚拟关键字并在 Houses 控制器的 GET 方法中显式加载房间,如下所示:

HousesController.cs

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

但它仍然无法序列化 XML/JSON,向我展示了与我尝试使用延迟加载发出 GET 请求时完全相同的错误消息。

我知道所有这些问题可能与我的两个实体之间可能发生的循环依赖有关,但我不知道如何解决它。因此,将所有这些总结为一个问题:

有没有办法提出一个请求,return 所有房屋及其各自的房间,(不使用辅助 POCO (DTO))?

所以为了解决我的问题,我通过禁用 EF 代理来禁用延迟加载:

ApplicationDbContext.cs

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

我急切地在房屋控制器的 GET 方法中加载了房间:

HousesController.cs

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

返回了我想要的 XML:

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

更新:

我找到了另一个解决方案来实现我最初的目标。您可以从属性中删除 virtual 关键字,而不是禁用 EF 代理,然后您只需在 GET 方法中明确包含所需的实体,如下所示:

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

按照这种方法,我从 Room 中删除了导航 属性,因为这加剧了序列化问题,因为它导致了两个实体之间的循环依赖。

HouseController.cs

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