Проблема с отношением "многие ко многим" + наследование TPH в Entity Framework 6

Я столкнулся с проблемой с EF6, хотя я вполне уверен, что это относится к предыдущим версиям, которые поддерживают этот тип отображения. Я боюсь, что знаю ответ на поставленный вопрос, но я надеюсь, что я делаю что-то не так, или есть лучшее решение, чем то, что я здесь представляю. Все классы выпотрошены для ясности.

Так что я

public abstract class SoftwareFirmware
{
    public long Id { get; private set; }
    public ICollection<DeviceType> DeviceTypes { get; private set; } 

    public SoftwareFirmware()
    {
        DeviceTypes=new HashSet<DeviceType>();
    }
}

а также

public class DeviceType
{
    public long Id { get; set; }
    public virtual ICollection<Firmware> AvailableFirmwareVerions { get; private set; }
    public virtual ICollection<Software> AvailableSoftwareVerions { get; private set; }

    public DeviceType()
    {
        AvailableFirmwareVerions = new HashSet<Firmware>();
        AvailableSoftwareVerions = new HashSet<Software>();
    }
}

которые, как вы можете видеть, имеют отношение многие ко многим. Я определил два класса, которые происходят от SoftwareFirmwareУдачно названный

public class Firmware : SoftwareFirmware {}

а также

public class Software : SoftwareFirmware {}

Я использую наследование таблиц в иерархии, поэтому Software а также Firmware хранятся в одной таблице со столбцом дискриминатора. Наконец, я сопоставил отношения в производной DbContext"s OnModelCreating метод с

modelBuilder.Entity<DeviceType>().HasMany(d => d.AvailableFirmwareVerions).WithMany(firmware=>firmware.DeviceTypes);
modelBuilder.Entity<DeviceType>().HasMany(d => d.AvailableSoftwareVerions).WithMany(sofware=>sofware.DeviceTypes);

Проблема в том, что Entity Framework не поддерживает наследование с этим отображением, так как я получаю следующее, когда EF пытается сгенерировать базу данных:

DeviceTypes: FromRole: NavigationProperty 'DeviceTypes' недопустим. Тип "Software" из FromRole "DeviceType_AvailableSoftwareVerions_Target" в AssociationType "DeviceType_AvailableSoftwareVerions" должен точно соответствовать типу "SoftwareFirmware", для которого объявлено это свойство NavigationProperty.

Из этого я понимаю, что тип, который наследуется от SoftwareFirmware недостаточно хорошо для NavigationProperty, это должно быть SoftwareFirmware тип. Если я порву DeviceType Коллекция из SoftwareFirmware Базовый класс и дублировать его в каждом из производных классов, вещи работают, но это, безусловно, не идеально.

Итак, наконец, мой вопрос - есть ли другой способ настроить это так, чтобы я мог сохранить свое свойство навигации в своем базовом классе? Если нет, есть ли более обходной путь, чем то, что я описал?


ОБНОВЛЕНИЕ: так что может показаться, что SQL Server Management Studio поступил неправильно, так как ранее я построил схему базы данных без перегруженной версии WithMany, которая принимает выражение, и она не включает таблицы соединений. Похоже, что SSMS не очень подходит для изменений схемы с точки зрения добавления новых диаграмм, даже когда база данных удалена и воссоздана - ее необходимо перезапустить. Сильная боль, но я отвлекся...

В качестве последнего усилия я вернулся к версии без параметров WithMany для сопоставлений удалил и заново создал базу данных, перезапустив приложение, перезапустив SSMS и вот! Соединительные таблицы были созданы. Все, что мне нужно было сделать, это добавить Ignore для базы SoftwareFirmware класса DeviceTypes собственность и все чисто. Так что мой код отображения FluentAPI выглядит так:

modelBuilder.Entity<DeviceType>().HasMany(d => d.AvailableFirmwareVerions).WithMany();
modelBuilder.Entity<DeviceType>().HasMany(d => d.AvailableSoftwareVerions).WithMany();
modelBuilder.Entity<SoftwareFirmware>().Ignore(s => s.DeviceTypes);

который генерирует эту схему - в значительной степени именно ту схему, которую я хотел (игнорируйте дополнительные свойства):

Однако, поскольку вызов без параметров WithMany только подключает свойство навигации с одной стороны, обновляет Software.DeviceTypes а также Firmware.DeviceTypes не отслеживаются EF, поэтому я вернулся туда, где начал.

2 ответа

Решение

Проблема в том, что у вас есть одно свойство SoftwareFirmware.DeviceTypes, но вы пытаетесь использовать его как часть двух отдельных отношений. SoftwareFirmware.DeviceTypes не может быть обратным ни к DeviceType.AvailableFirmwareVerions, ни к DeviceType.AvailableSoftwareVerions.

То, что вы пытаетесь смоделировать, немного странно, потому что вы относитесь к ним как к разным отношениям, но тоже нет. Здесь есть два варианта...

Вариант 1: это два отдельных отношения

Удалите SoftwareFirmware.DeviceTypes и добавьте свойство DeviceTypes в разделе "Микропрограмма и ПО".

Это именно то, что вы делаете, когда вы устанавливаете Ignore в свойстве SoftwareFirmware.DeviceTypes и используете пустую перегрузку WithMany - вот почему это работает. Вы говорите EF, что есть две взаимосвязи (одна Software -> DeviceType, а другая Firmware -> DeviceType) и что нет свойства навигации, которое указывает в обратном направлении. Так как вы проигнорировали SoftwareFirmware.DeviceTypes, он просто не является частью вашей модели.

Вариант 2: это одно отношение

Удалите два свойства навигации в DeviceType и замените их одной навигацией к базовому классу SoftwareFirmware. Вы всегда можете добавить некоторые свойства фасада, которые фильтруют содержимое в программном и программном обеспечении (как показано ниже)

public class DeviceType
{
    public long Id { get; set; }

    public virtual ICollection<SoftwareFirmware> AvailableVerions { get; private set; }

    public virtual IEnumerable<Firmware> AvailableFirmwareVerions
    {
        get
        {
            return this.AvailableVerions.OfType<Firmware>();
        }
    }

    public virtual IEnumerable<Software> AvailableSoftwareVerions
    {
        get
        {
            return this.AvailableVerions.OfType<Software>();
        }
    }

    public DeviceType()
    {
        AvailableVerions = new HashSet<SoftwareFirmware>();
    }
}

Эта проблема звучит знакомо. (проверяя мою электронную почту... да, это было больше года назад!) У меня был кто-то, кто прислал мне образец, где текущие отношения API потерпели неудачу. Их было не много ко многим, но я думаю, что это та же проблема. Я долго смотрел на это и спросил Роуэна Миллера (в команде), и он сказал, что беглый API не может понять свойство, исходящее от базового типа.

т.е. свободный API не может видеть свойство DEVICETYPE, когда он смотрит на AvailableSoftwareVerions или на AvailableFirmwareVersions. (Я не могу сказать вам, ПОЧЕМУ это. Вы могли бы подумать, что это может быть найдено с помощью размышлений, но, возможно, это просто не было разработано с учетом этого сценария.)

Это все еще не имело смысла для меня, поэтому он объяснил дальше (и я дополню его объяснение вашими типами, что немного запутало, так как у вас есть дополнительные уровни наследования, а вещи названы немного непоследовательно... но я

Концептуально классы на самом деле не имеют смысла, потому что в DeviceType может быть много программного обеспечения или встроенных программ... но свойство обратной навигации определено в программном обеспечении SoftwareFirmware. Так что же происходит, когда что-то, не являющееся микропрограммой или программным обеспечением, имеет тип устройства? Обратное настроено как> DeviceType.AvailableSoftwareVersions, но это не может работать. Даже если вычеркнуть EF из рисунка, правильный путь к модели, который должен включать свойство Project в Report.

Это было с EF5. Если моя память верна и это та же проблема, то, возможно, она не изменилась для EF6. Возможно, мы должны посмотреть, есть ли проблема для решения этой проблемы. Тем не менее, его дальнейшее объяснение предполагает, что это не ошибка, а защита.

(Я собираюсь пинговать его, чтобы убедиться, что я правильно вывел предыдущую проблему на эту).

В этом письме Роуэн также предложил использовать логику получения вместо свойств навигации в качестве обходного пути.

Другие вопросы по тегам