如何将TPH类型中的不同导航属性映射到同一个表?

时间:2011-09-10 15:55:48

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

我有这个现有的数据库模式,它意味着使用联合表自引用多对多关系。 位置表可能包含国家/地区城市地区或地区等信息到Disciminator字段。表RelatedLocation保存自引用关系。 Schema

我的域模型如下,其中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; }
}

On 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不支持这种方式的映射,或者我用错误的方法映射它!

1 个答案:

答案 0 :(得分:4)

我怀疑你正在尝试的映射是否可行。我会尝试类似的东西:

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之间,但这些集合根据您使用的具体类型填充了派生类的不同实例。只读集合只是在内存中执行类型转换的帮助程序(基于ParentLocationsRelatedLocations实体的延迟加载{/ 1}}。

修改

也许不是使用{em>过滤来自源集合中Location类型的所有对象的.OfType<T>(),而是T更可能尝试投射所有源集合中的对象键入.Cast<T>()并在无法进行转换时抛出异常。它应该基本上导致相同的结果,因为基类中的T应始终仅由相同的派生类型填充。例如:ICollection<Location>应仅包含Country.RelatedLocations类型的实体。但也许在这种情况下异常是好的,因为它表明出现了错误,而不是默默地忽略集合中另一种类型的实体(District会这样做。)

修改2

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

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

  • 添加一个新的IEnumerable,其中包含Country个实体列表:

    District
  • 再次检索此实体:

    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;
    

编辑3

您可以创建一个延迟加载代理,而不是使用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 创建Country。然后,您无需初始化new集合。我想知道这对于派生类型是如何工作的,但我发现有一个带有通用参数的RelatedLocations重载用于此目的:

Create