【问题标题】: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 列。标准归一化。

【问题讨论】:

    标签: c# entity-framework mapping


    【解决方案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) 即可。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-03-02
      • 1970-01-01
      • 2011-07-07
      • 1970-01-01
      • 2013-03-10
      • 2019-07-05
      • 2014-01-07
      相关资源
      最近更新 更多