42

我正在使用 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; }
}

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

4

3 回答 3

49

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

更好的方法是修改您的 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; }
    }
}

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

于 2012-03-28T17:28:30.743 回答
13

我在这里找到了解决这个问题的方法:Code First added to collections? 如何在存储库中使用 Code First?

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

但是我仍然不明白您如何最终会遇到无法使用的空集合并且无法将其设置为有效集合的情况。

我还在MSDN 论坛上从 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);

我仍然感到困惑,即使我的代码由于上面的修复而现在可以工作。

于 2010-11-01T14:25:26.150 回答
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 的代理类中引发了异常。这意味着该行为仍然存在。

于 2013-08-06T18:44:41.903 回答