#sql-server #entity-framework-4.1
#sql-сервер #entity-framework-4.1
Вопрос:
[Вопрос:] Можно ли использовать FK в качестве дискриминатора в 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, поскольку его нельзя было бы использовать в качестве дискриминатора.
Мое решение до сих пор…
Этот вопрос и ответ объясняет, почему они приняли решение не разрешать FK в качестве дискриминатора.
Мой обходной путь до сих пор состоит в том, чтобы добавить в базу данных другое поле для использования в качестве дискриминатора, которому затем присваивается то же значение, что и ListID, с помощью триггера вставки (для обработки вставок, отличных от EF), а затем добавить навигационное свойство ListID в объект ListItem.
У кого-нибудь есть какие-либо предложения / альтернативы?
Ответ №1:
Наиболее простое решение заключается во введении перечисления, которое используется в качестве дискриминатора для представления типа. В упомянутом вами сообщении четко описывается, что FK не может быть использован, поэтому вам нужно преобразовать его во что-то, что может быть применено. Это требует некоторого обслуживания, но поскольку вы также должны знать типы, которые являются производными от вашего listitem, я не вижу в этом серьезной проблемы. Другим вариантом, который у вас есть, было бы отказаться от TPH и использовать TablePerType. Таким образом, вам не нужно вводить перечисление, но вы создадите таблицу для каждого производного элемента списка.
Комментарии:
1. Спасибо за ваш вклад, Карло. Мне следовало бы упомянуть, что я обнаружил, что TPT не страдает от этой проблемы, но на самом деле это не вариант в моем сценарии. Не могли бы вы подробнее рассказать о подходе Enum? Как это позволяет мне избежать добавления другого поля в БД в качестве дискриминатора? Другая проблема заключается в том, что это не эксклюзивная таблица и EF, устаревшие приложения все еще записывают в нее (отсюда и триггерный подход).
Ответ №2:
Предполагая, что сначала код 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; }
И при создании модели вы определяете:
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();
}
Ответ №3:
Я бы использовал несколько сопоставлений. Но суть проблемы заключается в том, чтобы вообще не иметь такого типа отношений и пересмотреть свой дизайн.