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

时间:2022-12-21 23:11:54

前言

我比较喜欢安静,大概和我喜欢研究和琢磨技术原因相关吧,刚好到了元旦节,这几天可以好好学习下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 < )
{
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表添加如下数据。

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

(1)更新方式一

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

        IBlogRepository _blogRepository;
public HomeController(IBlogRepository blogRepository)
{
_blogRepository = blogRepository;
}
public IActionResult Index()
{
var blog = _blogRepository.GetSingle(d => d.Id == );
blog.Name = "EntityFramework Core 1.1";
_blogRepository.Commit();
return View();
}

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

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

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

        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 = , 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 == );
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 == );
_blogRepository.Delete(blog);
_blogRepository.Commit();
return Ok();
}

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

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

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

        public  IActionResult Index()
{
var blog = new Blog() { Id = };
_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 == ,
p=>p.Posts,
p=>....)

总结

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

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

  1. ASP&period;NET Core微服务 on K8S学习笔记(第一章:详解基本对象及服务发现)

    课程链接:http://video.jessetalk.cn/course/explore 良心课程,大家一起来学习哈! 任务1:课程介绍 任务2:Labels and Selectors 所有资源对 ...

  2. 你必须知道的EntityFramework 6&period;x和EntityFramework Core变更追踪状态

    前言 只要有时间就会时不时去看最新EF Core的进展情况,同时也会去看下基础,把握好基础至关重要,本节我们对比看看如标题EF 6.x和EF Core的不同,希望对正在学习EF Core的同行能有所帮 ...

  3. Webservice WCF WebApi 前端数据可视化 前端数据可视化 C&num; asp&period;net PhoneGap html5 C&num; Where 网站分布式开发简介 EntityFramework Core依赖注入上下文方式不同造成内存泄漏了解一下? SQL Server之深入理解STUFF 你必须知道的EntityFramework 6&period;x和EntityFramework Cor

    Webservice WCF WebApi   注明:改编加组合 在.net平台下,有大量的技术让你创建一个HTTP服务,像Web Service,WCF,现在又出了Web API.在.net平台下, ...

  4. List 调用 remove 方法时抛出 java&period;lang&period;UnsupportedOperationException 异常原因

    原因 使用 Arrays.asList(arr) 转换的 List 并不能进行 add 和 remove 操作.       Arrays.asList(arr) 返回的类型是 Aarrays$Arr ...

  5. Day07&lowbar;38&lowbar;集合中的remove&lpar;&rpar;方法

    集合中的remove()方法 remove() 移除集合中的一个指定对象 代码实例 package com.shige.Collection; import java.util.ArrayList; ...

  6. &lbrack;Cocoa&rsqb;深入浅出 Cocoa 之 Core Data(1)- 框架详解

    Core data 是 Cocoa 中处理数据,绑定数据的关键特性,其重要性不言而喻,但也比较复杂.Core Data 相关的类比较多,初学者往往不太容易弄懂.计划用三个教程来讲解这一部分: 框架详解 ...

  7. 深入浅出 Cocoa 之 Core Data(1)- 框架详解

    深入浅出 Cocoa 之 Core Data(1)- 框架详解 罗朝辉(http://blog.csdn.net/kesalin) CC 许可,转载请注明出处 Core data 是 Cocoa 中处 ...

  8. 一个基于EntityFramework Core的简单数据库访问层,适用于轻量级数据库业务

    这个访问层的代码实际上是园子里某个前辈的,本人只是觉得好使,记录了下来. 本访问层需要通过Nuget安装EntityFramework Core,不过个人认为EF 6同样可以使用. 搭配数据库,最好是 ...

  9. EntityFramework Core 学习系列(一)Creating Model

    EntityFramework Core 学习系列(一)Creating Model Getting Started 使用Command Line 来添加 Package  dotnet add pa ...

随机推荐

  1. nodejs操作mongodb

    一.下载地址 https://www.mongodb.com/download-center#community 二.控制台操作mongodb 1.安装完后添加环境变量. 2.在某个根目录下新建dat ...

  2. 在CentOS 6&period;4中编译安装gcc 4&period;8&period;1

    在CentOS 6.4中编译安装gcc 4.8.1 分类: C/C++ Linux/Unix2013-11-28 21:02 1877人阅读 评论(0) 收藏 举报 原文链接:http://www.c ...

  3. The maximum number of cell styles was exceeded&period; You can define up to 4000 styles

    POI操作Excel中,导出的数据不是很大时,则不会有问题,而数据很多或者比较多时, 就会报以下的错误,是由于cell styles太多create造成,故一般可以把cellstyle设置放到循环外面 ...

  4. (转)PHP函数之error&lowbar;reporting&lpar;E&lowbar;ALL &Hat; E&lowbar;NOTICE&rpar;详细说明

    在看帝国cms的connect.php是发现第一句是error_reporting(E_ALL ^ E_NOTICE);以前也没注意过这个语句,知道是设置错误提示的,但不清楚具体怎样设置使用.下面从网 ...

  5. js函数知识点

    1.即使写成functon a()也是可以调用外面定义的变量的,写(a,b),我估计是为了降低耦合性 2.即使写成function a()也是可以用arguments[a]来默认写了(a,b) 3.在 ...

  6. &lbrack;Python学习笔记&rsqb;&lbrack;第五章Python函数设计与使用&rsqb;

    2016/1/29学习内容 第四章 Python函数设计与使用 之前的几页忘记保存了 很伤心 变量作用域 -一个变量已在函数外定义,如果在函数内需要修改这个变量的值,并将这个赋值结果反映到函数之外,可 ...

  7. Delphi透明组件开发(去掉自己的csOpaque,去掉父控件的WS&lowbar;CLIPCHILDREN,增加WS&lowbar;EX&lowbar;TRANSPARENT,截获WM&lowbar;ERASEBKGND,然后在WM&lowbar;DRAWITEM里画) good

    透明的控件, 一般继承自TGraphicControl的(就是那些没有handle属性, 不能有focus的控件, 如image)都有Transparent属性. 对TWinControl类的控件, ...

  8. IOS中的绘图Quartz2D

    drawRect 方法的使用 常见图形的绘制:线条.多边形.圆 绘图状态的设置:文字颜色.线宽等 图形上下文状态的保存与恢复 图形上下文栈 Quartz 2D是一个二维绘图引擎,同时支持IOS和MAC ...

  9. day 15 - 2 内置函数练习

    内置函数练习 编写 sql 查询语句功能 文件内容: 1,Eva,22,13651054608,IT2,Vera,23,13304320533,Tearcher3,Renault,25,1333235 ...

  10. potplayer打开多个视频文件

    选项-基本-多重处理方式-新开一个播放进程播放