如何在使用Table-Per-Type继承的表之间定义EF多对多关系?

时间:2013-02-11 21:02:46

标签: entity-framework

我定义了以下模型:

public class Account {
   public int Id {get; set;}
   ... // other account properties

   public ICollection<AccountAddress> Addresses {get; set;}
}

public class Address {
    public int Id {get; set;}
    public string Street1 {get; set;}
    public string Street2 {get; set;}
    ... // other standard address properties
}

public class AccountAddress : Address {
  public int AccountId {get; set;}
  public DateTime? BeginDate {get; set;}
  public string Notes {get; set;}
  ... // other account address specific properties
}

public class Site {
  public int Id {get; set;}
  public Address SiteAddress {get; set;}
}

我们的想法是,任何地址都可以绑定到任何帐户以及任何网站。但是,只要地址与某个帐户相关联,就可能需要存储有关该地址的其他信息。

由于AccountAddress继承自Address,我想使用TPT(Table Per Type)继承将Address属性映射到Address表,将扩展属性映射到扩展地址属性表。

我试过了:

public class AccountConfiguration : EntityTypeConfiguration<Account>
{
    public AccountConfiguration()
    {
        ToTable("Accounts");
        HasKey(a => a.Id);
        HasMany(a => a.Addresses).WithMany().Map(x =>
        {
           x.MapLeftKey("accountId");
           x.MapRightKey("addressId");
           x.ToTable("accountaddresses");
        }
     }
 }

 public class AddressConfiguration : EntityTypeConfiguration<Address>
 {
     public AddressConfiguration()
     {
         ToTable("addresses");
         HasKey(a => a.Id);
         ... // other property mappings
     }
 }

 public class AccountAddressConfiguration : EntityTypeConfiguration<AccountAddress>
 {
     public AccountAddressConfiguration() 
     {
        ToTable("addressextended");
        ... //other property mappings
     }
  }

这里的问题是没有定义将accountId映射到addressextended表。因此,如果ID为40的地址与两个不同的帐户绑定,请执行以下查询:

var _account = _context.Accounts
   .Include(a => a.Addresses)
   .SingleOrDefault(a => a.Id = 1234);

有时会为错误的帐户提供扩展属性。

为了实现这一目标,我需要做些什么?

修改 虽然接受的答案没有具体回答我的问题(即如何定义两个实体之间的多对多关系,其中一个实体是用TPT继承映射定义的),但它确实提供了一些帮助来提出可接受的工作。 / p>

我确实删除了实体继承,只是添加了地址作为我的AccountAddress实体的属性。然后我给了AccountAddress(addressextended表)自己的​​主id。我将accountaddresses表更改为包含帐户ID和accountaddress id(addressextended表的新id)的连接表。

我还必须将一个导航属性添加到AccountAddress for Address以及一个与之配合的映射。

2 个答案:

答案 0 :(得分:1)

我相信您可以通过更改查看“AccountAddress”和“SiteAddress”表的方式来解决此问题。而不是将它们作为Address的子类,为什么不将它们作为“连接表”。 AccountAddress实体加入地址和帐户,因此从技术上讲,它必须拥有一个帐户和一个地址。您还可以指定在将地址与帐户关联时相关的其他信息(例如地址类型,清除日期等)。

据我所知,您要实现的目标是将多个地址链接到多个帐户(或网站),并保留关于每个链接的一些元数据。

public class Account {
   public int Id {get; set;}
   ... // other account properties

   public ICollection<AccountAddress> Addresses {get; set;}
}

public class Address {
    public int Id {get; set;}
    public string Street1 {get; set;}
    public string Street2 {get; set;}

    public ICollection<AccountAddresses> Accounts {get; set;} // this denotes your two-way connection with the Account entity
    ... // other standard address properties
}

public class AccountAddress {
  public int AccountId {get; set;}
  public virtual Account {get; set;} // these are your "Account" properties

  public int AddressId {get; set;}
  public virtual Address {get;set;} //these are your "Address" properties

  public DateTime? BeginDate {get; set;}
  public string Notes {get; set;}
  ... // other account address specific properties
}

此模型现在表示以下关系:

1个帐户&lt; - &gt; *帐户地址*&lt; - &gt; 1地址

通过AccountAddress实体模拟地址和帐户之间的多对多关系。

然后,您应该配置帐户和地址实体,以通过以下方式展示他们的一对多关系:

modelBuilder.Entity<Account>().HasMany(a => a.AccountAddresses)
                              .WithRequired(aa => aa.Account)
                              .HasForeignKey(aa => aa.AccountId);

modelBuilder.Entity<Address>().HasMany(a => a.Accounts)
                              .WithRequired(ac => ac.Address)
                              .HasForeignKey(ac => ac.AddressId);

您可以将查询修改为:

var _account = _context.Accounts
   .Include(a => a.Addresses)
   .Include(a => a.Addresses.Address)
   .SingleOrDefault(a => a.Id = 1234);

答案 1 :(得分:1)

地址是最好的复杂类型而不是完整实体的类型之一。主要原因是地址实体永远不会被重复使用:关系可能是1:n,但实际上它们总是1:1。这是因为在运行时定义关系的逻辑将无法使用您从中选择正确地址的现有地址列表(因此您可以重复使用地址),但会使用来自用户的输入,因此您最终会创建新的地址实体实例。

当从邮政编码表中获取地址时,无论如何都不需要存储地址,因此如果您要存储地址信息,则无需将其存储为实体。

与'地址'的m:n关系是IMHO永远不会在实践中使用。因此,重构实体以将Address用作实体内的复杂类型。如果需要不同的地址类型,请为不同的地址类型定义不同的复杂类型。数据模型中的继承是你无论如何都要避免的,除非你计划在运行时通过多态重用验证逻辑,否则它实际上并不那么好:性能会受损,你的模型实际上会更复杂。如果您希望有一个通用接口可以与一般地址信息进行通信,请定义IAddress并在复杂地址类型上实现它。

所以TL; DR:抛弃地址作为实体类型并将它们定义为复杂类型并摆脱继承,它根本不会帮助你。