【问题标题】:How can I change an Entity Framework ICollection to be an ObservableCollection?如何将实体框架 ICollection 更改为 ObservableCollection?
【发布时间】:2026-02-03 09:45:01
【问题描述】:

因此,我在使用实体框架设计器制作用作 MVVM 项目中模型的 EDMX 的过程中已经走得很远了。 我刚刚遇到一个问题,我很确定生成代码的ICollection<>(例如见下文)确实需要是ObservableCollection<>,以便将该集合绑定到DataGrid,以便成功的。 我认为我对修改 EF 代码生成以生成 ObservableCollections 而不是 ICollections 的可能性有所了解。有人试过成功吗?

我想另一种选择是让包含所选客户对象的 VM 还包含一个本地 ObservableCollection<Order>,它在选择客户对象时创建....我只是担心上下文保存并将数据保存在同步。

与子对象集合关联的典型代码生成对象:

    public partial class Customer
{
    public Customer()
    {
        this.Orders = new HashSet<Order>();
    }

    public int Id { get; set; }
    public System.DateTime Date { get; set; }

    public virtual ICollection<Order> Orders { get; set; }
}

【问题讨论】:

    标签: c# entity-framework mvvm


    【解决方案1】:

    这就是我所做的,并且首先使用 EF 数据库对我有用。

    这就是你需要生成的:

    public partial class Parent
    {
        public Parent()
        {
            this.Children= new ObservableCollection<Child>();
        }
    

    这样默认的构造函数就会被替换。 而且 ObservableCollection 就是 ICollection,所以你不需要改变任何东西。

    要在每次更新数据库模型时显示此内容,您必须使用以下部分更改 .tt 文件:

    public string UsingDirectives(bool inHeader, bool includeCollections = true)
    {
        return inHeader == string.IsNullOrEmpty(_code.VsNamespaceSuggestion())
            ? string.Format(
                CultureInfo.InvariantCulture,
                "{0}using System;{1}" +
                "{2}",
                inHeader ? Environment.NewLine : "",
                includeCollections ? (Environment.NewLine + "using System.Collections.ObjectModel;" 
                    + Environment.NewLine + "using System.Collections.Generic;") : "",
                inHeader ? "" : Environment.NewLine)
            : "";
    }
    

    还有这个:

        foreach (var navigationProperty in collectionNavigationProperties)
        {
    
        this.<#=code.Escape(navigationProperty)#> = new ObservableCollection<<#=typeMapper.GetTypeName(navigationProperty.ToEndMember.GetEntityType())#>>();
        }
    

    【讨论】:

      【解决方案2】:

      您的data logicmodels 应该与您的viewmodelmodels 分开。因此,我认为更好的选择是您在创建ObservableCollection 时所谈论的内容。保存时,您始终可以同步到上下文(我忘记了要同步的确切语法)。

      【讨论】:

      • 嗯...对。另外,我想在过去的几个小时里我可能一直在吠叫错误的树。我有一个长时间运行的 EF 上下文(因为 MVVM 绑定),所以我在我的数据适配器析构函数的上下文中调用 SaveChanges() 以确保更改被持久化。原来你can't do that。所以我的“可观察性”问题可能是一个转移注意力的问题。
      【解决方案3】:

      通常通过DbContext 公开的DbSet 类具有Local 属性,即ObservableCollection&lt;T&gt;。更多信息请见the official documentation

      【讨论】:

      • 文档似乎侧重于 Code First。我正在使用 EDMX 设计器,因此与示例不同,我不能只更改我的模型类以使用 ObservableCollection。实际上,我可以,但是更改设计器中的实体会覆盖这些更改。也就是说,除非我原来修改 T4 模板的问题有答案。感谢本地属性提示。
      【解决方案4】:

      是的,我已经这样做了,它成功地适用于我的业务应用程序。我将 Model.tt 文件修改为具有虚拟 ObservableCollection&lt;T&gt; 而不是 ICollection&lt;T&gt; 并将 HashSet&lt;T&gt; 替换为相同的。

      我还通过以下实现在实体上实现了INotifyPropertyChanged

      public event PropertyChangedEventHandler PropertyChanged;
      protected void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
      {
          var handler = PropertyChanged;
          if (handler != null)
          {
              handler(this, new PropertyChangedEventArgs(propertyName));
          }
      }
      

      我需要包含三个额外的 using 语句:

      using System.ComponentModel;
      using System.Runtime.CompilerServices;
      using System.Collections.ObjectModel;
      

      这是我在 CodeStringGenerator 中更改以实现我的 getter 和 setter 的函数:(抱歉,我还没有想办法让它更具可读性)

      public string Property(EdmProperty edmProperty)
      {
          var fourSpaces = "    ";
          return string.Format(
              CultureInfo.InvariantCulture,
              "{0} {1} _{2};{3}{4}{0} {1} {2}{3}{4}{{{3}{4}{4}{5}get {{ return _{2}; }} {3}{4}{4}{6}set{3}{4}{4}{{{3}{4}{4}{4}if (value == _{2}) return;{3}{4}{4}{4}_{2} = value;{3}{4}{4}{4}NotifyPropertyChanged();{3}{4}{4}}}{3}{4}}}{3}",
              Accessibility.ForProperty(edmProperty),
              _typeMapper.GetTypeName(edmProperty.TypeUsage),
              _code.Escape(edmProperty),
              Environment.NewLine,
              fourSpaces,
              _code.SpaceAfter(Accessibility.ForGetter(edmProperty)),
              _code.SpaceAfter(Accessibility.ForSetter(edmProperty)));
      }
      

      这是一个完整生成的实体文件示例供参考:

      namespace Eagl.Eagle.Data
      {
          using System;
          using System.ComponentModel;
          using System.Runtime.CompilerServices;
          using System.Collections.Generic;
          using System.Collections.ObjectModel;
      
          public partial class Game : INotifyPropertyChanged
          {
              public event PropertyChangedEventHandler PropertyChanged;
              protected void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
              {
                  var handler = PropertyChanged;
                  if (handler != null)
                  {
                      handler(this, new PropertyChangedEventArgs(propertyName));
                  }
              }
      
              public Game()
              {
                  this.Playtests = new ObservableCollection<Playtest>();
              }
      
              public int _Id;
              public int Id
              {
                  get { return _Id; } 
                  set
                  {
                      if (value == _Id) return;
                      _Id = value;
                      NotifyPropertyChanged();
                  }
              }
      
              public string _Name;
              public string Name
              {
                  get { return _Name; } 
                  set
                  {
                      if (value == _Name) return;
                      _Name = value;
                      NotifyPropertyChanged();
                  }
              }
      
      
              public virtual ObservableCollection<Playtest> Playtests { get; set; }
          }
      }
      

      【讨论】:

      • 也许我错过了什么。你在哪里告诉模板专门使用 ObservableCollection 而不是 ICollection?您是否只是对 tt 文件中对 ICollection 的引用进行了替换,然后必须添加您在上面指定的代码才能使其全部工作?