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

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

Сценарий

EF Objects

public class List
{
    public int Id { get; set; }
    public string Name { get; set; }
    public ICollection 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 .

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

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

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

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

Мое решение на данный момент ...

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

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

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

26
задан JimmyJames 3 January 2012 в 06:56
поделиться

1 ответ

Предполагая, что сначала код 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
ответ дан 28 November 2019 в 17:31
поделиться
Другие вопросы по тегам:

Похожие вопросы: