前言
一直以来对EF和EF Core都是单独分开来写,从未以比较的形式来讲解,如果您既用过EF 6.x也用过用EF Core是否有了解过EF和EF Core在插入数据时和返回主键有何异同呢?本篇博客是坐在电脑旁本打算写写EF 6.x插入数据注意的问题,心想何不比较二者呢?我也是在探索中(边敲代码边写博客中),下面我们来看看。
EF 6.x和EF Core插入数据异同
using (var ctx = new EfDbContext()) { ctx.Database.Log = Console.WriteLine; var customer = new Customer() { Email = "[email protected]", Name = "Jeffcky", Orders = new List() { new Order() { Code = "1", CreatedTime = DateTime.Now, ModifiedTime = DateTime.Now, Quantity = 10, Price =100 } } }; }
上述Customer和Order为一对多关系,Order实体中有Customer实体的外键,上述我们同时给Customer和Order赋了值,所以当我们插入Customer的同时Order表中也插入了数据,此时Order的CustomerId是Customer的主键,我们根本不需要为Order中的CustomerId显式赋值,这一点毋庸置疑。我想说的是如果两个表没有很强的关联关系,怎么说呢,换言之两个表没有配置所谓的关系又或许我们没有配置关系,一个表中列需要用到另外一个表的主键,那么这种的情况下,我们会以怎样的方式插入数据呢?实践是检验真理的唯一标准,下面我们来试试。
public class TestA { public int Id { get; set; } public string Other { get; set; } } public class TestB { public int Id { get; set; } public int TestAId { get; set; } public string Other { get; set; } }
protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Entity() .ToTable("TestAs") .HasKey(p => p.Id) .Property(p => p.Other); modelBuilder.Entity () .ToTable("TestBs") .HasKey(p => p.Id) .Property(p => p.Other); }
上述我们给出TestA和TestB,TestA和TestB没有任何关系,但是我们在插入TestB数据时需要得到Test的主键,那我们下面就进行如下数据添加。
using (var ctx = new EfDbContext()) { ctx.Database.Log = Console.WriteLine; var testA = new TestA() { Other = "other" }; ctx.TestAs.Add(testA); ctx.SaveChanges(); var testB = new TestB() { Other = "other", TestAId = testA.Id }; ctx.TestBs.Add(testB); ctx.SaveChanges(); }
此时我们看到提交后数据最终能够保存到数据库中,反观上述提交代码,我们首先是提交了TestA保存到数据库后然后拿到TestA的主键,然后再是提交TestB并保存到数据库中。那我们有没有考虑是否直接一次性提交呢,注释TestA提交,如下:
using (var ctx = new EfDbContext()) { ctx.Database.Log = Console.WriteLine; var testA = new TestA() { Other = "other" }; ctx.TestAs.Add(testA); //ctx.SaveChanges(); var testB = new TestB() { Other = "other", TestAId = testA.Id }; ctx.TestBs.Add(testB); ctx.SaveChanges(); }
WOW不行啊,下面我们来看看在EF Core中实现是不是可以,试试就知道了,别猜测。
modelBuilder.Entity(e => { e.ToTable("TestAs"); e.HasKey(p => p.Id); e.Property(p => p.Other); }); modelBuilder.Entity (e => { e.ToTable("TestBs"); e.HasKey(p => p.Id); e.Property(p => p.Other); });
using (var context = new EFCoreDbContext()) { var testA = new TestA() { Other = "other" }; context.TestAs.Add(testA); //ctx.SaveChanges(); var testB = new TestB() { Other = "other", TestAId = testA.Id }; context.TestBs.Add(testB); context.SaveChanges(); }
如果分两次提交那么无论是在EF 6.x还是EF Core中都是一样没有任何不同(在EF Core中没有测试也不用测试)。如果是一次性提交,此时在EF 6.x中的TestB中的TestAId为插入的是0,而EF Core中的TestB中的TestAId为-2147482647即INT类型最小值,至少找到了不同所在。Jeff自问自答的模式要来了,是不是就这样结束了呢?上述我们对TestA和TestB两个实体未配置任何关系,我们经过测试证明一次性提交并未达到我们预期,要是我们依然在不配置关系的前提下给出导航属性然后一次性提交呢,如下:
public class TestA { public int Id { get; set; } public string Other { get; set; } } public class TestB { public int Id { get; set; } public int TestAId { get; set; } public string Other { get; set; } public TestA TestA { get; set; } }
接下来我们在EF Core控制台再次运行上述代码看看,您思考下会不会将TestA中的主键添加进去呢。
如果您在EF 6.x中同样添加上述导航属性也是好使的,我就不测试了,那到此我们得出结论:若两个实体未显式配置任何关系但一个表需要得到另外一个表的主键,无论是在EF 6.x还是在EF Core中进行一次性提交不好使,只是在EF 6.x中一个表需要得到另外一个表的主键为0,而在EF Core中却是INT类型最小值,若我们显式配置了导航属性,那么无论是在EF 6.x还是EF Core中一次性提交可达到我们预期。
比较EF 6.x和EF Core插入数据返回主键
如果是主键为INT类型,默认情况无论是EF 6.x还是EF Core都将自动映射配置为自增长,要是我们显式配置了主键,那么对于EF 6.x和EF Core会有何不同呢?我们首先看看EF 6.x,如下(我们清除之前已提交数据):
using (var ctx = new EfDbContext()) { ctx.Database.Log = Console.WriteLine; var testA = new TestA() { Id = 1, Other = "other" }; ctx.TestAs.Add(testA); ctx.SaveChanges(); }
从上述我们提交三次看出,压根不叼我们设置的值,那么我们看看生成的SQL语句是怎样的呢,如下图:
这下我们明白了此时通过scope_identity返回为当前会话和当前作用域中的TestA表生成的最新标识值。接下来我们来看看EF Core。
using (var context = new EFCoreDbContext()) { var testA = new TestA() { Id = 1, Other = "other" }; context.TestAs.Add(testA); context.SaveChanges(); }
当我们进行第二次提交后将抛出异常,如下:
我们同样看看在EF Core中生成的SQL是怎样的。
原来如此没有返回当前会话自增长值,同时我们知道不能显式插入值,那就是关闭了IDENTITY_Insert。所以我们得出结论:在以INT作为主键且自增长时,在EF 6.x中能够显式给出值,而在EF Core中不能显式给定值即关闭了IDENTITY_INSERT不能显式插入主键值。
总结
本节我们详细叙述了EF 6.x和EF Core中插入数据和返回主键的不同之处,虽然作用不大,可以作为了解,最近比较累,可能会停更一小段时间,好好休息一下,书出版了会及时告知同行关注者。