[Q:] Можно ли использовать FK в качестве дискриминатора в EF и какие обходные пути придумали люди?
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 .
Есть ли у кого-нибудь предложения / альтернативы?
Предполагая, что сначала код 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();
}