为什么我的Entity Framework Code First代理集合为null,为什么我不能设置它?

Why is my Entity Framework Code First proxy collection null and why can't I set it?

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

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

1
2
3
4
5
6
7
8
9
10
11
12
public class DanceStyle
{
    public DanceStyle()
    {
        if (DanceEvents == null)
        {
            DanceEvents = new Collection<DanceEvent>();
        }
    }
    ...
    public virtual ICollection<DanceEvent> DanceEvents { get; set; }
}

这在数据上下文之外有效,但如果我使用查询检索对象,尽管测试为真,但当我尝试设置它时,会得到以下异常:"DanceStyle"类型上的属性"DanceEvents"无法设置,因为集合已设置为EntityCollection。

我可以看到它是空的,我不能添加到其中,但是我也不能将它设置为集合,因为代理声明它已经设置好了。所以我不能用它。我搞糊涂了。

这是舞蹈课:

1
2
3
4
5
6
7
8
9
10
11
12
public class DanceEvent
{
    public DanceEvent()
    {
        if (DanceStyles == null)
        {
            DanceStyles = new Collection<DanceStyle>();
        }
    }
    ...
    public virtual ICollection<DanceStyle> DanceStyles { get; set; }
}

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


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

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

1
2
3
4
5
6
7
8
9
public class DanceEvent
{
    private ICollection<DanceStyle> _danceStyles;
    public virtual ICollection<DanceStyle> DanceStyles
    {
        get { return _danceStyles ?? (_danceStyles = new Collection<DanceStyle>()); }
        protected set { _danceStyles = value; }
    }
}

在上面的代码中,集合属性不再是自动的,而是有一个支持字段。最好让setter受到保护,防止任何代码(代理除外)随后修改这些属性。您将注意到构造函数不再是必需的,已被删除。


我在这里找到了这个问题的解决方案:代码首先添加到集合中?如何首先将代码用于存储库?

我从除集合和惰性加载对象(即所有本机类型)之外的所有属性中删除了"virtual"。

但我仍然不明白,如何才能以这样的情况结束:您有一个不能使用的空集合,并且无法将其设置为有效的集合。

我也在一个msdn论坛上找到了Rowan Miller的答案。

Hi,

If you make all your properties virtual then EF will generate proxy classes at runtime that derives from your POCO classed, these proxies allow EF to find out about changes in real time rather than having to capture the original values of your object and then scan for changes when you save (this is obviously has performance and memory usage benefits but the difference will be negligible unless you have a large number of entities loaded into memory). These are known as 'change tracking proxies', if you make your navigation properties virtual then a proxy is still generated but it is much simpler and just includes some logic to perform lazy loading when you access a navigation property.

Because your original code was generating change tracking proxies, EF was replacing your collection property with a special collection type to help it find out about changes. Because you try and set the collection back to a simple list in the constructor you are getting the exception.

Unless you are seeing performance issues I would follow Terrence's suggestion and just remove 'virtual' from your non-navigation properties.

~Rowan

因此,如果我的所有属性都是虚拟的,那么看起来我只有完整的"更改跟踪代理"的问题。但是考虑到这一点,为什么我仍然不能在变更跟踪代理上使用虚拟属性呢?此代码在第三行爆炸,因为ds2.danceevents为空,无法在构造函数中设置:

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

我仍然很困惑,即使我的代码现在因为上面的修复而工作。


老问题…

POCO类:

1
2
3
4
5
6
7
8
9
10
public partial class MyPOCO
{
    public MyPOCO()
    {
        this.MyPocoSub = new HashSet<MyPocoSub>();
    }

    //VIRTUAL
    public virtual ICollection<MyPocoSub> MyPocoSub { get; set; }
}

代理代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
    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的代理类中引发了异常。这意味着行为仍然存在。