【问题标题】:How to map different navigation properties in TPH types to the same table?如何将 TPH 类型中的不同导航属性映射到同一个表?
【发布时间】:2011-09-11 19:10:27
【问题描述】:

我有这个现有的数据库模式,这意味着使用联合表的自引用多对多关系。 Location 表可能包含 CountryCityDistrict或 Area 信息到鉴别器字段。表 RelatedLocation 保存自引用关系。

我的领域模型如下,Location类是抽象类,每个继承的类都包含相关的导航属性。

public abstract class Location
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public class Country : Location
{
    public virtual ICollection<District> Districts { get; set; }
}

public class District : Location
{
    public virtual ICollection<Country> Countries { get; set; }
    public virtual ICollection<City> Cities { get; set; }
}

public class City : Location
{
    public virtual ICollection<District> Districts { get; set; }
    public virtual ICollection<Area> Areas { get; set; }
}

public class Area : Location
{
    public virtual ICollection<City> Cities { get; set; }
}

在 OnModelCreating 上,我使用以下方法来映射每个继承类的多对多关系

modelBuilder.Entity<Country>()
            .HasMany(c => c.Districts)
            .WithMany(d => d.Countries)
            .Map(t => t.ToTable("RelatedLocations").MapLeftKey("ParentId").MapRightKey("RelatedId"));
modelBuilder.Entity<City>()
            .HasMany(c => c.Districts)
            .WithMany(d => d.Cities)
            .Map(t => t.ToTable("RelatedLocations").MapLeftKey("ParentId").MapRightKey("RelatedId"));

在创建模型时,我收到并排除“每个 EntitySet 必须引用唯一的模式和表”,即 EF 多次抱怨将不同的关系映射到同一个表“RelatedLocaions” .

我不知道 EF4.1 不支持这种映射方式,或者我以错误的方式映射它!

【问题讨论】:

    标签: entity-framework-4.1 ef-code-first


    【解决方案1】:

    我怀疑您尝试的映射是否可行。我会尝试类似的方法:

    public abstract class Location
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public virtual ICollection<Location> ParentLocations { get; set; }
        public virtual ICollection<Location> RelatedLocations { get; set; }
    }
    
    public class Country : Location
    {
        // readonly = not mapped
        public IEnumerable<District> Districts
        {
            get { return RelatedLocations.OfType<District>(); }
        }
    }
    
    public class District : Location
    {
        public IEnumerable<Country> Countries
        {
            get { return ParentLocations.OfType<Country>(); }
        }
    
        public IEnumerable<City> Cities
        {
            get { return RelatedLocations.OfType<City>(); }
        }
    }
    
    // same approch for the other collections
    

    然后是这个映射:

    modelBuilder.Entity<Location>()
                .HasMany(l => l.ParentLocations)
                .WithMany(l => l.RelatedLocations)
                .Map(t => t.ToTable("RelatedLocations")
                           .MapLeftKey("ParentId")
                           .MapRightKey("RelatedId"));
    

    多对多映射总是在ParentLocationsRelatedLocations 之间进行,但是这些集合根据您使用的具体类型填充了派生类的不同实例。只读集合只是在 Location 实体的内存中执行类型转换的助手(基于延迟加载的 ParentLocationsRelatedLocations)。

    编辑

    也许不是使用.OfType&lt;T&gt;()过滤来自源集合的所有T 类型的对象,.Cast&lt;T&gt;() 更可取,它尝试将所有对象源集合类型为T,如果无法进行强制转换,则抛出异常。它应该基本上导致相同的结果,因为您的基类中的ICollection&lt;Location&gt; 应该始终只由相同的派生类型填充。例如:Country.RelatedLocations 应该只包含District 类型的实体。但在这种情况下,例外可能是好的,因为它表明有问题,而不是默默地忽略集合中另一种类型的实体(OfType 会这样做)。

    编辑 2

    我想强调IEnumerable 集合是helpers,它允许您检索具有派生类型的实体。集合只是执行类型转换,仅此而已。它们与到数据库的映射无关,EF 甚至没有“看到”它们的存在。您可以删除它们,EF 模型和数据库表列、关系和引用约束不会发生任何变化。

    您将如何在此模型中添加和检索实体?例子:

    • 使用District 实体列表添加新的Country

      var country = new Country() { RelatedLocations = new List<Location>() };
      country.Name = "Palau";
      // ParentLocations stays empty because Country has no parents
      var district1 = new District { Name = "District1" };
      var district2 = new District { Name = "District2" };
      country.RelatedLocations.Add(district1); // because District is a Location
      country.RelatedLocations.Add(district2);
      context.Locations.Add(country); // because Country is a Location
      context.SaveChanges();
      
    • 再次检索该实体:

      var country = context.Locations.OfType<Country>()
          .SingleOrDefault(c => c.Name == "Palau");
      // now get the districts, RelatedLocations is lazily loaded
      var districts = country.RelatedLocations.Cast<District>();
      // What type is districts? It's an IEnumerable<District>.
      // So we can also use a helper property:
      // var districts = country.Districts;
      
    • 检索一个地区:

      var district = context.Locations.OfType<District>()
          .SingleOrDefault(d => d.Name == "District1");
      var countries = district.ParentLocations.Cast<Country>();
      // or with the helper: var countries = district.Countries;
      // countries collection contains Palau, because of many-to-many relation
      

    编辑 3

    您可以创建延迟加载代理,而不是使用new 创建Country。然后就不需要初始化RelatedLocations 集合了。我想知道这如何与派生类型一起使用,但我刚刚发现Create 的重载带有用于此目的的泛型参数:

    var country = context.Locations.Create<Country>();
    

    【讨论】:

    • 不错的提示,但不要认为这会执行使用 IEnumerable 获取所有相关实体的意愿!此外,处理 CRUD 操作需要具有只读属性的额外步骤。
    • @Hossam:第一行是您问题的答案。 EF 无法实现您想要实现的映射。要么使用 Slauma 的方法,要么重新设计你的应用程序。
    • @Hossam:我试图阐明您将如何使用模型(添加和检索实体),请参阅我的 Edit2。
    • 感谢您的编辑和澄清,我相信这是处理现有数据库的最佳方式。
    猜你喜欢
    • 1970-01-01
    • 2013-10-19
    • 1970-01-01
    • 2023-03-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多