EntityFramework Core指定更新导航属性了解一下?
前言
本文来自和何镇汐大哥的探讨,很多时候我习惯于和别人交流过后会思考一些问题,无论是天马行空还是浅薄的想法都会记录下来,或许看到此博文的您能给我更多的思考,与人交流总能收获很多东西,出发点不一样则结论 不一样,思维方式不一样则路径不一样,愿你我共同进步。
EntityFramework Core无跟踪视图
首先依然给出本文需要用到的两个实体,如下:
public class Blog { public int Id { get; set; } public string Name { get; set; } public string Url { get; set; } public DateTime CreatedTime { get; set; } public DateTime ModifiedTime { get; set; } public byte Status { get; set; } public bool IsDeleted { get; set; } public ICollection<Post> Posts { get; set; } = new List<Post>(); }
public class Post { public int Id { get; set; } public string Name { get; set; } public DateTime CreatedTime { get; set; } public DateTime ModifiedTime { get; set; } public int BlogId { get; set; } public Blog Blog { get; set; } }
在EF Core中给我们提供了Update和UpdateRange方法,这两个方法你说作用大吧,我看作用也没有那么大。要利用这两个方法,必须对值进行一一赋值,如下:
var dbContext = new EFCoreDbContext(); var dbBlog = dbContext.Blogs.Include(d => d.Posts).FirstOrDefault(d => d.Id == 1); dbBlog.Name = "Jeffcky"; foreach (var post in dbBlog.Posts) { post.Name = "《你必须掌握的EntityFramework 6.x与Core 2.0》"; } dbContext.Update(dbBlog); dbContext.SaveChanges();
在EF 6.x中缺失Update和UpdateRange方法,但是它可以进行如下更新啊不是。
var dbContext = new EFCoreDbContext(); var newBlog = new Blog() { Id = 1, Name = "Jeffcky1", IsDeleted = false, Status = 0, Url = "https://www.cnblogs.com/CreateMyself", CreatedTime = DateTime.Now, ModifiedTime = DateTime.Now, Posts = new List<Post>() { new Post() { Id = 1, BlogId = 1, Name = "EF Core TrackGraph", CreatedTime = DateTime.Now, ModifiedTime = DateTime.Now } } }; var dbBlog = dbContext.Blogs.Include(d => d.Posts).FirstOrDefault(d => d.Id == 1); dbContext.Entry(dbBlog).CurrentValues.SetValues(newBlog); dbContext.SaveChanges();
不太了解详情的童鞋可能就说了在EF Core中也可以利用上述CurrentValues来指定更新列啊,如果您这样想那就大错特错了,来我们在EF Core中同样运行上述代码通过对比前后表中数据看看。
更新前
更新后
我们通过对比可看到,导航属性对应的表没有进行更新,不要问我为啥,在前面我也有讲过在EF Core中这种情况类似于和添加一样通过手动这是状态为Added,在EF 6.x中只要更新主表则对应与之相关的导航属性也会更新,但是在EF Core中只会更新主表,EF 6.x这么好的指定更新反而被剔除了,实在不应该啊。有人说赋值两次啊,不好意思也不行,如下:
var dbContext = new EFCoreDbContext(); var newBlog = new Blog() { Id = 1, Name = "Jeffcky1", IsDeleted = false, Status = 0, Url = "https://www.cnblogs.com/CreateMyself", CreatedTime = DateTime.Now, ModifiedTime = DateTime.Now, Posts = new List<Post>() { new Post() { Id = 1, BlogId = 1, Name = "EF Core TrackGraph", CreatedTime = DateTime.Now, ModifiedTime = DateTime.Now } } }; var dbBlog = dbContext.Blogs.Include(d => d.Posts).FirstOrDefault(d => d.Id == 1); dbContext.Entry(dbBlog).CurrentValues.SetValues(newBlog); dbContext.Entry(dbBlog.Posts).CurrentValues.SetValues(newBlog.Posts); dbContext.SaveChanges();
上述这种方式对关系映射是不行的,但是若是复杂属性则是可以,如下:
[Owned] public class StreetAddress { public string Street { get; set; } public string City { get; set; } } public class Order { public int Id { get; set; } public StreetAddress ShippingAddress { get; set; } }
var dbContext = new EFCoreDbContext(); var order = dbContext.Orders.FirstOrDefault(); order.ShippingAddress.City = "city"; order.ShippingAddress.Street = "street"; dbContext.SaveChanges();
这样更新肯定是可以的,我们不做过多探讨,利用CurrentValues只能进行两次赋值才行,如下。
var newOrder = new Order() { Id = 1, ShippingAddress = new StreetAddress() { City = "city", Street = "street" } }; var dbContext = new EFCoreDbContext(); var order = dbContext.Orders.FirstOrDefault(); dbContext.Entry(order).CurrentValues.SetValues(newOrder); dbContext.Entry(order.ShippingAddress).CurrentValues.SetValues(newOrder.ShippingAddress); var result = dbContext.SaveChanges();
让我们再次回到更新Blog,除了利用CurrentValues指定更新外,我们还可以在查询Posts时不进行显式加载,然后调用直接将更新newBlog赋值与dbBlog,这种方式和手动赋值本质一样,但是至少不用一一赋值不是,如下:
var dbContext = new EFCoreDbContext(); var newBlog = new Blog() { Id = 1, Name = "Jeffcky1", IsDeleted = false, Status = 0, Url = "https://www.cnblogs.com/CreateMyself", CreatedTime = DateTime.Now, ModifiedTime = DateTime.Now, Posts = new List<Post>() { new Post() { Id = 1, BlogId = 1, Name = "EF Core TrackGraph", CreatedTime = DateTime.Now, ModifiedTime = DateTime.Now } } }; var dbBlog = dbContext.Blogs .AsNoTracking() .Include(d => d.Posts).FirstOrDefault(d => d.Id == 1); dbBlog = newBlog; dbContext.Update(dbBlog); var result = dbContext.SaveChanges();
说了这么多在EF Core中对于指定更新列不是太友好,当属性过多利用手动赋值就太麻烦,应该保留EF 6.x中利用CurrntValues对导航属性也进行直接更新岂不更好,如果调用Update方法将当前实体与快照中的实体比较指定更新列应该才是最佳方案。
Include....ThenInclude加载导航属性是否为最佳方案呢?
我们看如下三个示例实体
public class A { public int Id { get; set; } public ICollection<B> Bs { get; set; } } public class B { public int Id { get; set; } public C C { get; set; } } public class C { public int Id { get; set; } }
此时我们来查询A并通过显式加载B和C,如下:
var dbContext = new EFCoreDbContext(); var As = dbContext.As.Include(d => d.Bs).ThenInclude(d => d.C).ToList();
大部分查询我们都会进行如上查询,但是我们是否思考是上述是否为最佳方案呢?或者性能更好呢?我也不知道,我也只是纯属猜测,因为要是我们进行如下加载数据呢?
static void IncludeLoadCollection(EFCoreDbContext dbContext, object obj) { var entityEntry = dbContext.Entry(obj); foreach (var collection in entityEntry.Collections) { if (collection.IsLoaded) { continue; } collection.Load(); if (collection.CurrentValue != null) { foreach (var child in collection.CurrentValue) { IncludeLoadCollection(dbContext, child); } } } }
var dbContext = new EFCoreDbContext(); var a = dbContext.As.FirstOrDefault(); IncludeLoadCollection(dbContext, a);
如上代码未经测试,只是作为个人思考而给,您看到后私下可自行测试对比上述方案和通过Include....ThenInclude哪种方案更好呢?本文稍微讲解了下个人认为EF Core对于指定更新没有一个恰当的方式除了手动更新列外,当然字段太多,大部分情况下都会借助AutoMapper等进行DTO。
出版购买通知
现京东和淘宝上可正式预售购买《你必须掌握的EntityFramework 6.x与Core 2.0》书籍,我博客右上方也给了一个购买链接,让各位久等了。感谢各位同行一直以来的大力支持,同时也再次感谢博客园这个大平台,给了我机会去分享技术,我对EF既谈不上精通更谈不上不专家只不过是平时私下喜欢研究罢了,书中大部分都是我个人的理解,同时技术更新迭代太快,我也一直在追逐中而非停滞不前,我相信:无论出身环境怎样,自身天赋如何,笃定都可以通过自身的努力来改变并且成长。