使用一对多关系序列化Entity Framework对象

35

我正在尝试使用EF和Code First以及Web API。在序列化多对多的关系时遇到了问题。当我尝试执行下面的Web API方法时,出现以下错误信息:

public class TagsController : ApiController
{

        private BlogDataContext db = new BlogDataContext();

        // GET api/Tags
        public IEnumerable<Tag> GetTags()
        {
            return db.Tags.AsEnumerable();
        }
}

我遇到了以下错误:

'System.Data.Entity.DynamicProxies.Tag_FF17EDDE6893000F7672649A39962DB0CA591C699DDB73E8C2A56203ED7C7B6D' 的数据契约名称为 'Tag_FF17EDDE6893000F7672649A39962DB0CA591C699DDB73E8C2A56203ED7C7B6D:http://schemas.datacontract.org/2004/07/System.Data.Entity.DynamicProxies',这是未预期的。请考虑使用DataContractResolver或将任何静态未知的类型添加到已知类型列表中,例如使用KnownTypeAttribute属性或将它们添加到传递给DataContractSerializer的已知类型列表。

我阅读了一些 SO 文章 (文章 1文章 2),它们建议添加以下特性以解决问题:

[DataContract(IsReference=true)]

但是这没有任何效果。同时使用 [IgnoreDataMember] 也没有任何效果。唯一似乎有效的选项是将 Configuration.ProxyCreationEnabled 设置为 false。这是我的唯一选择吗?还有其他解决方法吗?

样例 POCO 对象:

Tag

[DataContract(IsReference = true)]
public class Tag
{
        public Tag()
        {
            this.Blogs = new HashSet<Blog>();
        }

        [Key]
        [DataMember]
        public int Id { get; set; }

        [DataMember]
        public string Name { get; set; }

        [IgnoreDataMember]
        public virtual ICollection<Blog> Blogs { get; set; }
}

博客

[DataContract(IsReference = true)]
public class Blog
{
    public Blog()
    {
        this.Tags = new HashSet<Tag>();
    }

    [Key]
    [DataMember]
    public int Id { get; set; }

    [DataMember]
    public string Name { get; set; }

    [IgnoreDataMember]
    public virtual ICollection<Tag> Tags { get; set; }
}

1
我曾经遇到过完全相同的问题,而且我发现唯一的解决方法就是像你和Erik Philips所说的那样,关闭代理创建。IgnoreDataMember没有效果的原因是问题出在代理生成上,而不是Tags集合的序列化。 - Ryan Amies
1
我有同样的问题,你是怎么解决的? 当我禁用代理创建时,我无法获取父级的值,而我需要它。请帮帮我。 - Dark_Knight
2个回答

92

当您看到像这样的对象:

System.Data.Entity.DynamicProxies.Tag_FF17EDDE6893000F7672649A39962DB0CA591C699DDB73E8C2A56203ED7C7B6D

它是一个运行时EF生成的代理,代替了通常被认为是POCO对象的对象。

Entity Framework创建此对象是因为它跟踪了对象何时发生了更改,因此在调用.SaveChanges()时可以优化操作。缺点是您实际上并未使用您定义的特定对象,因此数据合同和框架(Json.net)无法像使用原始POCO对象那样使用它们。

为了防止EF返回此对象,您有两个选择(目前):

首先,尝试关闭DbContext上的代理对象创建

DbContext.Configuration.ProxyCreationEnabled = false;

这将完全禁用特定 DbContext 中每个查询的代理对象的创建。(这不影响 ObjectContext 中的缓存对象)。

其次,使用 EntityFramework 5.0+AsNoTracking() 方法(ProxyCreationEnabled 在 EF 5.0 中仍可用)

你也应该能够

DbContext.Persons.AsNoTracking().FirstOrDefault();
或者
DbContext.Persons.
  .Include(i => i.Parents)
  .AsNoTracking()
  .FirstOrDefault();

与其在 DbContext 中全局禁用代理创建,这只会关闭每个查询的代理创建。(这会影响 ObjectContext 中的缓存对象,但不会被缓存)


1
问题在于为了让Entity Framework跟踪更改,当您请求一个实例时,它会提供一个包装实例,其中包含用于更改跟踪的触发器。这个包装实例几乎总是无法序列化。 - Erik Philips
1
@ErikPhilips 对不起,我无法理解这个解决方案,所以我不应该禁用代理创建吗?还是应该禁用? 然后我应该使用 dbcontext.class.AsNoTracking().FirstOrDefault() 吗? 它确切地是做什么的? - Dark_Knight
@Dark_Knight,我更新了我的答案,希望它包含了你所需要的细节。 - Erik Philips
非常感谢!我也需要在EF6中使用它 - 你帮我省去了很多的挫折! - lentyai
@ErikPhilips 如果我全局禁用ProxyCreation会有什么后果? - zsubzwary
@Zakawat 听起来是个好问题。点击提问按钮,然后提出你的问题 :) - Erik Philips

2

我想保持代理创建不变,并发现使用 ProxyDataContractResolver 似乎可以解决我的问题。请参阅 msdn 以获取有关如何在wcf中使用它的参考,虽然这并不完全是WebAPI,但应该可以让您朝着正确的方向前进。


2
你介意向我解释如何使用ProxyDataContractResolver来序列化poco实体吗?我找到的唯一示例涉及WCF,但我无法弄清楚它是如何工作的。 - Bruno Santos

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接