Использование внешнего ключа (FK) в качестве дискриминатора для таблицы на иерархию (TPH) - PullRequest
26 голосов
/ 24 октября 2011

[Вопрос:] Можно ли использовать ФК в качестве дискриминатора в EF и какие обходные пути люди придумали?

Сценарий

EF объекты

public class List
{
    public int Id { get; set; }
    public string Name { get; set; }
    public ICollection<ListItem> Items { get; set; }
}

public abstract class ListItem
{
    public int Id { get; set; }
    public List List { get; set; }
    public string Text { get; set; }
}

База данных

Существующая БД, которая не используется исключительно EF (т.е. не может быть изменена) имеет следующие поля:

List
    Id         int not null      (identity)
    Name       varchar

ListItem
    Id         int not null      (identity)
    ListId     int not null      (FK to List.Id)
    Text       varchar

Желаемый результат

Я хочу, чтобы идентификатор списка был дискриминатором для ListItem . т.е. для каждой записи в списке реализован отдельный класс, произошедший от ListItem .

Например, для списка [Id: 1]

public class PersonListItem : ListItem
{
    public int PersonId { get; set; }
    public Person Person { get; set; }
}

public class ListItemConfiguration : EntityTypeConfiguration<ListItem>
{
    Map<PersonListItem>(m => m.Requires("ListId").HasValue(1));
}

В приведенном выше сценарии сохранение изменений приводит к исключению SQL, поскольку EF пытается вставить его в List_Id при создании нового экземпляра ListItem . List_Id не является полем, и я не могу отобразить ListId как свойство на ListItem , поскольку его нельзя использовать в качестве дискриминатора.

Мое решение пока ...

В этих вопросах и ответах объясняется, почему они приняли решение не допускать ФК в качестве дискриминатора.

Мой обходной путь до сих пор заключается в добавлении еще одного поля в базу данных для использования в качестве дискриминатора, для которого затем устанавливается то же значение, что и ListId с использованием триггера вставки (для обработки вставок без EF), а затем добавьте навигационное свойство ListId к ListItem сущности.

У кого-нибудь есть предложения / альтернативы?

Ответы [ 3 ]

2 голосов
/ 24 марта 2013

Предполагая, что код сначала EF 4, есть несколько (очень?) Надуманный способ, который требует использования представлений для создания столбца виртуального дискриминатора, такого же, как ListId.

Вы должны быть в состоянии добавитьпредставление в БД и «вместо» запускает это представление для имитации нового столбца.Во всем этом вам нужно обойти некоторые проблемы, которые EF имеет с этим подходом.

CREATE view [dbo].[vListItem] as 
select Id, ListId,[Text]
,cast(ListId as varchar(10)) as Discriminator  
from xListItem;
GO

Note: need the cast to varchar <= int causes errors on EF (MaxLength facet)

Пример триггера для вставки:

CREATE TRIGGER trg_vListItem_Insert
ON [vListItem]
INSTEAD OF INSERT
AS
Begin
    -- set nocount on
    Insert into xListItem (ListId,[Text]) 
    Select i.ListId, i.[Text]
    from Inserted i

    -- Need this so Ef knows a row has been inserted
    select Id from xListItem where @@ROWCOUNT > 0 and Id = scope_identity()
End

Note: Delete and Update would be similar.

Тогда вы можете иметь в своей модели:

public class vListItem
{
    [Key]
    public int Id { get; set; }

    public string Text { get; set; }
    [ForeignKey("ListId")]
    public xList aList { get; set; }
    public int ListId { get; set; }
}

public class vPerson : vListItem
{
}
public class vThing : vListItem
{
}

На вашем DbContext вы можете иметь:

    public DbSet<vListItem> vitems { get; set; }
    public DbSet<vPerson> vpersons { get; set; }
    public DbSet<vThing> vthings { get; set; }

И, OnModelСоздавая вам определение:

modelBuilder.Entity<vListItem>()
    .Map<vPerson>(m => m.Requires("Discriminator").HasValue("1"))
    .Map<vThing>(m => m.Requires("Discriminator").HasValue("2"))
    ;

Вот так.Некоторые тесты:

    [TestMethod]
    public void TestMethodPersonInsert()
    {
        Entities db = new Entities();
        xList xl = db.lists.SingleOrDefault(x => x.Id == 1);
        vPerson vp = new vPerson();
        vp.Text = "p4";
        vp.aList = xl;
        db.vpersons.Add(vp);
        db.SaveChanges();
    }

    [TestMethod]
    public void TestMethodThingInsert()
    {
        Entities db = new Entities();
        xList xl = db.lists.SingleOrDefault(x => x.Id == 2);
        vThing vt = new vThing();
        vt.Text = "t0";
        vt.aList = xl;
        db.vthings.Add(vt);
        db.SaveChanges();
    }
2 голосов
/ 01 ноября 2011

Наиболее простым решением является введение перечисления, которое используется в качестве дискриминатора для представления типа. Пост, который вы упомянули, ясно описывает FK, не может быть использован, поэтому вам нужно рефакторировать его для чего-то, что можно применить. Это требует некоторого обслуживания, но так как вы также должны знать типы, которые происходят из вашего списка, я не вижу в этом серьезной проблемы. Другой вариант - отойти от TPH и использовать TablePerType. Таким образом, вам не нужно вводить перечисление, но вы будете создавать таблицу для каждого производного элемента списка.

0 голосов
/ 30 декабря 2011

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...