EF CodeFirst самоссылающиеся многие-ко-многим ... на абстрактных или производных классах - PullRequest
2 голосов
/ 02 февраля 2012

Я пытаюсь смоделировать множество ссылок на многие в EF CodeFirst с полиморфной структурой таблиц. Я использую CTP-версию октября 2011 года, которая поддерживает свойства навигации для производных типов (что хорошо работает в других тестах, которые я делал).

Проблема:

Когда я устанавливаю это конкретное отношение «многие ко многим» в отображении базовой (абстрактной) таблицы и пытаюсь получить связанные записи, я получаю SQL-запрос с сотнями K объединений и объединений ... только время, затрачиваемое на генерацию оператор SQL равен 30 секундам по сравнению с голыми миллисекундами для его выполнения. Тем не менее, он возвращает соответствующие результаты. Когда я изменяю многие на многие, чтобы они существовали между двумя производными объектами, производимый запрос является идеальным ... но я не могу снова сопоставить ту же самую связанную таблицу M2M для других производных объектов, не будучи информированным о том, что объединяющая таблица "уже была сопоставлена ».

Особенности:

В существующей структуре базы данных есть базовая таблица - Сторона - которая объединяется 1 ... 1 или 0 с Заказчиком, Продавцом, Пользователем и Отделом (каждая из которых является типом Стороны).

Стороны связаны друг с другом через существующую таблицу соединений PartyRelationship (ID, InternalPartyID, ExternalPartyID). По соглашению, InternalPartyID содержит PartyID пользователя, а ExternalPartyID содержит PartyID клиента, поставщика или отдела, с которым они связаны.

Пытаясь использовать EF CodeFirst в новом проекте (WCF DataServices), я создал класс 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, Клиент, Продавец, Отдел и Пользователь подобны:

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 (189 КБ), который выполняется за 600 мс.

Я также пытался сопоставить таблицу PartyRelationship с двунаправленной таблицей один ко многим (обе для Party как «одна» таблица), но с аналогичным результатом.

Нужны ли отдельные таблицы соединений для клиентов-пользователей, поставщиков-пользователей и пользователей-отделов? Стоит ли смотреть на разделение вертикальной таблицы или представления базы данных, которые разделяют PartyRelationship на отдельные логические объекты (чтобы я мог переназначить одну и ту же таблицу)? Есть ли другой способ настройки модели EF в этом сценарии?

...