EF Core 三 、 EF Core CRUD (增刪改查)


EF Core CRUD

上篇文章中,我們已經基本入門了EFCore,搭建了一個簡單的EFCore項目,本文開始簡單使用下EF,做增刪改查的相關操作;

一、數據新增操作(C)

        public static async void Insert_測試新增數據1()
        {
            var myDbContext = new MyDbContext();
            if (myDbContext.TestTables.Any(p => p.Id == 1)) return;
            var newEntity = new TestTable
            {
                Id = 1,
                Name = "主表數據1"
            };

            await myDbContext.TestTables.AddAsync(newEntity);
            myDbContext.SaveChanges();

            Console.WriteLine($"TestTable Insert Success");
            Console.WriteLine($"------------------------");
        }

是不是很簡單的代碼?so eays ...
我們還是來分析下整體代碼的運行;
1.首先我們自己在代碼段中新增了一個實體對象,並對其字段做賦值,然后通過Add方法加入到DbSet中
2.通過DbContext.SaveChanges提交數據到數據庫保存

那EF是如何偷偷的在背后幫我們完成這一切的呢?

EFCore實體四狀態, 如下四種狀態;

 public enum EntityState
    {
        /// <summary>
        ///     The entity is not being tracked by the context.
        /// </summary>
        Detached = 0,
        /// <summary>
        ///     The entity is being tracked by the context and exists in the database. Its property
        ///     values have not changed from the values in the database.
        /// </summary>
        Unchanged = 1,
        /// <summary>
        ///     The entity is being tracked by the context and exists in the database. It has been marked
        ///     for deletion from the database.
        /// </summary>
        Deleted = 2,
        /// <summary>
        ///     The entity is being tracked by the context and exists in the database. Some or all of its
        ///     property values have been modified.
        /// </summary>
        Modified = 3,
        /// <summary>
        ///     The entity is being tracked by the context but does not yet exist in the database.
        /// </summary>
        Added = 4
    }

Detached : 實體未被跟蹤
Unchanged:未修改
Deleted : 刪除狀態
Modified:修改狀態
Added:新增狀態
Detached 未被跟蹤狀態,很多同學可能無法理解了,EFCore會默認自動跟蹤實體信息,用來維護實體狀態,也是方便后續提交時的處理;EFCore提供兩種查詢方法,跟蹤查/非跟蹤查,跟蹤查得到的數據是Unchanged,而非跟蹤查的到的數據是Detached,這兩種方式我們后面詳細說明,這里先簡單描述下;

EFCore管理內存實體
查看DbContext源碼中的Add方法,跟蹤方法,發現Add方法會調用到 EntityReferenceMap.cs 類中的Update方法 (下面的源碼內容),此方法中EFCore會在內存中維護我們操作的實體信息,將我們操作的實體信息管理到內存中(我們的增刪改查操作,EFCore都會再內存維護,方法中只是對實體狀態維護,SaveChanges才會提交);

 public virtual void Update(
            [NotNull] InternalEntityEntry entry,
            EntityState state,
            EntityState? oldState)
        {
            var mapKey = entry.Entity ?? entry;
            var entityType = entry.EntityType;
            if (_hasSubMap && entityType.HasDefiningNavigation())
            {
                if (_dependentTypeReferenceMap == null)
                {
                    _dependentTypeReferenceMap = new Dictionary<IEntityType, EntityReferenceMap>();
                }
                if (!_dependentTypeReferenceMap.TryGetValue(entityType, out var dependentMap))
                {
                    dependentMap = new EntityReferenceMap(hasSubMap: false);
                    _dependentTypeReferenceMap[entityType] = dependentMap;
                }
                dependentMap.Update(entry, state, oldState);
            }
            else
            {
                if (oldState.HasValue)
                {
                    Remove(mapKey, entityType, oldState.Value);
                }
                if (!oldState.HasValue || state != EntityState.Detached)
                {
                    switch (state)
                    {
                        case EntityState.Detached:
                            if (_detachedReferenceMap == null)
                            {
                                _detachedReferenceMap = new Dictionary<object, InternalEntityEntry>(ReferenceEqualityComparer.Instance);
                            }
                            _detachedReferenceMap[mapKey] = entry;
                            break;
                        case EntityState.Unchanged:
                            if (_unchangedReferenceMap == null)
                            {
                                _unchangedReferenceMap = new Dictionary<object, InternalEntityEntry>(ReferenceEqualityComparer.Instance);
                            }
                            _unchangedReferenceMap[mapKey] = entry;
                            break;
                        case EntityState.Deleted:
                            if (_deletedReferenceMap == null)
                            {
                                _deletedReferenceMap = new Dictionary<object, InternalEntityEntry>(ReferenceEqualityComparer.Instance);
                            }
                            _deletedReferenceMap[mapKey] = entry;
                            break;
                        case EntityState.Modified:
                            if (_modifiedReferenceMap == null)
                            {
                                _modifiedReferenceMap = new Dictionary<object, InternalEntityEntry>(ReferenceEqualityComparer.Instance);
                            }
                            _modifiedReferenceMap[mapKey] = entry;
                            break;
                        case EntityState.Added:
                            if (_addedReferenceMap == null)
                            {
                                _addedReferenceMap = new Dictionary<object, InternalEntityEntry>(ReferenceEqualityComparer.Instance);
                            }
                            _addedReferenceMap[mapKey] = entry;
                            break;
                    }
                }
            }
        }

代碼中就是針對不同狀態的實體,EF采用不同的集合進行維護,按照我們的測試代碼,會將新增實體放入上面的_addedReferenceMap 集合中,方便EFCore做提交時的操作;
EFCore SaveChanges
然后來看下將實體加入到內存集合后,提交操作到底做了什么
首先跟蹤到 DbContext的 SaveChanges方法,其內部會調用StateManger.SaveChanges方法,代碼如下:

        public virtual int SaveChanges(bool acceptAllChangesOnSuccess)
        {
            if (ChangedCount == 0)
            {
                return 0;
            }
            var entriesToSave = GetEntriesToSave(cascadeChanges: true);
            if (entriesToSave.Count == 0)
            {
                return 0;
            }
            try
            {
                var result = SaveChanges(entriesToSave);
                if (acceptAllChangesOnSuccess)
                {
                    AcceptAllChanges((IReadOnlyList<IUpdateEntry>)entriesToSave);
                }
                return result;
            }
            catch
            {
                foreach (var entry in entriesToSave)
                {
                    ((InternalEntityEntry)entry).DiscardStoreGeneratedValues();
                }
                throw;
            }
        }

首先 GetEntriesToSave 方法,這個方法就是和上面的Add呼應,將EFCore加入到內存集合中管理的實體再次讀取出來,得到一個entriesToSave集合,也就是需要保存的實體集合,看下其中的部分代碼就是將內存集合中的數據得到

            if (!hasDependentTypes)
            {
                var numberOfStates
                    = (returnAdded ? 1 : 0)
                      + (returnModified ? 1 : 0)
                      + (returnDeleted ? 1 : 0)
                      + (returnUnchanged ? 1 : 0);

                if (numberOfStates == 1)
                {
                    if (returnUnchanged)
                    {
                        return _unchangedReferenceMap.Values;
                    }
                    if (returnAdded)
                    {
                        return _addedReferenceMap.Values;
                    }
                    if (returnModified)
                    {
                        return _modifiedReferenceMap.Values;
                    }
                    if (returnDeleted)
                    {
                        return _deletedReferenceMap.Values;
                    }
                }
                if (numberOfStates == 0)
                {
                    return Enumerable.Empty<InternalEntityEntry>();
                }
            }

可以看到將不同狀態的實體集合返回,得到了一個需要保存的實體集合數據,那得到需要保存的實體之后,就需要執行數據庫命令了;
整體的保存入口方法還是由 DbContext 提供,DbContext.SaveChanges 方法會調用到 BatchExecutor.cs類中的Execute方法

private int Execute(DbContext _, (IEnumerable<ModificationCommandBatch>, IRelationalConnection) parameters)
        {
            var commandBatches = parameters.Item1;
            var connection = parameters.Item2;
            var rowsAffected = 0;
            IDbContextTransaction startedTransaction = null;
            try
            {
                if (connection.CurrentTransaction == null
                    && (connection as ITransactionEnlistmentManager)?.EnlistedTransaction == null
                    && Transaction.Current == null
                    && CurrentContext.Context.Database.AutoTransactionsEnabled)
                {
                    startedTransaction = connection.BeginTransaction();
                }
                else
                {
                    connection.Open();
                }
                foreach (var batch in commandBatches)
                {
                    batch.Execute(connection);
                    rowsAffected += batch.ModificationCommands.Count;
                }
                startedTransaction?.Commit();
            }
            finally
            {
                if (startedTransaction != null)
                {
                    startedTransaction.Dispose();
                }
                else
                {
                    connection.Close();
                }
            }
            return rowsAffected;
        }

上訴代碼中,根據得到的命令集合,循環執行命令來執行命令,最后通過事務統一來提交操作,也是確保DbContext內的事務一致性;
至此,我們EfCore的新增操作就簡單分析完了,通過EFCore的DbContext來添加實體對象,通過Add方法,此時對象會加入到EFCore的內存管理集合中,不同狀態對象不同的管理集合,調用SaveChanges方法保存,此時EFCore會將內存的實體對象讀取出來,然后通過數據庫事務統一提交;EFCore在整個操作中給我們加入了一層數據緩存層,也就是內存管理(后面會慢慢交流這一層的內容);

二、數據查詢 (R)

數據查詢的內容上一篇入門文章中我已經說了相關方法,這里再把調用方式同步下
EF 的數據查詢分為兩種,跟蹤查詢和非跟蹤查詢;
1.跟蹤查詢是默認方式,默認EF查詢出來的數據是跟蹤模式(也可以手動調整),用於對數據做更新等數據庫操作;
2.非跟蹤查詢模式,此模式用於單純的數據查詢,后續不需要對數據做相關修改,因為不需要對實體做調整的監測,所以會比跟蹤查詢相對快一些;
兩種方式各有利弊,需要根據自己的業務實際需要來選擇;
兩種模式的文檔說明(https://docs.microsoft.com/zh-cn/ef/core/querying/tracking)

var myDbContext = new MyDbContext();
            var list = myDbContext.TestTables.ToList();
            Console.WriteLine($"TestTable Count: {list.Count}");
            if (!list.Any()) return;
            Console.WriteLine($"TestTable Detail ----------------  ");
            foreach (var item in list)
            {
                Console.WriteLine($"ID : {item.Id} , Name : {item.Name}");
            }
            Console.WriteLine($"------------------------");

三、數據更新 (U)

數據更新操作,必須使用跟蹤查詢得到數據,然后修改得到的實體信息,再通過DbContext的SaveChanges提交數據即可;
三部曲:
1.跟蹤查詢得到數據
2.修改實體數據
3.DbContext.SaveChanges保存數據

            var myDbContext = new MyDbContext();
            var list = myDbContext.TestTables.AsTracking().ToList();
            var firstEntity = list.FirstOrDefault(p => p.Id == 1);
            if (firstEntity != null) firstEntity.Name = $"{firstEntity.Name} Query_跟蹤查詢";
            myDbContext.SaveChanges();
            Console.WriteLine($"------------------------");

四、數據刪除 (D)

1.使用跟蹤查詢,將數據查詢出來
2.通過DbSet將其Remove(也是再內存集合中做了標記,將其放入了remove集合中)
3.SaveChanges提交保存

            var myDbContext = new MyDbContext();
            var entity = myDbContext.TestTables.FirstOrDefault(p => p.Id == 1);
            if (entity != null)
                myDbContext.TestTables.Remove(entity);
            myDbContext.SaveChanges();

有沒有發現問題,(⊙o⊙)? 刪除個數據還要查詢出來?業務場景中,很多都是前端傳入主鍵ID,執行執行相關表的數據刪除,那是否可以使用非跟蹤查詢模式呢?
驗證下 .....

            var myDbContext = new MyDbContext();
            var entity = myDbContext.TestTables.AsNoTracking().FirstOrDefault(p => p.Id == 1);
            if (entity != null)
                myDbContext.TestTables.Remove(entity);
            myDbContext.SaveChanges();

結果當然是可以刪除啦,😄
那既然是非跟蹤可以刪除,也意味着自己構建的實體可以刪除,那是否只需要主鍵就可以刪除了 ?

            var myDbContext = new MyDbContext();
            var entity = new TestTable()
            {
                Id = 1
            };
            myDbContext.TestTables.Remove(entity);
            myDbContext.SaveChanges();

如上的代碼,確實也是可以的,刪除成功 !!!

示例代碼地址:https://gitee.com/wuxingquema/knowledge-points
至此我們完成了EFCore的入門簡單操作,基本都是簡單的單表操作,只是為了演示整個EFCore的代碼結構,大家可以自己上手嘗試下,后續我們將開始EFCore相關的騷操作以及一些進階操作


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM