为啥我的 Entity Framework Code First 代理集合为空,为啥我不能设置它?

Posted

技术标签:

【中文标题】为啥我的 Entity Framework Code First 代理集合为空,为啥我不能设置它?【英文标题】: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; 

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

【问题讨论】:

【参考方案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 的代理类中引发了异常。这意味着该行为仍然存在。

【讨论】:

以上是关于为啥我的 Entity Framework Code First 代理集合为空,为啥我不能设置它?的主要内容,如果未能解决你的问题,请参考以下文章

修改从 Entity Framework Core 返回的本地实体后,为啥我的执行消失了?

为啥我必须为 Code First / Entity Framework 提供无参数构造函数

为啥我的 Entity Framework Core Database-First 模型自定义更改在 Re-Scaffold 后消失了?任何解决方案

为啥运行时表达式会导致 Entity Framework Core 5 的缓存发生冲突?

为啥 Entity framework Entity Master Details Entity Edit

为啥要使用 Attach 来更新 Entity Framework 6?