EF CodeFirst 自引用多对多...在抽象类或派生类上

发布于 2025-01-02 15:59:04 字数 4717 浏览 3 评论 0原文

我正在尝试使用多态表结构在 EF CodeFirst 中对自引用多对多进行建模。我使用的是 2011 年 10 月的 CTP,它支持派生类型的导航属性(在我所做的其他测试中效果很好)。

问题:

当我在基(抽象)表的映射中设置这个特定的多对多关系并尝试获取相关记录时,我得到了一个包含数百 K 联合和连接的 SQL 查询......只是生成所花费的时间SQL 语句的执行时间为 30 秒,而执行它只需几毫秒。但是,它确实返回适当的结果。当我更改两个派生对象之间存在的多对多时,生成的查询是完美的...但是我无法为其他派生对象再次映射相同的相关 M2M 表,而不被告知连接表已“已映射” ”。

细节:

现有的数据库结构有一个基表——Party——它以 1...1 或 0 与 Customer、Vendor、User 和 Department(每个属于 Party 类型)连接。

各方通过现有的联接表 PartyRelationship(ID、InternalPartyID、ExternalPartyID)相互关联。按照约定,InternalPartyID 包含用户的 PartyID,ExternalPartyID 包含与其关联的客户、供应商或部门的 PartyID。

尝试在新项目(WCF DataServices)中使用 EF CodeFirst,我将 Party 类创建为:

public abstract class Party
{
    public Party()
    {
        this.Addresses = new List<Address>();
        this.PhoneNumbers = new List<PhoneNumber>();
        this.InternalRelatedParties = new List<Party>();
        this.ExternalRelatedParties = new List<Party>();
}

    public int PartyID { get; set; }
    public short Active { get; set; }
    //other fields common to Parties

    public virtual ICollection<Address> Addresses { get; set; }
    public virtual ICollection<PhoneNumber> PhoneNumbers { get; set; }
    public virtual ICollection<Party> InternalRelatedParties { get; set; }
    public virtual ICollection<Party> ExternalRelatedParties { get; set; }
}

然后,使用 TPT 继承,Customer、Vendor、Department 和 User 类似于:

public class Customer : Party
{
    public string TermsCode { get; set; }
    public string DefaultFundsCode { get; set; }
    //etc
}

public class User : Party
{
    public string EmployeeNumber { get; set; }
    public string LoginName { get; set; }
    //etc
}

连接表:

public class PartyRelationship
{
    public int PartyRelationshipID { get; set; }
    public int InternalPartyID { get; set; }
    public int ExternalPartyID { get; set; }
    //certain other fields specific to the relationship
}

映射:

public class PartyMap : EntityTypeConfiguration<Party>
{
    public PartyMap()
    {
        // Primary Key
        this.HasKey(t => t.PartyID);

        // Properties
        this.ToTable("Party");
        this.Property(t => t.PartyID).HasColumnName("PartyID");
        this.Property(t => t.Active).HasColumnName("Active");
        //etc

        // Relationships
        this.HasMany(p => p.InternalRelatedParties)
           .WithMany(rp => rp.ExternalRelatedParties)
           .Map(p => p.ToTable("PartyRelationship")
           .MapLeftKey("ExternalPartyID")
           .MapRightKey("InternalPartyID"));
    }
}

public class PartyRelationshipMap : EntityTypeConfiguration<PartyRelationship>
{
    public PartyRelationshipMap()
    {
        // Primary Key
        this.HasKey(t => t.PartyRelationshipID);

        // Properties
        // Table & Column Mappings
        //this.ToTable("PartyRelationship"); // Commented out to prevent double-mapping
        this.Property(t => t.PartyRelationshipID).HasColumnName("PartyRelationshipID");
        this.Property(t => t.InternalPartyID).HasColumnName("InternalPartyID");
        this.Property(t => t.ExternalPartyID).HasColumnName("ExternalPartyID");
        this.Property(t => t.CreateTime).HasColumnName("CreateTime");
        this.Property(t => t.CreateByID).HasColumnName("CreateByID");
        this.Property(t => t.ChangeTime).HasColumnName("ChangeTime");
        this.Property(t => t.ChangeByID).HasColumnName("ChangeByID");
    }
}

上下文:

public class MyDBContext : DbContext
{
    public MyDBContext()
        : base("name=MyDBName")
    {
        Database.SetInitializer<MyDBContext>(null);
        this.Configuration.ProxyCreationEnabled = false;
    }

    public DbSet<Party> Parties { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Conventions.Remove<IncludeMetadataConvention>();
        modelBuilder.Configurations.Add(new PartyMap());
        modelBuilder.Configurations.Add(new PartyRelationshipMap());
    }
}

一个 URL,例如作为 http://localhost:29004/Services/MyDataService.svc/Parties(142173)/SAData.Customer/InternalRelatedParties 最终返回正确的 oData,但需要 30 秒才能生成巨大的 SQL 语句(189K )在 600 毫秒内执行。

我还尝试使用双向一对多映射 PartyRelationship 表(两者都作为“一”表映射到 Party),但结果类似。

我是否需要单独的客户-用户、供应商-用户和部门-用户连接表?我是否应该查看垂直表拆分或将 PartyRelationship 分成单独的逻辑实体的数据库视图(以便我可以重新映射同一个表)?在这种情况下,还有其他方法可以配置 EF 模型吗?

I'm trying to model a self-referencing many to many in EF CodeFirst with a polymorphic table structure. I'm using the October 2011 CTP which supports navigation properties on derived types (which works well in other tests I've done).

The problem:

When I set up this particular many to many relationship in the base (abstract) table's mapping and try to get related records, I get a SQL query with hundreds of K of unions and joins...just the time taken to generate the SQL statement is 30 seconds, compared to bare milliseconds to execute it. However, it does return appropriate results. When I change the many to many to exist between two derived objects, the query produced is perfect...but I can't map the same relating M2M table again for other derived objects without being informed that the joining table has "already been mapped".

Specifics:

An existing database structure has a base table--Party--which is joined 1...1 or 0 with Customer, Vendor, User, and Department (each a type of Party).

Parties are related to each other via an existing join table PartyRelationship (ID, InternalPartyID, ExternalPartyID). By convention, InternalPartyID contains a User's PartyID and ExternalPartyID contains the PartyID of the Customer, Vendor, or Department with which they are associated.

Trying to use EF CodeFirst in a new project (WCF DataServices), I have created the Party class as:

public abstract class Party
{
    public Party()
    {
        this.Addresses = new List<Address>();
        this.PhoneNumbers = new List<PhoneNumber>();
        this.InternalRelatedParties = new List<Party>();
        this.ExternalRelatedParties = new List<Party>();
}

    public int PartyID { get; set; }
    public short Active { get; set; }
    //other fields common to Parties

    public virtual ICollection<Address> Addresses { get; set; }
    public virtual ICollection<PhoneNumber> PhoneNumbers { get; set; }
    public virtual ICollection<Party> InternalRelatedParties { get; set; }
    public virtual ICollection<Party> ExternalRelatedParties { get; set; }
}

Then, using TPT inheritance, Customer, Vendor, Department and User are similar to:

public class Customer : Party
{
    public string TermsCode { get; set; }
    public string DefaultFundsCode { get; set; }
    //etc
}

public class User : Party
{
    public string EmployeeNumber { get; set; }
    public string LoginName { get; set; }
    //etc
}

The joining table:

public class PartyRelationship
{
    public int PartyRelationshipID { get; set; }
    public int InternalPartyID { get; set; }
    public int ExternalPartyID { get; set; }
    //certain other fields specific to the relationship
}

Mappings:

public class PartyMap : EntityTypeConfiguration<Party>
{
    public PartyMap()
    {
        // Primary Key
        this.HasKey(t => t.PartyID);

        // Properties
        this.ToTable("Party");
        this.Property(t => t.PartyID).HasColumnName("PartyID");
        this.Property(t => t.Active).HasColumnName("Active");
        //etc

        // Relationships
        this.HasMany(p => p.InternalRelatedParties)
           .WithMany(rp => rp.ExternalRelatedParties)
           .Map(p => p.ToTable("PartyRelationship")
           .MapLeftKey("ExternalPartyID")
           .MapRightKey("InternalPartyID"));
    }
}

public class PartyRelationshipMap : EntityTypeConfiguration<PartyRelationship>
{
    public PartyRelationshipMap()
    {
        // Primary Key
        this.HasKey(t => t.PartyRelationshipID);

        // Properties
        // Table & Column Mappings
        //this.ToTable("PartyRelationship"); // Commented out to prevent double-mapping
        this.Property(t => t.PartyRelationshipID).HasColumnName("PartyRelationshipID");
        this.Property(t => t.InternalPartyID).HasColumnName("InternalPartyID");
        this.Property(t => t.ExternalPartyID).HasColumnName("ExternalPartyID");
        this.Property(t => t.CreateTime).HasColumnName("CreateTime");
        this.Property(t => t.CreateByID).HasColumnName("CreateByID");
        this.Property(t => t.ChangeTime).HasColumnName("ChangeTime");
        this.Property(t => t.ChangeByID).HasColumnName("ChangeByID");
    }
}

Context:

public class MyDBContext : DbContext
{
    public MyDBContext()
        : base("name=MyDBName")
    {
        Database.SetInitializer<MyDBContext>(null);
        this.Configuration.ProxyCreationEnabled = false;
    }

    public DbSet<Party> Parties { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Conventions.Remove<IncludeMetadataConvention>();
        modelBuilder.Configurations.Add(new PartyMap());
        modelBuilder.Configurations.Add(new PartyRelationshipMap());
    }
}

A URL such as http://localhost:29004/Services/MyDataService.svc/Parties(142173)/SAData.Customer/InternalRelatedParties eventually returns correct oData but takes 30 seconds to produce an enormous SQL statement (189K) that executes in 600 ms.

I've also tried mapping the PartyRelationship table with a bidirectional one to many (both to Party as the "one" table), but with a similar outcome.

Do I need separate join tables for Customer-User, Vendor-User, and Department-User? Should I look at vertical table splitting or database views that separates PartyRelationship into separate logical entities (so I can remap the same table)? Is there another way the EF model should be configured in this scenario?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文