您好,登錄后才能下訂單哦!
當(dāng)我們利用EF Core查詢數(shù)據(jù)庫時如果我們不顯式關(guān)閉變更追蹤的話,此時實(shí)體是被追蹤的,關(guān)于變更追蹤我們下節(jié)再敘。就像我們之前在EF 6.x中討論的那樣,不建議手動關(guān)閉變更追蹤,對于有些特殊情況下,關(guān)閉變更追蹤可能會導(dǎo)致許多問題的發(fā)生。
對于EF Core 1.1中依然有四種狀態(tài),有的人說不是有五種狀態(tài)么,UnChanged、Added、Modified、Deleted、Detached。如果我們按照變更追蹤來劃分的話,實(shí)際上只有四種,將Detached排除在外,Detached不會被上下文所追蹤。那么狀態(tài)如何改變的呢?內(nèi)部有一個IStateManager接口,通過此接口來對實(shí)體狀態(tài)進(jìn)行管理,此時再取決于SaveChanges被調(diào)用后背后是如何進(jìn)行處理,我也就稍微看了下源碼,深入的東西沒去過多研究。
Added:實(shí)體還未插入到數(shù)據(jù)庫當(dāng)中,當(dāng)調(diào)用SaveChanges后將修改其狀態(tài)并將實(shí)體插入到數(shù)據(jù)庫。
UnChanged:實(shí)體存在數(shù)據(jù)庫中,但是在客戶端未進(jìn)行修改,當(dāng)調(diào)用SaveChanges后將忽略。
Modified:實(shí)體存在數(shù)據(jù)庫中,同時實(shí)體在客戶端也進(jìn)行了修改,當(dāng)調(diào)用SaveChanges后將更改其狀態(tài)并更新數(shù)據(jù)持久化到數(shù)據(jù)庫。
Deleted:實(shí)體存在數(shù)據(jù)庫中,當(dāng)調(diào)用SaveChanges方法后將刪除實(shí)體。
在EF Core 1.1中依然存在Add、Attach、Update方法,我們通過上下文或者DbSet<TEntity>能夠看到,當(dāng)將實(shí)體傳遞到這些方法中時,它們與實(shí)體追蹤可達(dá)圖緊密聯(lián)系在一起,比如說我們之前討論的博客的導(dǎo)航屬性文章的發(fā)表,當(dāng)我們添加文章的發(fā)表的這個實(shí)體時,然后調(diào)用Add方法后此時文章的發(fā)表這個實(shí)體也就被添加。在EF 6.x中我們說過當(dāng)我們調(diào)用Add等方法時EF內(nèi)部機(jī)制將會自動調(diào)用DetectChanges,但是在EF Core 1.1中則不再調(diào)用DetectChanges方法??湛跓o憑,我下載了源碼,如下:
public virtual void Add(TEntity item) { var entry = _stateManager.GetOrCreateEntry(item); if (entry.EntityState == EntityState.Deleted || entry.EntityState == EntityState.Detached) { OnCountPropertyChanging(); entry.SetEntityState(EntityState.Added); _count++; OnCollectionChanged(NotifyCollectionChangedAction.Add, item); OnCountPropertyChanged(); } }
上述我們沒有看到任何自動調(diào)用DetectChanges的邏輯,在EF 6.x中我們講到當(dāng)調(diào)用SaveChanges時此時會回調(diào)DetectChanges,而在EF Core 1.1中同樣也是如此,所以相對于EF 6.x而言,EF Core 1.1只是在SaveChanges時回調(diào)DetectChanges,在Add、Attacth、Update等方法則不再回調(diào)DetectChanges,這樣的話性能就會好很多。我們看到源代碼中調(diào)用SaveChanges時邏輯如下:
public virtual int SaveChanges(bool acceptAllChangesOnSuccess) { CheckDisposed(); TryDetectChanges(); try { return StateManager.SaveChanges(acceptAllChangesOnSuccess); } catch (Exception exception) {..} }
接下來我們再來看看當(dāng)調(diào)用Add、Update等方法時到底發(fā)生了什么。
Add:當(dāng)調(diào)用Add方法時就沒什么可說的了,此時將在圖中的對應(yīng)的所有實(shí)體推入到Added狀態(tài),也就說在調(diào)用SaveChanges時將會插入到數(shù)據(jù)庫中去。
Attach:當(dāng)調(diào)用Attach方法時將在圖中的所有實(shí)體推入到UnChanged狀態(tài),但是有一個額外情況,比如我們在一個類中添加導(dǎo)航屬性數(shù)據(jù)時,此時Attach的話將會使用混合模式,將此實(shí)體的狀態(tài)為UnChanged而導(dǎo)航屬性的狀態(tài)則是Added狀態(tài),所以當(dāng)插入到數(shù)據(jù)庫中時,這個已存在的數(shù)據(jù)將不會被保存,只有新添加的導(dǎo)航屬性數(shù)據(jù)才會被插入到數(shù)據(jù)庫中去。
Update:Update方法和Attach方法一樣只是將其狀態(tài)修改為Modified,而將新添加的實(shí)體的修改將進(jìn)行插入。
Remove:當(dāng)調(diào)用Remove方法時此時它只會影響傳遞給該方法的實(shí)體,不會去遍歷實(shí)體的可到達(dá)圖。如果一個實(shí)體的狀態(tài)是UnChanged或者M(jìn)odified,說明該實(shí)體已存在數(shù)據(jù)庫中,此時只需將其狀態(tài)修改為Deleted。如果實(shí)體的狀態(tài)為Added,此時說明該實(shí)體在數(shù)據(jù)庫中不存在,此時會脫離上下文而不被跟蹤。所以Remove方法側(cè)重強(qiáng)調(diào)實(shí)體要被追蹤,否則的話需要首先被Attach然后將其推入到Deleted狀態(tài)。
在EF Core 1.1中多了AddRanges、UpdateRanges等方法,它們和實(shí)際調(diào)用多次調(diào)用非Range方法其實(shí)是一樣的,它內(nèi)部也會去遍歷實(shí)體集合并更新其狀態(tài),如下:
public virtual void UpdateRange([NotNull] IEnumerable<object> entities) => SetEntityStates(Check.NotNull(entities, nameof(entities)), EntityState.Modified);
我們再看SetEntityStates這個方法的實(shí)現(xiàn)。
private void SetEntityStates(IEnumerable<object> entities, EntityState entityState) { var stateManager = StateManager; foreach (var entity in entities) { SetEntityState(stateManager.GetOrCreateEntry(entity), entityState); } }
EF Core內(nèi)部機(jī)制的處理肯定比我們之前手動去遍歷添加實(shí)體集合性能要高,意外看到一篇文章上有說僅僅只高效一點(diǎn),因?yàn)镽ange方法自動調(diào)用DetectChanges方法,找了半天也沒看見在哪里調(diào)用DetectChanges,郁悶,算是一點(diǎn)疑惑吧。
【注意】EF團(tuán)隊(duì)之前一直在承諾EF Core會更高效和更高可擴(kuò)展,但是我閱讀源碼發(fā)現(xiàn)內(nèi)部還是自動調(diào)用了DetectChanges,性能方面的話還是不算太高效,但是,但是源碼中已經(jīng)明確給出,關(guān)于DetectChanges方法,未來對于這個api會進(jìn)行更改或者徹底移除,源碼注釋如下:
/// <summary> /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// </summary> void DetectChanges([NotNull] IStateManager stateManager);
對于變更追蹤也好,默認(rèn)啟用變更追蹤也好,我們都是通過ChangeTracker屬性來獲取到,如下:
EFCoreContext efCoreContext; efCoreContext.ChangeTracker.AutoDetectChangesEnabled; efCoreContext.ChangeTracker.DetectChanges;
在ChangeTracker中也有一個重要的方法那就是如下:
efCoreContext.ChangeTracker.TrackGraph;
我們暫且起名為跟蹤圖吧,它是對實(shí)體狀態(tài)的完全控制,比如我們在將數(shù)據(jù)插入到數(shù)據(jù)庫之前想設(shè)置其某一個值為臨時值,我們就可以通過該方法來實(shí)現(xiàn)。
Blog blog; using (var efCoreContext = new EFCoreContext(options)) { efCoreContext.ChangeTracker.TrackGraph(blog, node => { var entry = node.Entry; if ((int)entry.Property("Id").CurrentValue < 0) { entry.State = EntityState.Added; entry.Property("Id").IsTemporary = true; } else { entry.State = EntityState.Modified; } }); }
在EF Core 1.1其余的就是關(guān)于Add、Update等方法的異步操作了,對于操作數(shù)據(jù)庫不至于阻塞的情況也還是挺好的。
關(guān)于EF Core 1.1中一些基本的知識我們過了一遍,下面我們來看看這些方法到底該如何高效使用呢?
關(guān)于這個方法就沒有太多敘述的了,對應(yīng)的則是異步方法。我們重點(diǎn)看看其他的方法。
當(dāng)我們根據(jù)主鍵去更新所有實(shí)體這個so easy了,我們在Blog表添加如下數(shù)據(jù)。
(1)更新方式一
現(xiàn)在我們查出Id=1的實(shí)體,然后將Name進(jìn)行修改如下:
IBlogRepository _blogRepository; public HomeController(IBlogRepository blogRepository) { _blogRepository = blogRepository; } public IActionResult Index() { var blog = _blogRepository.GetSingle(d => d.Id == 1); blog.Name = "EntityFramework Core 1.1"; _blogRepository.Commit(); return View(); }
上述我們直接查詢出來主鍵對應(yīng)的實(shí)體然后修改其值,最后提交更新其實(shí)體的對應(yīng)修改的屬性。最后順理成章的數(shù)據(jù)字段進(jìn)行了修改
我們知道因?yàn)椴樵兂鰜淼膶?shí)體在未關(guān)閉變更追蹤的情況下始終都是被追蹤的,所以必須進(jìn)行對應(yīng)修改,但是要是下面的情況呢。
public IActionResult Index(int Id,Blog blog) { return Ok(); }
在客戶端對數(shù)據(jù)進(jìn)行了修改,我們需要根據(jù)主鍵Id進(jìn)行對應(yīng)屬性修改,當(dāng)然不希望多此一舉的話,我們可以根據(jù)主鍵Id去查詢對應(yīng)的實(shí)體,然后將屬性進(jìn)行賦值最后提交修改保存到數(shù)據(jù)庫中,大概就演變成如下情況。
public IActionResult Index(int Id,Blog blog) { var oldBlog = _blogRepository.GetSingle(d => d.Id == Id); oldBlog.Name = blog.Name; oldBlog.Url = blog.Url; _blogRepository.Commit(); return Ok(); }
誠然上述方法能達(dá)到我的目的,其實(shí)還有簡便的方法,如下:
(2)更新方式二
既然有簡單的方法為何我們不用呢,這樣的場景就是更新指定屬性,以往的情況都是自己封裝一個Update方法,然后利用反射去包含需要修改的屬性接著更改其屬性的狀態(tài)為修改,最后提交修改即可。是的,這就是我們說的方法,但是,但是在EF Core 1.1中完全不需要我們?nèi)シ庋b,我們需要做的只是封裝成一個通用方法即可,內(nèi)置實(shí)現(xiàn)EF Core已經(jīng)幫我們實(shí)現(xiàn),我們來看看。
void Update(T entity, params Expression<Func<T, object>>[] properties);
很熟悉吧,我們在基倉儲接口給出這樣一個接口,接著我們來實(shí)現(xiàn)此接口,如下:
public void Update(T entity, params Expression<Func<T, object>>[] properties) { _context.Entry(entity).State = EntityState.Unchanged; foreach (var property in properties) { var propertyName = ExpressionHelper.GetExpressionText(property); _context.Entry(entity).Property(propertyName).IsModified = true; } }
是不是夠簡單粗暴,開源就是好啊,查找資料時發(fā)現(xiàn)老外已經(jīng)給出了具體實(shí)現(xiàn),當(dāng)直接調(diào)用時居然發(fā)現(xiàn)已經(jīng)給我們封裝了,接下來我們再來修改指定的屬性就變成了如下:
public IActionResult Index() { var blog = new Blog() { Id = 1, Name = "EntityFramework Core 1.1" }; _blogRepository.Update(blog, d => d.Name); _blogRepository.Commit(); return Ok(); }
上述只是演示,實(shí)際項(xiàng)目當(dāng)中時我們只需給出我們修改的主鍵和實(shí)體即可。如果是修改實(shí)體集合的話,再重載一個遍歷就ok。到這里你是不是發(fā)現(xiàn)已經(jīng)非常完美了,還有更完美的解決方案,請繼續(xù)往下看。
(3)更新方式三
其實(shí)在ASP.NET Core MVC中有比上面進(jìn)一步還爽的方式通過利用TryUpdateModelAsync方法來實(shí)現(xiàn),此方法有多個重載來實(shí)現(xiàn),完全不需要我們?nèi)シ庋b。如下:
public async Task<IActionResult> Index() { var blog = _blogRepository.GetSingle(d => d.Id == 1); blog.Name = "EntityFramework Core 1.1"; await TryUpdateModelAsync(blog, "", d => d.Name); _blogRepository.Commit(); return Ok(); }
上述三種更新方式各有其應(yīng)用場景,如果必須要總結(jié)的話就主要是第二種方式和第三種方式該如何取舍,第二種方式通過我們手動封裝的方式不需要再進(jìn)行查詢,直接更改其狀態(tài)進(jìn)行提交更新即可,而第三種方式需要進(jìn)行查詢才會被追蹤最終提交更新,看個人覺得哪種方式更加合適就取哪種吧。關(guān)于EF Core 1.1中對于數(shù)據(jù)更新我們就講解完了,我們再來看看刪除。
對于上述和更新一樣如果該實(shí)體已經(jīng)被變更追蹤,直接調(diào)用內(nèi)置的方法Delete方法即可,大部分場景下是根據(jù)主鍵去刪除數(shù)據(jù)。這里有兩種方式供我們選擇,請往下看。
(1)刪除方式一
public IActionResult Index() { var blog = _blogRepository.GetSingle(d => d.Id == 1); _blogRepository.Delete(blog); _blogRepository.Commit(); return Ok(); }
我們查詢出需要刪除的實(shí)體,然后通過調(diào)用Remove(這里我封裝了)方法將其標(biāo)識為Deleted狀態(tài)進(jìn)行刪除,當(dāng)查詢數(shù)據(jù)我們可以關(guān)閉變更追蹤,一來數(shù)據(jù)量大的話對內(nèi)存壓力不會太大,二來因?yàn)檎{(diào)用Remove方法會將其標(biāo)識為Deleted狀態(tài)也會被追蹤,不會有任何問題。
(2)刪除方式二【推薦】
為了盡量減少請求時間,我們能一步完成的何必要用兩步呢,我們完全可以直接實(shí)例化一個實(shí)體,將其主鍵賦值,最后修改其狀態(tài)為Deleted,最終將持久化到數(shù)據(jù)庫中刪除對應(yīng)的數(shù)據(jù)。如下:
public IActionResult Index() { var blog = new Blog() { Id = 1 }; _blogRepository.Delete(blog); _blogRepository.Commit(); return Ok(); }
最后還剩下一個查詢沒有講述,這個和添加方法一樣,比較簡單我們稍微過一下即可。由于在EF Core中不再支持延遲加載,所以我們需要通過Include顯式獲取我們需要的導(dǎo)航屬性,比如如下:
DbContext dbContext; dbContext.Set<Blog>().Include(d => d.Posts);
如果有多個導(dǎo)航屬性,我們接著進(jìn)行ThenInclude,如下:
DbContext dbContext; dbContext.Set<Blog>().AsNoTracking().Include(d => d.Posts).ThenInclude(d => d....).
為了避免這樣多次ThenInclude,方便調(diào)用我們進(jìn)行如下封裝即可:
public T GetSingle(Expression<Func<T, bool>> predicate, params Expression<Func<T, object>>[] includeProperties) { IQueryable<T> query = _context.Set<T>(); foreach (var includeProperty in includeProperties) { query = query.Include(includeProperty); } return query.Where(predicate).FirstOrDefault(); }
此時我們只需要進(jìn)行對應(yīng)調(diào)用即可,大概如下:
_blogRepository.GetSingle( d=>d.Id == 1, p=>p.Posts, p=>....)
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。