溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

EntityFramework Core 1.1 Add、Attach、Update、Remove方法如何高效使用詳解

發(fā)布時間:2020-06-07 18:47:05 來源:網(wǎng)絡(luò) 閱讀:389 作者:xsster 欄目:數(shù)據(jù)庫

EntityFramework Core 1.1方法理論詳解

當(dāng)我們利用EF Core查詢數(shù)據(jù)庫時如果我們不顯式關(guān)閉變更追蹤的話,此時實(shí)體是被追蹤的,關(guān)于變更追蹤我們下節(jié)再敘。就像我們之前在EF 6.x中討論的那樣,不建議手動關(guān)閉變更追蹤,對于有些特殊情況下,關(guān)閉變更追蹤可能會導(dǎo)致許多問題的發(fā)生。

實(shí)體狀態(tài)

對于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í)體。

實(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憑,我下載了源碼,如下:

EntityFramework Core 1.1 Add、Attach、Update、Remove方法如何高效使用詳解

        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();
            }
        }

EntityFramework Core 1.1 Add、Attach、Update、Remove方法如何高效使用詳解

上述我們沒有看到任何自動調(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時邏輯如下:

EntityFramework Core 1.1 Add、Attach、Update、Remove方法如何高效使用詳解

        public virtual int SaveChanges(bool acceptAllChangesOnSuccess)
        {
            CheckDisposed();            TryDetectChanges();            try
            {                return StateManager.SaveChanges(acceptAllChangesOnSuccess);
            }            catch (Exception exception)
            {..}
         }

EntityFramework Core 1.1 Add、Attach、Update、Remove方法如何高效使用詳解

接下來我們再來看看當(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)。

Range方法 

在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)。

EntityFramework Core 1.1 Add、Attach、Update、Remove方法如何高效使用詳解

        private void SetEntityStates(IEnumerable<object> entities, EntityState entityState)
        {            var stateManager = StateManager;            foreach (var entity in entities)
            {
                SetEntityState(stateManager.GetOrCreateEntry(entity), entityState);
            }
        }

EntityFramework Core 1.1 Add、Attach、Update、Remove方法如何高效使用詳解

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)。

EntityFramework Core 1.1 Add、Attach、Update、Remove方法如何高效使用詳解

            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;
                    }
                });
            }

EntityFramework Core 1.1 Add、Attach、Update、Remove方法如何高效使用詳解

在EF Core 1.1其余的就是關(guān)于Add、Update等方法的異步操作了,對于操作數(shù)據(jù)庫不至于阻塞的情況也還是挺好的。

EntityFramework Core 1.1方法實(shí)踐詳解

關(guān)于EF Core 1.1中一些基本的知識我們過了一遍,下面我們來看看這些方法到底該如何高效使用呢?

Add/AddRange

關(guān)于這個方法就沒有太多敘述的了,對應(yīng)的則是異步方法。我們重點(diǎn)看看其他的方法。

Update/UpdateRange

當(dāng)我們根據(jù)主鍵去更新所有實(shí)體這個so easy了,我們在Blog表添加如下數(shù)據(jù)。

EntityFramework Core 1.1 Add、Attach、Update、Remove方法如何高效使用詳解

(1)更新方式一

現(xiàn)在我們查出Id=1的實(shí)體,然后將Name進(jìn)行修改如下:

EntityFramework Core 1.1 Add、Attach、Update、Remove方法如何高效使用詳解

        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();
        }

EntityFramework Core 1.1 Add、Attach、Update、Remove方法如何高效使用詳解

上述我們直接查詢出來主鍵對應(yīng)的實(shí)體然后修改其值,最后提交更新其實(shí)體的對應(yīng)修改的屬性。最后順理成章的數(shù)據(jù)字段進(jìn)行了修改

EntityFramework Core 1.1 Add、Attach、Update、Remove方法如何高效使用詳解

我們知道因?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ù)庫中,大概就演變成如下情況。

EntityFramework Core 1.1 Add、Attach、Update、Remove方法如何高效使用詳解

        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();
        }

EntityFramework Core 1.1 Add、Attach、Update、Remove方法如何高效使用詳解

誠然上述方法能達(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)此接口,如下:

EntityFramework Core 1.1 Add、Attach、Update、Remove方法如何高效使用詳解

        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;
            }
        }

EntityFramework Core 1.1 Add、Attach、Update、Remove方法如何高效使用詳解

是不是夠簡單粗暴,開源就是好啊,查找資料時發(fā)現(xiàn)老外已經(jīng)給出了具體實(shí)現(xiàn),當(dāng)直接調(diào)用時居然發(fā)現(xiàn)已經(jīng)給我們封裝了,接下來我們再來修改指定的屬性就變成了如下:

EntityFramework Core 1.1 Add、Attach、Update、Remove方法如何高效使用詳解

        public IActionResult Index()
        {            var blog = new Blog() { Id = 1, Name = "EntityFramework Core 1.1" };
            _blogRepository.Update(blog, d => d.Name);
            _blogRepository.Commit();            return Ok();
        }

EntityFramework Core 1.1 Add、Attach、Update、Remove方法如何高效使用詳解

上述只是演示,實(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。如下:

EntityFramework Core 1.1 Add、Attach、Update、Remove方法如何高效使用詳解

        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();
        }

EntityFramework Core 1.1 Add、Attach、Update、Remove方法如何高效使用詳解

上述三種更新方式各有其應(yīng)用場景,如果必須要總結(jié)的話就主要是第二種方式和第三種方式該如何取舍,第二種方式通過我們手動封裝的方式不需要再進(jìn)行查詢,直接更改其狀態(tài)進(jìn)行提交更新即可,而第三種方式需要進(jìn)行查詢才會被追蹤最終提交更新,看個人覺得哪種方式更加合適就取哪種吧。關(guān)于EF Core 1.1中對于數(shù)據(jù)更新我們就講解完了,我們再來看看刪除。

Remove/RemoveRange

對于上述和更新一樣如果該實(shí)體已經(jīng)被變更追蹤,直接調(diào)用內(nèi)置的方法Delete方法即可,大部分場景下是根據(jù)主鍵去刪除數(shù)據(jù)。這里有兩種方式供我們選擇,請往下看。

(1)刪除方式一

EntityFramework Core 1.1 Add、Attach、Update、Remove方法如何高效使用詳解

        public  IActionResult Index()
        {            var blog = _blogRepository.GetSingle(d => d.Id == 1);
            _blogRepository.Delete(blog);
            _blogRepository.Commit();            return Ok();
        }

EntityFramework Core 1.1 Add、Attach、Update、Remove方法如何高效使用詳解

我們查詢出需要刪除的實(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ù)。如下:

EntityFramework Core 1.1 Add、Attach、Update、Remove方法如何高效使用詳解

        public  IActionResult Index()
        {            var blog = new Blog() { Id = 1 };
            _blogRepository.Delete(blog);
            _blogRepository.Commit();            return Ok();
        }

EntityFramework Core 1.1 Add、Attach、Update、Remove方法如何高效使用詳解

Query 

最后還剩下一個查詢沒有講述,這個和添加方法一樣,比較簡單我們稍微過一下即可。由于在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)行如下封裝即可:

EntityFramework Core 1.1 Add、Attach、Update、Remove方法如何高效使用詳解

        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();
        }

EntityFramework Core 1.1 Add、Attach、Update、Remove方法如何高效使用詳解

此時我們只需要進(jìn)行對應(yīng)調(diào)用即可,大概如下:

               _blogRepository.GetSingle(
               d=>d.Id == 1,
               p=>p.Posts,
               p=>....)


向AI問一下細(xì)節(jié)

免責(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)容。

AI