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

前言

我比较喜欢安静,大概和我喜欢研究和琢磨技术原因相关吧,刚好到了元旦节,这几天可以好好学习下EF Core,同时在项目当中用到EF Core,借此机会给予比较深入的理解,这里我们只讲解和EF 6.x中不同,相同的则不再叙述。

EntityFramework Core 1.1方法理论详解

当我们利用EF Core查询数据库时如果我们不显式关闭变更追踪的话,此时实体是被追踪的,关于变更追踪我们下节再叙。就像我们之前在EF 6.x中讨论的那样,不建议手动关闭变更追踪,对于有些特殊情况下,关闭变更追踪可能会导致许多问题的发生。

实体状态

对于EF Core 1.1中依然有四种状态,有的人说不是有五种状态么,UnChanged、Added、Modified、Deleted、Detached。如果我们按照变更追踪来划分的话,实际上只有四种,将Detached排除在外,Detached不会被上下文所追踪。那么状态如何改变的呢?内部有一个IStateManager接口,通过此接口来对实体状态进行管理,此时再取决于SaveChanges被调用后背后是如何进行处理,我也就稍微看了下源码,深入的东西没去过多研究。

Added:实体还未插入到数据库当中,当调用SaveChanges后将修改其状态并将实体插入到数据库。

UnChanged:实体存在数据库中,但是在客户端未进行修改,当调用SaveChanges后将忽略。

Modified:实体存在数据库中,同时实体在客户端也进行了修改,当调用SaveChanges后将更改其状态并更新数据持久化到数据库。

Deleted:实体存在数据库中,当调用SaveChanges方法后将删除实体。

实体方法

在EF Core 1.1中依然存在Add、Attach、Update方法,我们通过上下文或者DbSet<TEntity>能够看到,当将实体传递到这些方法中时,它们与实体追踪可达图紧密联系在一起,比如说我们之前讨论的博客的导航属性文章的发表,当我们添加文章的发表的这个实体时,然后调用Add方法后此时文章的发表这个实体也就被添加。在EF 6.x中我们说过当我们调用Add等方法时EF内部机制将会自动调用DetectChanges,但是在EF Core 1.1中则不再调用DetectChanges方法。空口无凭,我下载了源码,如下:

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

上述我们没有看到任何自动调用DetectChanges的逻辑,在EF 6.x中我们讲到当调用SaveChanges时此时会回调DetectChanges,而在EF Core 1.1中同样也是如此,所以相对于EF 6.x而言,EF Core 1.1只是在SaveChanges时回调DetectChanges,在Add、Attacth、Update等方法则不再回调DetectChanges,这样的话性能就会好很多。我们看到源代码中调用SaveChanges时逻辑如下:

        public virtual int SaveChanges(bool acceptAllChangesOnSuccess)
        {
            CheckDisposed();

            TryDetectChanges();

            try
            {
                return StateManager.SaveChanges(acceptAllChangesOnSuccess);
            }
            catch (Exception exception)
            {..}
         }

接下来我们再来看看当调用Add、Update等方法时到底发生了什么。

Add:当调用Add方法时就没什么可说的了,此时将在图中的对应的所有实体推入到Added状态,也就说在调用SaveChanges时将会插入到数据库中去。

Attach:当调用Attach方法时将在图中的所有实体推入到UnChanged状态,但是有一个额外情况,比如我们在一个类中添加导航属性数据时,此时Attach的话将会使用混合模式,将此实体的状态为UnChanged而导航属性的状态则是Added状态,所以当插入到数据库中时,这个已存在的数据将不会被保存,只有新添加的导航属性数据才会被插入到数据库中去。

Update:Update方法和Attach方法一样只是将其状态修改为Modified,而将新添加的实体的修改将进行插入。

Remove:当调用Remove方法时此时它只会影响传递给该方法的实体,不会去遍历实体的可到达图。如果一个实体的状态是UnChanged或者Modified,说明该实体已存在数据库中,此时只需将其状态修改为Deleted。如果实体的状态为Added,此时说明该实体在数据库中不存在,此时会脱离上下文而不被跟踪。所以Remove方法侧重强调实体要被追踪,否则的话需要首先被Attach然后将其推入到Deleted状态。

Range方法 

在EF Core 1.1中多了AddRanges、UpdateRanges等方法,它们和实际调用多次调用非Range方法其实是一样的,它内部也会去遍历实体集合并更新其状态,如下:

public virtual void UpdateRange([NotNull] IEnumerable<object> entities)
            => SetEntityStates(Check.NotNull(entities, nameof(entities)), EntityState.Modified);

我们再看SetEntityStates这个方法的实现。

        private void SetEntityStates(IEnumerable<object> entities, EntityState entityState)
        {
            var stateManager = StateManager;

            foreach (var entity in entities)
            {
                SetEntityState(stateManager.GetOrCreateEntry(entity), entityState);
            }
        }

EF Core内部机制的处理肯定比我们之前手动去遍历添加实体集合性能要高,意外看到一篇文章上有说仅仅只高效一点,因为Range方法自动调用DetectChanges方法,找了半天也没看见在哪里调用DetectChanges,郁闷,算是一点疑惑吧。

【注意】EF团队之前一直在承诺EF Core会更高效和更高可扩展,但是我阅读源码发现内部还是自动调用了DetectChanges,性能方面的话还是不算太高效,但是,但是源码中已经明确给出,关于DetectChanges方法,未来对于这个api会进行更改或者彻底移除,源码注释如下:

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

追踪图 

对于变更追踪也好,默认启用变更追踪也好,我们都是通过ChangeTracker属性来获取到,如下:

  EFCoreContext efCoreContext;
  efCoreContext.ChangeTracker.AutoDetectChangesEnabled;
  efCoreContext.ChangeTracker.DetectChanges;

在ChangeTracker中也有一个重要的方法那就是如下:

 efCoreContext.ChangeTracker.TrackGraph;

我们暂且起名为跟踪图吧,它是对实体状态的完全控制,比如我们在将数据插入到数据库之前想设置其某一个值为临时值,我们就可以通过该方法来实现。

            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其余的就是关于Add、Update等方法的异步操作了,对于操作数据库不至于阻塞的情况也还是挺好的。

EntityFramework Core 1.1方法实践详解

关于EF Core 1.1中一些基本的知识我们过了一遍,下面我们来看看这些方法到底该如何高效使用呢?

Add/AddRange

关于这个方法就没有太多叙述的了,对应的则是异步方法。我们重点看看其他的方法。

Update/UpdateRange

当我们根据主键去更新所有实体这个so easy了,我们在Blog表添加如下数据。

(1)更新方式一

现在我们查出Id=1的实体,然后将Name进行修改如下:

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

上述我们直接查询出来主键对应的实体然后修改其值,最后提交更新其实体的对应修改的属性。最后顺理成章的数据字段进行了修改

我们知道因为查询出来的实体在未关闭变更追踪的情况下始终都是被追踪的,所以必须进行对应修改,但是要是下面的情况呢。

        public IActionResult Index(int Id,Blog blog)
        {
            return Ok();
        }

在客户端对数据进行了修改,我们需要根据主键Id进行对应属性修改,当然不希望多此一举的话,我们可以根据主键Id去查询对应的实体,然后将属性进行赋值最后提交修改保存到数据库中,大概就演变成如下情况。

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

诚然上述方法能达到我的目的,其实还有简便的方法,如下:

(2)更新方式二

既然有简单的方法为何我们不用呢,这样的场景就是更新指定属性,以往的情况都是自己封装一个Update方法,然后利用反射去包含需要修改的属性接着更改其属性的状态为修改,最后提交修改即可。是的,这就是我们说的方法,但是,但是在EF Core 1.1中完全不需要我们去封装,我们需要做的只是封装成一个通用方法即可,内置实现EF Core已经帮我们实现,我们来看看。

void Update(T entity, params Expression<Func<T, object>>[] properties);

很熟悉吧,我们在基仓储接口给出这样一个接口,接着我们来实现此接口,如下:

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

是不是够简单粗暴,开源就是好啊,查找资料时发现老外已经给出了具体实现,当直接调用时居然发现已经给我们封装了,接下来我们再来修改指定的属性就变成了如下:

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

上述只是演示,实际项目当中时我们只需给出我们修改的主键和实体即可。如果是修改实体集合的话,再重载一个遍历就ok。到这里你是不是发现已经非常完美了,还有更完美的解决方案,请继续往下看。

(3)更新方式三

其实在ASP.NET Core MVC中有比上面进一步还爽的方式通过利用TryUpdateModelAsync方法来实现,此方法有多个重载来实现,完全不需要我们去封装。如下:

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

上述三种更新方式各有其应用场景,如果必须要总结的话就主要是第二种方式和第三种方式该如何取舍,第二种方式通过我们手动封装的方式不需要再进行查询,直接更改其状态进行提交更新即可,而第三种方式需要进行查询才会被追踪最终提交更新,看个人觉得哪种方式更加合适就取哪种吧。关于EF Core 1.1中对于数据更新我们就讲解完了,我们再来看看删除。

Remove/RemoveRange

对于上述和更新一样如果该实体已经被变更追踪,直接调用内置的方法Delete方法即可,大部分场景下是根据主键去删除数据。这里有两种方式供我们选择,请往下看。

(1)删除方式一

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

我们查询出需要删除的实体,然后通过调用Remove(这里我封装了)方法将其标识为Deleted状态进行删除,当查询数据我们可以关闭变更追踪,一来数据量大的话对内存压力不会太大,二来因为调用Remove方法会将其标识为Deleted状态也会被追踪,不会有任何问题。

(2)删除方式二【推荐】

为了尽量减少请求时间,我们能一步完成的何必要用两步呢,我们完全可以直接实例化一个实体,将其主键赋值,最后修改其状态为Deleted,最终将持久化到数据库中删除对应的数据。如下:

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

Query 

最后还剩下一个查询没有讲述,这个和添加方法一样,比较简单我们稍微过一下即可。由于在EF Core中不再支持延迟加载,所以我们需要通过Include显式获取我们需要的导航属性,比如如下:

 DbContext dbContext;
 dbContext.Set<Blog>().Include(d => d.Posts);

如果有多个导航属性,我们接着进行ThenInclude,如下:

            DbContext dbContext;
            dbContext.Set<Blog>().AsNoTracking().Include(d => d.Posts).ThenInclude(d => d....).

为了避免这样多次ThenInclude,方便调用我们进行如下封装即可:

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

此时我们只需要进行对应调用即可,大概如下:

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

总结 

本节我们比较详细的叙述了EntityFramework Core 1.1中一些方法的正确使用以及相关理论知识,下节再讲讲其他理论知识,EF Core 1.1现在非常稳定,不要担心再出太多坑,是时候学学EF Core了,come on。

时间: 2024-09-20 18:51:42

EntityFramework Core 1.1 Add、Attach、Update、Remove方法如何高效使用详解的相关文章

EntityFramework 7 更名为EntityFramework Core(预发布状态)

前言 最近很少去学习和探索新的东西,尤其是之前一直比较关注的EF领域,本身不太懒,但是苦于环境比较影响自身的心情,所以迟迟没有下笔,但是不去学习感觉在精神层面缺少点什么,同时也有园友说EF又更新了,要我再写一篇,最终经过思想斗争后,还是花了一点时间去继续探索.本篇比较理论的去分享最近EF进展,后面有时间会继续关注EF团队在EF上的动向,并给出相对应的实例. EF Core 1.0.0 (1)EntityFramework是微软在.NET中推荐使用的数据访问技术,而EntityFramework

EntityFramework Core并发深挖详解,一纸长文,你准备好看完了吗?

前言 之前有关EF并发探讨过几次,但是呢,博主感觉还是有问题,为什么会觉得有问题,其实就是理解不够透彻罢了,于是在项目中都是用的存储过程或者SQL语句来实现,利用放假时间好好补补EF Core并发的问题,本文比较长,请耐心点看. EntityFramework Core并发初级版初探 关于并发无非就两种:乐观并发和悲观并发,悲观并发简言之则是当客户端对数据库中同一值进行修改时会造成阻塞,而乐观并发则任何客户端都可以对可以对数据进行查询或者读取,在EF Core中不支持悲观并发,结果则产生并发冲突

EntityFramework Core迁移时出现数据库已存在对象问题解决方案

前言 刚开始接触EF Core时本着探索的精神去搞,搞着搞着发现出问题了,后来就一直没解决,觉得很是不爽,借着周末好好看看这块内容. EntityFramework Core迁移出现对象在数据库中已存在 在EF Core之前对于迁移的命令有很多,当进行迁移出现对象已在数据库中存在时我们通过如何命令即可解决: Add-Migration Initial -IgnoreChanges 但是在EF Core对于迁移现如今只存在如下两个命令: dotnet ef migrations add <<mi

神马玩意,EntityFramework Core 1.1又更新了?走,赶紧去围观

前言 哦,不搞SQL了么,当然会继续,周末会继续更新,估计写完还得几十篇,但是我会坚持把SQL更新完毕,绝不会烂尾,后续很长一段时间没更新的话,不要想我,那说明我是学习新的技能去了,那就是学习英语,本来没有打算再探究目前.NET中跨平台的东西,毕竟才出来没多久,还是有很多坑,希望有人踩过再来学习会好很多,可惜项目中都是用的最新的东西,我不得不去探索,于是有关EntityFramework Core的坑就这么出来了,来,我们一起看看. EntityFramework Core 1.1迁移 我们首先

EntityFramework Core问题处理集锦(一)

前言 和大家脱离了一段时间,有时候总想着时间挤挤总是会有的,但是并非人愿,后面会借助周末的时间来打理博客,如有问题可以在周末私信我或者加我QQ皆可,欢迎和大家一起探讨,本节我们来讨论EF Core中的一些问题后面陆陆续续会将EF Core中需要注意的地方补充上来,有些是我一直以来比较疏忽的地方,不喜勿喷.用在实际项目中的时候才发现和平时所学有很大差异,靠着项目才能检验出真理. EntityFramework Core问题集锦 更新单个实体 更新单个实体的方式有两种: (1)查询出实体进行赋值更新

EntityFramework Core 1.1有哪些新特性呢?我们需要知道

前言 在项目中用到EntityFramework Core都是现学现用,及时发现问题及时测试,私下利用休闲时间也会去学习其他未曾遇到过或者用过的特性,本节我们来讲讲在EntityFramework Core 1.1中出现了哪些新特性供我们使用. EntityFramework Core 1.1新特性探讨 DbSet.Find 在EF 6.x中也有此方法的实现,在EF Core 1.1中也同样对此方法进行了实现,为什么要拿出来讲呢,当然也有其道理,我们一起来看看.在仓储中我们实现Find这个方法,

EntityFramework Core并发导致显式插入主键问题

前言 之前讨论过EntityFramework Core中并发问题,按照官网所给并发冲突解决方案以为没有什么问题,但是在做单元测试时发现too young,too simple,下面我们一起来看看. .NET Core 1.1单元测试问题 我们循序渐进,首先从单元测试开始说起,可能其中就有你在.NET Core上进行单元测试会遇到的问题,别着急,不妨一看.我们需要创建.NET Core类库,,如下: 接下来对project.json进行如下修改. { "version": "

EntityFramework Core解决并发详解

前言 对过年已经无感,不过还是有很多闲暇时间来学学东西和多陪陪爸妈,这一点是极好的,好了,本节我们来讲讲EntityFramework Core中的并发问题. 话题(EntityFramework Core并发) 对于并发问题这个话题相信大家并不陌生,当数据量比较大时这个时候我们就需要考虑并发,对于并发涉及到的内容也比较多,在EF Core中我们将并发分为几个小节来陈述,让大家看起来也不太累,也容易接受,我们由浅入深.首先我们看下给出的Blog实体类. public class Blog : I

EntityFramework Core Raw SQL

前言 本节我们来讲讲EF Core中的原始查询,目前在项目中对于简单的查询直接通过EF就可以解决,但是涉及到多表查询时为了一步到位就采用了原始查询的方式进行.下面我们一起来看看. EntityFramework Core Raw SQL 基础查询(执行SQL和存储过程) 啥也不说了,拿起键盘就是干,如下: public class HomeController : Controller { private IBlogRepository _blogRepository; public HomeC