设置实体框架与非主键字段的一对一映射

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) 即可。

以上是关于设置实体框架与非主键字段的一对一映射的主要内容,如果未能解决你的问题,请参考以下文章

Hibernate多表关系配置——一对一关系映射

Hibernate映射

如何通过非主键创建一对一关系(EF First Code)

JPA映射关系

JPA:将 OneToOne Long 值(不是实体)映射到另一个不是主键的表列

SpringDataJPA使用Oracle序列设置值:主键字段、非主键字段