我一直在圈子里玩这个并且似乎无法谷歌正确的答案 - 即使花了几个小时,所以你是我的最后手段!
在我的网络应用中,我想让用户使用不同的身份验证机制来访问他们的帐户。除了通常的用户/密码之外,我还想让他们使用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();
}
(不知道为什么但DbContext似乎没有尝试使用上述数据填充我的数据库,直到我明确地调用.SaveChanges()
...任何想法?)
在数据库中,EF没有定义Account
和OpenIdAccount
之间的任何关系。这是第一个不正确的警告标志;当然OpenIdAccount应该将Id
定义为指向Account.Id的PK和FK吗?
当.net尝试执行UpdateException
时,我得到以下.SaveChanges()
:
跨实体或关联共享的值 在多个位置生成。检查一下 映射不会将EntityKey拆分为多个 存储生成的列。
今天是我使用EF4和代码优先开发的第一天。花了很多时间阅读有关EF4 +代码优先+自定义映射的内容,我遇到了一个问题,在我再次开始之前,我一直处于困境并需要向正确的方向发挥作用:-)
所以我希望你们能够理解上述内容并解释我的一些愚蠢的错误/误解!
答案 0 :(得分:4)
不用担心,你是对的地方:)让我们进入你的问题:
(不知道为什么但DbContext似乎没有尝试使用上述数据填充我的数据库,直到我明确地调用.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