【问题标题】:Why is my Entity Framework Code First proxy collection null and why can't I set it?为什么我的 Entity Framework Code First 代理集合为空,为什么我不能设置它?
【发布时间】:2011-05-03 10:39:39
【问题描述】:

我正在使用 DBContext 并且有两个属性都是虚拟的类。当我查询上下文时,我可以在调试器中看到我正在获取一个代理对象。但是,当我尝试添加一个集合属性时,它仍然为空。我认为代理会确保集合被初始化。

因为我的 Poco 对象可以在其数据上下文之外使用,所以我在构造函数中添加了一个检查集合是否为 null 并在必要时创建它:

public class DanceStyle
{
    public DanceStyle()
    {
        if (DanceEvents == null)
        {
            DanceEvents = new Collection<DanceEvent>();
        }
    }
    ...
    public virtual ICollection<DanceEvent> DanceEvents { get; set; }
}

这在数据上下文之外有效,但如果我使用查询检索对象,虽然测试为真,但当我尝试设置它时,我得到以下异常:'DanceStyle_B6089AE40D178593955F1328A70EAA3D8F0F01DDE9F9FBD615F60A34F9178B94'类型上的属性'DanceEvents'不能设置,因为该集合已设置为 EntityCollection。'

我可以看到它是空的,我不能添加到它,但我也不能将它设置为一个集合,因为代理说它已经设置好了。因此我不能使用它。我很困惑。

这里是 DanceEvent 类:

public class DanceEvent
{
    public DanceEvent()
    {
        if (DanceStyles == null)
        {
            DanceStyles = new Collection<DanceStyle>();
        }
    }
    ...
    public virtual ICollection<DanceStyle> DanceStyles { get; set; }
}

我在上面的代码中省略了其他值类型属性。我在上下文类中没有这些类的其他映射。

【问题讨论】:

    标签: entity-framework collections poco proxies


    【解决方案1】:

    正如您在回答自己的问题时正确观察到的那样,通过阻止实体框架创建更改跟踪代理,从集合属性中删除“虚拟”关键字可以解决该问题。但是,这对很多人来说并不是一个解决方案,因为更改跟踪代理非常方便,并且可以在您忘记检测代码中正确位置的更改时帮助防止出现问题。

    更好的方法是修改您的 POCO 类,以便它们在其 get 访问器中实例化集合属性,而不是在构造函数中。这是您的 POCO 类,已修改为允许创建更改跟踪代理:

    public class DanceEvent
    {
        private ICollection<DanceStyle> _danceStyles;
        public virtual ICollection<DanceStyle> DanceStyles
        {
            get { return _danceStyles ?? (_danceStyles = new Collection<DanceStyle>()); }
            protected set { _danceStyles = value; }
        }
    }
    

    在上面的代码中,collection 属性不再是自动的,而是有一个支持字段。最好让 setter 受保护,以防止任何代码(代理除外)随后修改这些属性。你会注意到构造函数不再需要并且被移除了。

    【讨论】:

    • 这是另一种方法,但它没有解释我的评论:“这在数据上下文之外有效,但如果我使用查询检索对象,虽然测试是真的,当我尝试设置它,我得到以下异常:'无法设置类型'DanceStyle_B6089AE40D178593955F1328A70EAA3D8F0F01DDE9F9FBD615F60A34F9178B94'上的属性'DanceEvents',因为集合已设置为EntityCollection。'我可以看到它是空的,我不能添加到它,但我也不能将它设置为一个集合,因为代理说它已经设置了。因此我不能使用它。我很困惑。"
    • 我无法重现您所描述的内容。根据我的经验,当实体被实例化为代理时(由于它被查询返回,或者如果使用 DbSet.Create 方法),它的集合属性将使用 EntityCollection 对象进行实例化。您永远不必设置这些属性——只需从中添加/删除实体即可。
    • 自从我 2 年前写下我的问题后,这种行为可能已经发生了变化。
    • 我刚刚在 EF6 中遇到了这种行为,这个答案仍然有效!
    【解决方案2】:

    我在这里找到了解决这个问题的方法:Code First adding to collections? How to use Code First with repositories?

    我从所有属性中删除了“虚拟”,除了集合和延迟加载的对象,即所有本机类型。

    但我仍然不明白你怎么会遇到这样的情况:你有一个无法使用的空集合并且无法将其设置为有效集合。

    我还在 MSDN 论坛上找到了this answer from Rowan Miller

    嗨,

    如果您将所有属性设为虚拟,则 EF 将在运行时生成从您的 POCO 类派生的代理类,这些代理允许 EF 实时了解更改,而不必捕获对象的原始值,然后保存时扫描更改(这显然具有性能和内存使用优势,但差异可以忽略不计,除非您将大量实体加载到内存中)。这些被称为“更改跟踪代理”,如果您将导航属性设为虚拟,则仍会生成一个代理,但它更简单,并且仅包含一些在您访问导航属性时执行延迟加载的逻辑。

    因为您的原始代码正在生成更改跟踪代理,所以 EF 将您的集合属性替换为一种特殊的集合类型,以帮助它找出更改。因为您尝试将集合设置回构造函数中的简单列表,所以您得到了异常。

    除非您发现性能问题,否则我会按照 Terrence 的建议,从您的非导航属性中删除“虚拟”。

    ~罗文

    因此,如果我的所有属性都是虚拟的,我似乎只会遇到完整的“更改跟踪代理”的问题。但是鉴于此,为什么我仍然不能在更改跟踪代理上使用虚拟属性?由于 ds2.DanceEvents 为 null 且无法在构造函数中设置,因此该代码在第三行爆炸:

    DanceStyle ds2 = ctx.DanceStyles.Where(ds => ds.DanceStyleId == 1).Single();
    DanceEvent evt = CreateDanceEvent();
    ds2.DanceEvents.Add(evt);
    

    我仍然感到困惑,尽管由于上述修复,我的代码现在可以正常工作。

    【讨论】:

      【解决方案3】:

      老问题...

      Poco 类:

      public partial class MyPOCO
      {
          public MyPOCO()
          {
              this.MyPocoSub = new HashSet<MyPocoSub>();
          }
      
          //VIRTUAL
          public virtual ICollection<MyPocoSub> MyPocoSub { get; set; }
      }
      

      和代理代码:

          public override ICollection<MyPocoSubSet> MyPocoSubSets
          {
              get
              {
                  ICollection<MyPocoSubSet> myPocoSubSets = base.MyPocoSubSets;
                  if (!this.ef_proxy_interceptorForMyPocoSubSets(this, myPocoSubSets))
                  {
                      return base.MyPocoSubSets;
                  }
                  return myPocoSubSets;
              }
              set
              {
                  if (value != this.RelationshipManager.GetRelatedEnd("WindowsFormsApplication.Models.MyPocoSubSet_MyPOCO", "MyPocoSubSet_MyPOCO_Source"))
                  {
                      // EXCEPTION 
                      throw new InvalidOperationException("The property 'MyPocoSubSets' on type 'MyPOCO_A78FCE6C6A890855C68B368B750864E3136B589F9023C7B1D90BF7C83FD291AC' cannot be set because the collection is already set to an EntityCollection.");
                  }
                  base.MyPocoSubSets = value;
              }
          }
      

      正如您所见,ExtityFramework 5 的代理类中引发了异常。这意味着该行为仍然存在。

      【讨论】:

        猜你喜欢
        • 2014-08-18
        • 2015-10-11
        • 1970-01-01
        • 1970-01-01
        • 2011-05-12
        • 1970-01-01
        • 2011-01-16
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多