前言
本文來自和何鎮汐大哥的探討,很多時候我習慣於和別人交流過后會思考一些問題,無論是天馬行空還是淺薄的想法都會記錄下來,或許看到此博文的您能給我更多的思考,與人交流總能收獲很多東西,出發點不一樣則結論 不一樣,思維方式不一樣則路徑不一樣,願你我共同進步。
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既談不上精通更談不上不專家只不過是平時私下喜歡研究罷了,書中大部分都是我個人的理解,同時技術更新迭代太快,我也一直在追逐中而非停滯不前,我相信:無論出身環境怎樣,自身天賦如何,篤定都可以通過自身的努力來改變並且成長。