c# - 如何在实体框架 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
解决方案
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 到其依赖项。
顺便说一句:我从这个映射示例中的实体中删除了所有注释。
推荐阅读
- javascript - HTML5 Geolocation 未在 Safari 浏览器上提示位置
- javascript - 书签以查看隐藏的选择
- linux - 在当前目录中的所有目录和子目录中搜索与文件扩展名列表匹配的文件。将这些文件复制到维护文件结构的新目录
- java - 来自 Azure 函数和应用服务的输出 JSON 数组被拆分为单独的消息
- azure-devops - 如何获取 Azure DevOps Pipelines 任务的任务版本?
- driver - 中断处理程序未在为其安装的中断上执行
- django-rest-framework - 关联组或用户注册权限
- c# - 如何在 Xamarin Forms 中使用自定义字体(任何外部字体)作为嵌入资源?
- r - 如何选择两个因素并执行回归
- java - 如何在java中设置Timer来连接Mysql