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

如上的代码,确实也是可以的,删除成功 !!!

至此我们完成了EFCore的入门简单操作,基本都是简单的单表操作,只是为了演示整个EFCore的代码结构,大家可以自己上手尝试下,后续我们将开始EFCore相关的骚操作以及一些进阶操作

EF Core 三 、 EF Core CRUD的更多相关文章

  1. .Net Core(三)MVC Core

    MVC Core的改动感觉挺大的,需要的功能大多从Nuget安装,还内置了IOC,支持SelfHost方式运行等等. 一.项目结构的变化创建的新MVC项目的结构发生了变化,比如:静态文件需要统一放置到 ...

  2. Azure Cosmos DB (三) EF Core 操作CURD

    一,引言 接着上一篇使用 EF Core 操作 Azure CosmosDB 生成种子数据,今天我们完成通过 EF Core 实现CRUD一系列功能.EF Core 3.0 提供了CosmosDB 数 ...

  3. 9.翻译系列:EF 6以及EF Core中的数据注解特性(EF 6 Code-First系列)

    原文地址:http://www.entityframeworktutorial.net/code-first/dataannotation-in-code-first.aspx EF 6 Code-F ...

  4. .net core webapi+EF Core

    .net core webapi+EF Core 一.描述: EF Core必须下载.net core2.0版本 Micorsoft.EntityFrameworkCore:EF框架的核心包Micor ...

  5. 【ASP.NET Core】EF Core - “影子属性” 深入浅出经典面试题:从浏览器中输入URL到页面加载发生了什么 - Part 1

    [ASP.NET Core]EF Core - “影子属性”   有朋友说老周近来博客更新较慢,确实有些慢,因为有些 bug 要研究,另外就是老周把部分内容转到直播上面,所以写博客的内容减少了一点. ...

  6. 一个官翻教程集合:ASP.NET Core 和 EF Core 系列教程

    通过一个大学课程案例讲解了复杂实体的创建过程及讲解 1.ASP.NET Core 和 Entity Framework Core 系列教程——入门 (1 / 10) 2.ASP.NET Core 和 ...

  7. ASP.NET CORE 使用 EF CORE访问数据库

    asp.net core通过ef core来访问数据库,这里用的是代码优先,通过迁移来同步数据库与模型. 环境:vs2017,win10,asp.net core 2.1 一.从建立asp.net c ...

  8. ASP.NET Core 使用 EF 框架查询数据 - ASP.NET Core 基础教程 - 简单教程,简单编程

    原文:ASP.NET Core 使用 EF 框架查询数据 - ASP.NET Core 基础教程 - 简单教程,简单编程 ASP.NET Core 使用 EF 框架查询数据 上一章节我们学习了如何设置 ...

  9. ASP.NET Core 配置 EF SQLite 支持 - ASP.NET Core 基础教程 - 简单教程,简单编程

    原文:ASP.NET Core 配置 EF SQLite 支持 - ASP.NET Core 基础教程 - 简单教程,简单编程 ASP.NET Core 配置 EF SQLite 支持 上一章节我有提 ...

  10. .NET Core+WebApi+EF访问数据新增用户数据

    新建一个.NET Core项目,我使用的IDE是VS2019 依次创建三个Core类库:第一个命名api.Model,第二个api.Common,第三个api.Bo 解释一下这个三类库的作用: 第一个 ...

随机推荐

  1. BGP - 不同 AS 间运行的协议

    在之前介绍的网络场景中,ERGRP,OPSF,RIP 等都是运行在单独一个 AS(自治系统之间).这些协议统称为 IGP - 内部网关协议 ,目的主要是为自治系统内发现邻居和计算路由,从而找到合适的路 ...

  2. Mybatis项目搭建

    MyBatis是一个优秀的持久层框架.原生的jdbc操作存在大量的重复性代码(如注册驱动,创建连接,创建statement,结果集检测等).框架的作用就是把这些繁琐的代码封装. MyBatis通过XM ...

  3. dns配置文件的方式

    1./etc/resolve.conf /etc/resolv.conf它是DNS客户机配置文件,用于设置DNS服务器的IP地址及DNS域名,还包含了主机的域名搜索顺序.该文件是由域名解析 器(res ...

  4. 解决LINK : fatal error LNK1104: 无法打开文件“xxxx.lib”

    举个例子

  5. ERROR: No matching distribution found for cv2

    ImportError: No module named cv2和No matching distribution found for cv2的问题 原因 这个是由于没有导入opencv库导致的 解决 ...

  6. Java-GUI基础(一)

    简介 首先,Java的学习就是不断学习一个个包与类的过程,对于GUI部分有两个核心包:java.awt与javax.swing, 这两个包可以说是涵盖了java的GUI部分所有的类(和他们的方法). ...

  7. IOCP 模型1

    // IOCP.cpp : Defines the entry point for the console application. // // #include "stdafx.h&quo ...

  8. 令人惊叹的百度Echarts,大数据分析的必备工具

    学习目录 1.可视化面板介绍     1.1技术要点     1.2案例适配方案     1.3页面主体布局2.Echarts(重点)     2.1echarts介绍     2.2echarts体 ...

  9. 作为servlet容器的hi-nginx-java

    hi-nginx-java是一个独立于java官方的servlet规范,它有能力把NGINX直接编成servlet容器服务器.换言之,无需安装tomcat等容器服务器,也无需使用nginx的反向代理功 ...

  10. 1.使用javax.mail, spring的JavaMailSender,springboot发送邮件

    一.java发邮件 电子邮件服务器:这些邮件服务器就类似于邮局,它主要负责接收用户投递过来的邮件,并把邮件投递到邮件接收者的电子邮箱中,按功能划分有两种类型 SMTP邮件服务器:用户替用户发送邮件和接 ...