首页 > 解决方案 > 如何在实体框架 6 中创建两侧工作导航属性 1:1

问题描述

public class Person 
{
    public int ID { get; set; }//PK
    
    [Required]
    public Heart Heart { get; set; }
}

public class Heart
{
    public int ID { get; set; }//PK

    public int PersonID { get; set; }//FK
    
    [ForeignKey(nameof(PersonID))]
    public Person Person { get; set; }
}

//Lists for example
var persons = new List<Person>();
persons.Add(new Person() { ID = 1 });
persons.Add(new Person() { ID = 2});

var hearts = new List<Heart>();
hearts.Add(new Heart() { ID = 1, PersonID = 2 }));
hearts.Add(new Heart() { ID = 2, PersonID = 1 }));

所以我尝试得到心脏,我想包括它的导航属性人。

    hearts.Where(h => h.ID == 1).Include(h => h.Person);
    //query generated
    // SELECT * FROM `hearts` AS `Extent1` INNER JOIN `persons` AS `Extent2` ON `Extent1`.`PersonID` = `Extent2`.`ID
    //here we get the correct navigation property heart.PersonID = person.ID which is Person with ID 2;

然后我尝试获取 Person 并且我想包含它的导航属性 Heart

persons.Where(p => p.ID == 1).Include(p => p.Heart);
//query generated
// SELECT * FROM `persons` AS `Extent1` INNER JOIN `hearts` AS `Extent2` ON `Extent1`.`ID` = `Extent2`.`ID
//here we get wrong result person.ID = heart.ID; we get heart with ID = 1 instead of ID = 2

因此,当我使用包含时,它需要我首先尝试获取的实体的外键,当我使用包含作为导航属性时,它总是将第一个实体 FK 与包含的实体 PK 进行比较。

我想当我带人时得到正确的心。

我正在使用实体框架 6

标签: c#entity-framework

解决方案


1:1 应该共享密钥(在 EF 中)所以这会起作用(使用 Heart.PersonID 作为密钥):

using System;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity;
using System.Diagnostics;
using System.Linq;

namespace SoAns
{
    /***************************************
    
    Database:

    SELECT * FROM dbo.Persons

        ID
    -----------
    1
    2


    SELECT * FROM dbo.Hearts

    ID          PersonID
    ----------- -----------
    2           1
    1           2

    ***************************************/

    public class Person
    {
        public int ID { get; set; }//PK
        public Heart Heart { get; set; }
    }

    public class Heart
    {
        public int ID { get; set; }//PK.. in the database, not in the mapping!
        public int PersonID { get; set; } //FK, but this is a 1:1
        public Person Person { get; set; }
    }

    public class Db : DbContext
    {
        public DbSet<Person> Persons { get; set; }
        public DbSet<Heart> Hearts { get; set; }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);

            modelBuilder.Entity<Person>().ToTable("persons");
            modelBuilder.Entity<Person>().HasKey(p => p.ID);
            modelBuilder.Entity<Person>().Property(p => p.ID).HasColumnName("ID").HasColumnType("int").HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity).IsRequired();

            modelBuilder.Entity<Heart>().ToTable("hearts");
            modelBuilder.Entity<Heart>().HasKey(h => h.PersonID);
            modelBuilder.Entity<Heart>().Property(h => h.ID).HasColumnName("ID").HasColumnType("int").HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity).IsRequired();
            modelBuilder.Entity<Heart>().Property(h => h.PersonID).HasColumnName("PersonID").HasColumnType("int").IsRequired();

            modelBuilder.Entity<Heart>().HasRequired(h => h.Person).WithOptional(p => p.Heart);
        }

        public Db(string connectionString) : base(connectionString)
        {
            this.Database.Log = Console.WriteLine;
        }
    }

    public class Program
    {
        private static void Main()
        {
            var db = new Db(@"Server=.\sqlexpress;Database=PersonHeart;Integrated Security=True");

            var heart = db.Hearts.Where(h => h.ID == 1).Include(h => h.Person).First();
            var person = db.Persons.Where(p => p.ID == 1).Include(p => p.Heart).First();

            Debug.Assert(heart.ID == 1 && heart.PersonID == 2 && heart.Person.ID == 2);
            Debug.Assert(person.ID == 1 && person.Heart.ID == 2);
        }
    }
}

但是,这不会以任何方式保护您免受以下数据库的侵害:

  • 具有相同 PersonID 的多个心
  • 没有相应 Heart 行的人

为了稍微改进您的数据库设计,您可以:

  • 共享 Person 使用的密钥。(去掉Heart中的列ID,使用PersonID作为PK和FK)
  • 确定聚合根并为该行提供唯一的 PK 和非空 FK 到其依赖项。

顺便说一句:我从这个映射示例中的实体中删除了所有注释。


推荐阅读