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

时间:2016-07-23 16:45:09

标签: entity-framework serialization asp.net-web-api circular-dependency

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

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

我故意将虚拟关键字添加到House类的Rooms属性和Room类中的House属性,因为我希望能够在查看House时看到House所有的房间,而我当我咨询房间(懒惰装载)时,我想看房间的房子。

但是,当我向控制器发出GET请求时,实体的序列化失败,并返回一个充满错误的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>

如果我&#34;禁用&#34;通过从属性中删除虚拟关键字进行延迟加载,实体已正确序列化,但我无法再访问关联的实体。如果我要求获取所有房屋的GET请求,我将无法再访问House's Rooms:

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>

我尝试使用Eager Loading来解决我的问题,方法是删除虚拟关键字并在Houses控制器的GET方法中明确加载Rooms,如下所示:

HousesController.cs

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

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

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

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

1 个答案:

答案 0 :(得分:1)

因此,要解决我的问题,我通过禁用EF代理来禁用延迟加载:

ApplicationDbContext.cs

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

我急切地在Houses控制器中加载了我的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>

<强>更新

我找到了另一种实现我最初目标的解决方案。您可以从属性中删除虚拟关键字,而不是禁用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);
}