EntityFramework 外键值映射

时间:2023-03-09 16:10:32
EntityFramework 外键值映射

如果在 EF OnModelCreating 中配置了实体外键映射,也就是 SQL Server 中的 ForeignKey,那么我们在添加实体的时候,主实体的主键值会自动映射到子实体的外键值,并且这个操作在一个 SaveChanges 中,但如果没有在 OnModelCreating 中进行外键映射配置,我们添加实体的时候,就不会自动映射外键值了,什么意思呢?我们先看一个示例代码:

public class SchoolDbContext : DbContext
{
public SchoolDbContext()
: base("db_school")
{ } public DbSet<Student> Students { get; set; }
public DbSet<Class> Classs { get; set; } protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Student>()
.HasKey(n => n.StudentId); modelBuilder.Entity<Class>()
.HasKey(n => n.ClassId); base.OnModelCreating(modelBuilder);
}
} public class Student
{
public int StudentId { get; set; }
public int ClassId { get; set; }
public string Name { get; set; }
} public class Class
{
public int ClassId { get; set; }
public string Name { get; set; }
}

示例很简单,Class 和 Student 是一对多关系,但我们并没有在 OnModelCreating 中进行外键映射配置,所以生成到 SQL Server 的 db_school 数据库,会是这样:

EntityFramework 外键值映射

可以看到 Student 中的 ClassId 字段并不是外键,下面我们添加 Student 和 Class 实体:

static void Main(string[] args)
{
using (var context = new SchoolDbContext())
{
var entityClass = new Class() { Name = "calss1" };
var entityStudent = new Student() { ClassId = entityClass.ClassId, Name = "student1" };
context.Classs.Add(entityClass);
context.Students.Add(entityStudent);
context.SaveChanges();
}
}

执行结果:

EntityFramework 外键值映射

可以看到,Student 表中的 ClassId 值是 0,而并不是我们预想的 1,这是一个问题,在不增加外键的情况下,我们一般会这样解决:

static void Main(string[] args)
{
using (var context = new SchoolDbContext())
{
var entityClass = new Class() { Name = "calss2" };
context.Classs.Add(entityClass);
context.SaveChanges(); var entityStudent = new Student() { ClassId = entityClass.ClassId, Name = "student2" };
context.Students.Add(entityStudent);
context.SaveChanges();
}
}

执行结果:

EntityFramework 外键值映射

这种处理方式,虽然“解决”上面的问题,但其实有很多的隐患,多执行一次 SaveChanges,EF 就会多发起一次请求,增加了性能开销,并且 SaveChanges 是事务性的,如果第一个执行成功了,第二个执行失败了,这时候第一个事务并不会回滚,因为它独立于第二个,所以,最后就会造成数据的不一致性,虽然几率非常点,但我们应该尽量避免。

那有没有更好的解决方式呢?这个问题我之前有点想复杂了,其实解决非常简单,就是在 Student 实体中添加 virtual 修饰的 Class 属性,就可以了,如下:

public class Student
{
public int StudentId { get; set; }
public int ClassId { get; set; }
public string Name { get; set; } public virtual Class Class { get; set; }//添加属性
}

需要注意的是,我们并不需要在 OnModelCreating 中进行 Class 和 ClassId 的映射配置,EF 会自动查找 ClassId(属性名 + Id),所以,“外键”命名要注意规范统一,如果命名为 Class_Id 就无效了。

再次执行添加实体的代码,发现会报错:

EntityFramework 外键值映射

什么意思呢?就是实体更改了,需要进行 EF 迁移,如果你进行 EF 迁移的话,会发现,虽然我们没有在 OnModelCreating 中进行 ClassId 外键映射配置,但 EF 也会自动映射 ForeignKey 到数据库的,所以代码命名尽量规范些,EF 是比较“智能”的。

我们解决这个问题的前提条件是“不增加外键配置”,所以我们要让 EF 忽略实体更改:

public SchoolDbContext()
: base("db_school")
{
Database.SetInitializer<SchoolDbContext>(null);//忽略映射
}

再次执行添加实体代码:

static void Main(string[] args)
{
using (var context = new SchoolDbContext())
{
var entityClass = new Class() { Name = "calss3" };
var entityStudent = new Student() { ClassId = entityClass.ClassId, Name = "student3" };
context.Classs.Add(entityClass);
context.Students.Add(entityStudent);
context.SaveChanges();
}
}

执行结果:

EntityFramework 外键值映射