使用外键(FK)作为每层次表(TPH)的鉴别器

时间:2011-10-24 06:35:27

标签: sql-server entity-framework-4.1

[问:]是否可以在F中使用FK作为鉴别器,人们会提出哪些变通办法?

场景

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

期望的结果

我希望List of Id成为 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在创建新的 ListItem 实例时尝试插入List_Id。 List_Id不是一个字段,我不能将 ListId 映射为 ListItem 上的属性,因为它不能用作鉴别器。

到目前为止我的解决方案......

This Q&A解释了为什么他们决定不允许FK作为鉴别者。

到目前为止,我的解决方法是向数据库添加另一个字段以用作鉴别器,然后使用插入触发器(处理非EF插入)将其设置为与 ListId 相同的值然后将 ListId 导航属性添加到 ListItem 实体。

有没有人有任何建议/选择?

3 个答案:

答案 0 :(得分:2)

最容易解决的问题在于引入一个用作鉴别器的枚举来表示类型。您提到的帖子清楚地描述了FK无法使用,因此您需要将其重构为可应用的内容。它需要一些维护,但由于你还必须知道从listitem派生的类型,我不认为这是一个主要问题。你有的其他选择是远离TPH并使用TablePerType。这样你实际上不必引入枚举,但是你将为每个派生的列表项目增加一个表。

答案 1 :(得分: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; }

而且,OnModelCreating你定义:

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 :(得分:0)

我会使用多个映射。但是,问题的核心是根本没有这种关系,重新审视你的设计。