设置实体框架与非主键字段的一对一映射
Posted
技术标签:
【中文标题】设置实体框架与非主键字段的一对一映射【英文标题】:Set Entity Framework one-to-one mapping with non primary key field 【发布时间】:2014-03-07 15:40:16 【问题描述】:我有以下两个类:
public class Blog
public int Id get; set;
public BlogDetail BlogDetail get; set;
public class BlogDetail
public int Id get; set;
public int BlogId get; set;
public Blog Blog get; set;
Blog 和BlogDetail 是一一对应的关系,外键是Blog.Id 到BlogDetail.BlogId。
BlogDetail.BlogId 是强制一对一关系的唯一约束。
BlogDetail.Id 列是必需的,因为另一个表会将 BlogDetail 引用为一对多。
我添加了以下映射:
public BlogMap()
HasKey(t => t.Id);
Property(t => t.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
public BlogDetailMap()
HasKey(t => t.Id);
Property(t => t.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
HasRequired(t => t.Blog).WithMany().HasForeignKey(e => e.BlogId);
我试图让 EF 6 使用无主键字段作为键来生成正确的 sql 查询,但无济于事。它给了我以下 sql 查询:
select *
from Blog
left join BlogDetail on Blog.BlogDetail_Id = BlogDetail.Id
实际上我需要的是:
select *
from Blog
left join BlogDetail on Blog.Id = BlogDetail.BlogId
有谁知道如何在 EF 中使用非主键字段实现一对一映射?我已尽一切可能无济于事。
如果有帮助,我将在 BlogDetail 上悬挂另一张桌子:
public class BlogDetailSupplier
public int Id get; set;
public int BlogDetailId get; set;
public BlogDetail BlogDetail get; set;
这就是为什么 BlodDetail 必须有一个 ID PK 列和一个 briefId FK 列。标准归一化。
【问题讨论】:
【参考方案1】:如果我理解正确,这就是你想要的:
public BlogMap()
HasOptional(b => b.BlogDetail).WithOptionalPrincipal(bd => bd.Blog);
这样BlogDetail
将拥有一个BlogId
外键,同时保持它自己的(身份)Id 字段,您可以在一对多关系中使用该字段。
更新 1
使用以下模型创建了一个新项目,通过应用上述映射,我能够实现您在问题中提出的问题:
public class Blog
public int Id get; set;
public string Name get; set;
public BlogDetail BlogDetail get; set;
public class BlogDetail
public int Id get; set;
public int PostCount get; set;
public Blog Blog get; set;
public class MyContext : DbContext
public DbSet<Blog> Blogs get; set;
public DbSet<BlogDetail> BlogDetails get; set;
protected override void OnModelCreating(DbModelBuilder modelBuilder)
modelBuilder.Entity<Blog>().HasOptional(b => b.BlogDetail).WithOptionalPrincipal(bd => bd.Blog);
public class ContextInitializer : DropCreateDatabaseIfModelChanges<MyContext>
protected override void Seed(MyContext db)
var blog1 = new Blog Name = "Blog 1", BlogDetail = new BlogDetail PostCount = 10 ;
var blog2 = new Blog Name = "Blog 2", BlogDetail = new BlogDetail PostCount = 28 ;
db.Blogs.AddRange(new List<Blog> blog1, blog2 );
在这里您可以看到查询及其结果:
这里是表格:
【讨论】:
你真的试过这个吗?它不起作用,并产生一个更疯狂的查询,其中包含三个连接到 blogdetail 并看起来加入 blog_id 这不起作用。 我在我的一个模型中遇到了同样的情况,它按预期工作。也许你做错了什么? 这不是我原来问题的答案。您所做的是首先使用代码,它自然生成 FK 字段作为_Id,在本例中为 blog_id,我可以从您的“答案”中清楚地看到。我们不是首先使用代码,而是使用映射来映射到已经存在的数据库。我们需要将 BlogDetail 上的字段称为 BlogId 而不是 Code 首先生成的 Blog_id。我没有做错任何事,我建议您重新阅读我发布的原始问题。 哇...你对寻求帮助的人来说太自大了。我回答的第一部分代码只是为了提供映射有效的证据。我的“答案”(如您所指)仍然对您有好处,只需简单的 .Map() 将字段名称设置为BlogId
。很抱歉帮助你,我会记住不要再试了。
@HenriqueMiranda 您的修复似乎“有效”,因为它提供了“0..1 到 0..1”的关系,并且不需要 EF 的流式 API 利用的共享主键对于 1 到 0..1 的关系。但是,我相信原始帖子想要后者(1 到 0..1),在这种情况下,EF 将强制在依赖实体上共享 PK 并使其成为 FK。【参考方案2】:
如果你在对象上
受保护的覆盖无效 OnModelCreating(ModelBuilder modelBuilder) modelBuilder.Entity()entity.HasOne().WithOne().HasForeignKey(x=>x.BlogId);
非常重要!! 当您进行查询时,_context.Blog.Include(inc=>inc.BlogDetail)
不要忘记 INCLUDE 否则对象将为空!!
【讨论】:
【参考方案3】:使用这些地图:
public BlogMap()
HasKey(t => t.Id);
public BlogDetailMap()
HasKey(t => t.Id);
HasRequired(t => t.Blog)
.WithMany()
.HasForeignKey(x => x.BlogId)
.WillCascadeOnDelete(false);
这几乎会产生您之后的结果:
MyContext.BlogDetails.Includes(x => x.Blogs);
选择在 BlogDetails 上,并使用您之后的 ID 加入博客。
SELECT
[Extent1].[id] AS [id],
[Extent1].[title] AS [title],
[Extent1].[BlogId] AS [BlogId],
[Extent2].[id] AS [id1],
[Extent2].[title] AS [title1],
[Extent2].[BlogDetail_id] AS [BlogDetail_id]
FROM [dbo].[BlogDetails] AS [Extent1]
INNER JOIN [dbo].[Blogs] AS [Extent2] ON [Extent1].[BlogId] = [Extent2].[id]
【讨论】:
恐怕这是我已经尝试过但不起作用的简单方法。它最终生成一个带有 Blog.Id = BlogDetail.Id 连接的查询。问题是 EF 使用两个表的主键作为不正确的连接。 嗯,这就是一对一关系的运作方式。依赖者的 ID 由其委托人的 ID 定义。 我在关于在 linq 中加入的答案中添加了更多信息。 我知道如何在 linq 中加入,但这不是我想要的。 BlogDetail 表的外键字段不一定是 BlogDetail 表的键。 另一个更新,在挖掘之后我意识到你正在正确地进行映射,这太疯狂了,但是哦,好吧。我只需要添加 WillCascadeOnDelete(false) 即可。以上是关于设置实体框架与非主键字段的一对一映射的主要内容,如果未能解决你的问题,请参考以下文章