Fork me on GitHub

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 数据库,会是这样:

可以看到 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();
    }
}

执行结果:

可以看到,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();
    }
}

执行结果:

这种处理方式,虽然“解决”上面的问题,但其实有很多的隐患,多执行一次 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 就无效了。

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

什么意思呢?就是实体更改了,需要进行 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();
    }
}

执行结果:

posted @ 2015-10-09 16:35  田园里的蟋蟀  阅读(10442)  评论(8编辑  收藏  举报