带抽象类的EF4 TPT - 定义关系

时间:2010-11-21 20:29:08

标签: .net entity-framework ef4-code-only

我一直在圈子里玩这个并且似乎无法谷歌正确的答案 - 即使花了几个小时,所以你是我的最后手段!

设置

在我的网络应用中,我想让用户使用不同的身份验证机制来访问他们的帐户。除了通常的用户/密码之外,我还想让他们使用Google的OpenId,Yahoo的OpenId,甚至是Facebook。映射到类中似乎相当简单:一个抽象Account,其中有几个<something>Account类继承了Account的一些基本属性。我从以下两个类开始:

public abstract class Account
{
    public int Id { get; set; }
    public int OwnerId { get; set; }

    public virtual Person Owner { get; set; }
}

public class OpenIdAccount : Account
{
    public string Identifier { get; set; }
}

作为一个完美主义者,并且在我的日常工作中做了很多db dev,我认为每种类型的表(TPT)将是最理想的选择。由于EF4默认使用TPH,因此在我定义的事物的DbContext端:

public class MySampleDb : DbContext
{
    public DbSet<Person> People { get; set; }
    public DbSet<Account> Accounts { get; set; }
    public DbSet<OpenIdAccount> OpenIdAccounts { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Account>().MapHierarchy(a => new
        {
            a.Id, 
            a.OwnerId
        }).ToTable("Account");

        modelBuilder.Entity<OpenIdAccount>().MapHierarchy(oid => new
        {
            oid.Id,
            oid.Identifier
        }).ToTable("OpenIdAccount");
    }
}

现在,我希望从一些简单的测试开始,并认为在每次运行中播种数据库是获得一致数据的好方法:

protected override void Seed(MySampleDb context)
{
    Person johns = new Person
    {
        Id = 1,
        Nickname = "John Skeet"
    };

    OpenIdAccount google = new OpenIdAccount
    {
        Id = 2,
        OwnerId = 1,
        Identifier = "https://www.google.com/accounts/o8/id?id=AItOawnmUz4e6QIn9mgd98WMAbnzC25sji5lpSM"
    };

    context.People.Add(johns);
    context.Accounts.Add(google);
    context.SaveChanges();
}

(不知道为什么但D​​bContext似乎没有尝试使用上述数据填充我的数据库,直到我明确地调用.SaveChanges() ...任何想法?)

问题

在数据库中,EF没有定义AccountOpenIdAccount之间的任何关系。这是第一个不正确的警告标志;当然OpenIdAccount应该将Id定义为指向Account.Id的PK和FK吗?

两个

当.net尝试执行UpdateException时,我得到以下.SaveChanges()

  

跨实体或关联共享的值   在多个位置生成。检查一下   映射不会将EntityKey拆分为多个   存储生成的列。

后续步骤

今天是我使用EF4和代码优先开发的第一天。花了很多时间阅读有关EF4 +代码优先+自定义映射的内容,我遇到了一个问题,在我再次开始之前,我一直处于困境并需要向正确的方向发挥作用:-)

所以我希望你们能够理解上述内容并解释我的一些愚蠢的错误/误解!

1 个答案:

答案 0 :(得分:4)

不用担心,你是对的地方:)让我们进入你的问题:

  

(不知道为什么但D​​bContext似乎没有尝试使用上述数据填充我的数据库,直到我明确地调用.SaveChanges()...任何想法?)

这正是它的设计工作方式。在 Seed 方法中,在将新对象添加到各自的DbSet后,必须调用 SaveChanges 。所以你在那里很好。

  

在数据库中,EF没有定义Account和OpenIdAccount之间的任何关系。

您的继承实现是每个具体类型的表或TPC继承 NOT TPT,它来自您使Account类为 abstract <的事实/ strong>以及您在帐户和OpenIdAccount之间没有一对一关系时所看到的是EF在 TPC 映射时的确切默认行为。 如果从Account类中删除abstract关键字,那么您将拥有 TPT ,并且您的代码将正常工作。

那么这是否意味着你应该放弃你的TPC并把它变成TPT?嗯,这当然是一个解决方案,但如果您仍然希望保留您的TPC,则无需继续使用它,因为完全可以使用TPC和Code First,我们只需对您的模型进行一些细微的更改即可这行得通。

解决方案:

TPC表示“为我的层次结构中的每个非抽象类型创建一个完全独立的表”。请注意,因为两个表之间没有外键,我们需要注意提供唯一键,因此我们必须关闭主键属性上的标识。有两种方法:

1。使用 System.ComponentModel.DataAnnotations中的DataAnnotations:

public abstract class Account 
{
    [StoreGenerated(StoreGeneratedPattern.None)]
    public int Id { get; set; }
    public int OwnerId { get; set; }
    public virtual Person Owner { get; set; }
}


2。使用FluentAPI:

modelBuilder.Entity<Account>().Property(a => a.Id)
    .StoreGeneratedPattern = System.Data.Metadata.Edm.StoreGeneratedPattern.None;

通过上述方法之一将其关闭后,您会看到您获得的异常消失,模型开始工作。

为了使它真正成为TPC,你应该映射每个表中的所有内容,因为在TPC中每个类都有一个表,并且每个表都有一个列,用于该类型的每个属性

modelBuilder.Entity<Account>().MapHierarchy(a => new {
    a.Id,
    a.OwnerId,
})
.ToTable("Accounts");

modelBuilder.Entity<OpenIdAccount>().MapHierarchy(o => new {
    o.Id,
    o.OwnerId,
    o.Identifier
})
.ToTable("OpenIdAccounts");


所有这一切,我认为TPC并不打算在这种情况下使用,你应该使用TPT。有关此主题的更详细讨论,您可以查看 Alex James 的这篇优秀文章:
How to choose an Inheritance Strategy