【问题标题】:Using Foreign Key (FK) as Discriminator for Table-Per-Hierarchy (TPH)使用外键 (FK) 作为每层表 (TPH) 的鉴别器
【发布时间】:2011-10-24 06:35:26
【问题描述】:

[Q:] 是否可以在 EF 中使用 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 独占使用(即无法更改)的现有 DB 具有如下字段:

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 的 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 作为鉴别器。

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

有人有任何建议/替代方案吗?

【问题讨论】:

    标签: sql-server entity-framework-4.1


    【解决方案1】:

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

    【讨论】:

    • 感谢您的意见,卡罗。我应该提到我发现 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; }
    

    而且,您定义的 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();
        }
    

    【讨论】:

      【解决方案3】:

      我会使用多个映射。但是,问题的核心是根本不存在这种类型的关系并重新审视您的设计。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2023-03-21
        • 1970-01-01
        • 2014-11-21
        • 2016-06-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多