你必须知道的EntityFramework 6.x和EntityFramework Core变更追踪状态
前言
只要有时间就会时不时去看最新EF Core的进展情况,同时也会去看下基础,把握好基础至关重要,本节我们对比看看如标题EF 6.x和EF Core的不同,希望对正在学习EF Core的同行能有所帮助,同时也希望通过本文能对您心中可能产生的疑惑进行解答,本文略长,请耐心阅读。
深入探讨EF 6.x和EF Core变更追踪状态话题
请注意虽然EF 6.x和EF Core在使用方式上没有什么不同,但是内置实现却有所不同,了解它们的不同很重要,同时也希望更多同行选择EF Core,EF 6.x令人诟病毋庸置疑,在使用上不了解基本知识就会出很大的问题,这一点我已经明确阐述过,EF Core为我们做了许多,只是我们并未知道而已,看完本文相信您会认同我说的这句话,为了便于大家理解我们用实例来说明。
EntityState状态设置和使用对应方法是否匹配?
无论是在EntityFramework 6.x还是EntityFramework Core中DbSet上始终包含有Add、Attath、Remove等方法,当我们调用Add方法来添加对象时,此时内部则是将对象状态置为添加状态(Add),如果我们调用Remove方法删除对象,内部则是将对象置为删除状态(Deleted),在EF 6.x中没有Update方法,若是更新所有列则是只需将对象状态修改为Modified,无论怎样都是通过EntityState来根据我们的操作来设置对象相应的状态,下面我们一起来看下例子。
using (var ctx = new EfDbContext())
{
var customer = new Customer() { Id = , Name = "Jeffcky" }; ctx.Entry(customer).State = EntityState.Modified; ctx.Customers.Add(customer); var state = ctx.Entry(customer).State; var result = ctx.SaveChanges();
};
如上示例是在EF 6.x中,我们首先实例化一个customer,然后将其状态修改为Modified,最后我们调用Add方法添加到上下文中,此时我们得到customer的状态会是怎样的呢?
没毛病,对不对,最终调用Add方法其状态将覆盖我们手动通过Entry设置的Modified状态,接下来我们将如上通过Entry方法修改为如下:
ctx.Entry(customer).State = EntityState.Unchanged;
如果我们这样做了,结果当然也是好使的,那要是我们继续修改为如下形式呢?
using (var ctx = new EfDbContext())
{
var customer = new Customer() { Id = , Name = "Jeffcky" }; ctx.Entry(customer).State = EntityState.Deleted; ctx.Customers.Add(customer); var state = ctx.Entry(customer).State; var result = ctx.SaveChanges();
};
结果还是Added状态,依然好使,我们继续进行如下修改。
using (var ctx = new EfDbContext())
{
var customer = new Customer() { Id = , Name = "Jeffcky" }; ctx.Entry(customer).State = EntityState.Added; ctx.Customers.Attach(customer); var state = ctx.Entry(customer).State; var result = ctx.SaveChanges();
};
恩,还是没问题,这里我们可以得出我们通过Entry方法手动设置未被跟踪对象的状态后,最后状态会被最终调用的方法所覆盖,一切都是明朗,还没完全结束。那接下来我们添加导航属性看看呢?
using (var ctx = new EfDbContext())
{
var customer = new Customer() { Id = , Name = "Jeffcky" };
var order = new Order() { Id = , CustomerId = };
customer.Orders.Add(order); ctx.Entry(order).State = EntityState.Modified; ctx.Customers.Attach(customer); var state = ctx.Entry(order).State; var result = ctx.SaveChanges();
};
反观上述代码,我们实例化customer和order对象,并将order添加到customer导航属性中,接下来我们将order状态修改为Modified,最后调用Attath方法附加customer,根据我们上述对单个对象的结论,此时order状态理论上应该是Unchanged,但是真的是这样?
和我们所期望的截然相反,此时通过调用attach方法并未将我们手动通过Entry方法设置状态为Modified覆盖,换言之此时造成了对象状态不一致问题,这是EF 6.x的问题,接下来我们再来看一种情况,你会发现此时会抛出异常,抛出的异常我也看不懂,也不知道它想表达啥意思(在EF Core中不会出现这样的情况,我就不占用一一篇幅说明,您可自行实践)。
using (var ctx = new EfDbContext())
{
var customer = new Customer() { Id = , Name = "Jeffcky" };
var order = new Order() { Id = };
customer.Orders.Add(order); ctx.Entry(order).State = EntityState.Deleted; ctx.Customers.Attach(customer); var state = ctx.Entry(order).State; var result = ctx.SaveChanges();
};
由上我们得出什么结论呢?在EF 6.x中使用Entry设置对象状态和调用方法对相关的对象影响将出现不一致的情况,接下来我们来对比EF 6.x和EF Core在使用上的区别,对此我们会有深刻的理解,如果我们还沿袭EF 6.x那一套,你会发现居然不好使,首先我们来看EF 6.x例子。
using (var ctx = new EfDbContext())
{
var customer = new Customer()
{
Name = "Jeffcky",
Email = "2752154844@qq.com",
Orders = new List<Order>()
{
new Order()
{
Code = "order",
CreatedTime = DateTime.Now,
ModifiedTime = DateTime.Now,
Price = ,
Quantity =
}
}
};
ctx.Customers.Add(customer);
var result = ctx.SaveChanges();
};
这里需要说明的是我将customer和order是配置了一对多的关系,从如上例子也可看出,我们调用SaveChanges方法毫无疑问会将customer和order插入到数据库表中,如下:
接下来我们手动通过Entry方法设置customer状态为Added,再来看看,如下:
using (var ctx = new EfDbContext())
{
var customer = new Customer()
{
Name = "Jeffcky",
Email = "2752154844@qq.com",
Orders = new List<Order>()
{
new Order()
{
Code = "order",
CreatedTime = DateTime.Now,
ModifiedTime = DateTime.Now,
Price = ,
Quantity =
}
}
};
ctx.Entry(customer).State = EntityState.Added;
var result = ctx.SaveChanges();
};
对照如上我们再来看看在EF Core中是如何处理的呢?直接调用Add方法就不浪费时间演示了,用过EF Core的都知道必然好使,我们看看手动设置状态。
public static void Main(string[] args)
{
using (var context = new EFCoreDbContext())
{
var blog = GetBlog();
var post = new Post()
{
CommentCount = ,
CreatedTime = DateTime.Now,
ModifiedTime = DateTime.Now,
Name = "Jeffcky"
};
context.Entry(blog).State = EntityState.Added;
var result = context.SaveChanges();
}
Console.ReadKey();
}
static Blog GetBlog()
{
return new Blog()
{
IsDeleted = false,
CreatedTime = DateTime.Now,
ModifiedTime = DateTime.Now,
Name = "Jeffcky",
Status = ,
Url = "http://www.blogs/com/createmyself"
};
}
通过实践证明此时不会将Post添加到表中,为什么会如此呢?因为我们只是手动设置了blog的状态为Added,而未对Post进行设置,看到这里想必您知道了EF 6.x和EF Core的不同。EF团队之所以这么做的目的在于如EF 6.x一样手动设置根对象的状态其导航属性即相应关联的对象也会设置,这样做会造成混乱,当我们添加对象时其导航属性也会对应添加,虽然看起来很自然,也适应一些情况,但是对象模型并不清楚主体和依赖关系,所以在EF Core中则发生了改变,通过Entry方法只会对传入对象的状态有所影响而对关联的对象不会发生任何改变,这点尤其重要,我们在使用EF Core时要格外注意,额外多说一句在EF Core通过Entry().State这个APi设置状态只会对单个对象产生影响不会对关联对象产生任何影响即忽略关联对象。
EntityFramework Core为什么在上下文中添加对应方法?
不知道使用过EF Core的您有没有发现,在EF 6.x中我们发现在上下文中并没有如暴露的DbSet上的方法比如Add、AddRange、Remove、RemoveRange等等,但是在EF Core则存在对应的方法,不知道您发现过没有,我虽然发现,但是一直不明白为何如此这样做,这样做的目的在哪里呢?我还特意看了EF Core实现源码,结果发现其内部好像还是调用了暴露在DbSet上的方法,如果我没记错的话,这样不是多此一举,吃饱了撑着了吗,有这个时间实现这样一个玩意,那怎么不早早实现通过Incude进行过滤数据呢?EF Core团队在github上讨论当前这不是优先级比较高的特性,其实不然,很多时候我们需要通过导航属性来筛选数据,少了这一步,我们只能加载到内存中再进行过滤。好了回到话题,我也是偶然看到一篇文章,才发现这样设计的目的何在,接下来我们首先来看看在EF 6.x中的上下文中没有对应的方法结果造成的影响是怎样的呢?通过实例我们一看便知。
using (var ctx = new EfDbContext())
{
var order = ctx.Orders.FirstOrDefault();
var newOrder = new Order()
{
CustomerId = order.CustomerId,
CreatedTime = DateTime.Now,
ModifiedTime = DateTime.Now,
Code = "addOrder",
Price = ,
Quantity =
};
ctx.Orders.Add(newOrder);
var result = ctx.SaveChanges();
};
特意给出如上表中数据来进行对比,如上代码我们查询出第一个Order即上图标注,然后我们重新实例化一个Order进行添加,此时您能想象到会发生什么吗?瞧瞧吧。
结果是添加到表中了,但是但是但是,重要的事情说三遍,仔细看看数据和我们要添加的Order数据对照看看,万万没想到,此时得到的数据是主键等于1的数据也就是旧数据。让我们再次回到EF Core中演示上述例子。
using (var context = new EFCoreDbContext())
{
var post = context.Posts.FirstOrDefault();
var newPost = new Post()
{
CreatedTime = Convert.ToDateTime("2018-06-01"),
ModifiedTime = Convert.ToDateTime("2018-06-01"),
Name = "《你必须掌握的Entity Framework 6.x与Core 2.0》书籍出版",
CommentCount = ,
BlogId = post.BlogId
};
context.Add(newPost);
var result = context.SaveChanges();
}
如上代码重新实例化一个Blog并添加到表中数据和如上图中数据完全不一样,我们通过上下文中暴露的Add方法来添加Blog,我们来看看最终在表中的数据是怎样的呢?
在EF Core上下文中有了Add,Attach、Remove方法以及Update和四个相关的Range方法(AddRange等等)和暴露在DbSet上的方法一样。 同时在上下文中的方法更加聪明了。 它们现在可以确定类型并自动将实体对象关联到我们想要的的DbSet。不能说很方便,而是非常方便,因为它允许我们编写通用代码而完全不需要再实例化DbSet,当然我们也可以这样做,只不过现在又多了一条康庄大道罢了,代码简易且易于发现。
即使是如下动态对象,EF Core也能正确关联到对应的对象,您亲自实践便知。
using (var context = new EFCoreDbContext())
{
dynamic newBlog = new Blog()
{
IsDeleted = true,
CreatedTime = Convert.ToDateTime("2018-06-01"),
ModifiedTime = Convert.ToDateTime("2018-06-01"),
Name = "《你必须掌握的Entity Framework 6.x与Core 2.0》书籍出版",
Status = ,
Url = "http://www.cnblogs.com/CreateMyself/p/8655069.html"
};
context.Add(newBlog);
var result = context.SaveChanges();
}
让我们再来看看一种情况来对比EF 6.x和EF Core在使用方式上的不同,首先我们来看看EF 6.x例子:
using (var ctx = new EfDbContext())
{
var customer = ctx.Customers.Include(d => d.Orders).FirstOrDefault();
var newOrder = new Order()
{
CreatedTime = DateTime.Now,
ModifiedTime = DateTime.Now,
Code = "addOrder",
Price = ,
Quantity = ,
CustomerId = customer.Id
};
ctx.Orders.Attach(newOrder);
var result = ctx.SaveChanges();
};
此时我们能够看到我们只是通过Attatch方法附加了newOrder,然后进行通过SaveChanges进行提交,此时并未提交到数据库表中,那EF Core处理机制是不是也一样呢?我们来看看:
using (var context = new EFCoreDbContext())
{
var blog = context.Blogs.FirstOrDefault();
var newPost = new Post()
{
CreatedTime = Convert.ToDateTime("2018-06-01"),
ModifiedTime = Convert.ToDateTime("2018-06-01"),
Name = "《你必须掌握的Entity Framework 6.x与Core 2.0》书籍出版",
CommentCount = ,
BlogId = blog.Id
};
context.Attach(newPost);
var result = context.SaveChanges();
}
很惊讶是不是,在EF Core最终添加到数据库表中了,依照我们对EF 6.x的理解,通过Attach方法只是将实体对象状态修改为Unchanged,如果我们想添加对象那么必须调用Add方法,此时对象状态将变为Added状态,也就是说在EF 6.x中如果我们调用Attatch方法,但是需要将对象添加到数据库表中,此时必须要调用Add方法,反之如果我们调用Add方法,那么调用Attath方法附加对象则多此一举,但是在EF Core中这种情况通过上述演示很显然发生了改变。那么EF Core内部是根据什么来判断的呢?我们来看如下源代码:
通过上述源代码不难看出在EF Core对于未设置主键都将视为添加换句话说则是如果调用Attach方法附加一个未被跟踪的对象时且主键值未被填充时,EF Core将其视为添加,所以如果我们需要添加对象时此时可直接调用Attach而无需调用Add方法。如果您依然不信,您可自行进行如下测试,也同样会被添加到表中。
public static void Main(string[] args)
{
using (var context = new EFCoreDbContext())
{
var blog = GetBlog();
context.Attach(blog);
var result = context.SaveChanges();
}
Console.ReadKey();
}
static Blog GetBlog()
{
return new Blog()
{
IsDeleted = false,
CreatedTime = DateTime.Now,
ModifiedTime = DateTime.Now,
Name = "Jeffcky",
Status = ,
Url = "http://www.blogs/com/createmyself"
};
}
EF Core团队这么做的目的是什么呢?大部分情况下通过调用Attach方法将可抵达图中所有未跟踪实体的状态设置为Unchanged,除非实体对象的主键我们没有设置且正在使用值生成,对于更新/修改同理。很显然 这对许多断开连接的实体的常见情况非常有用。但是,在许多情况下它不起作用,因为在特殊情况下是根实体,即使我们未设置主键也强制它的状态保持不变,这样做显然不合理。如果我们通过未设置主键调用Attach并最终添加它,这被认为是意外行为,我们需要放开对根实体的特殊封装,通过调用Attach方法来改变这种行为,这会使得Attach变得更加适用,它并不是突破性的改变。
Jeff自问自答模式来了,那么我们是否允许我们多次调用Attach来附加实体对象呢?您觉得是否可行呢?我们来验证下:
using (var context = new EFCoreDbContext())
{
var blog = GetBlog();
context.Attach(blog);
context.Attach(blog);
var result = context.SaveChanges();
}
EntityFramework Core为什么添加无连接跟踪图(Disconnected TrackGraph)?
追踪图是EF中全新的概念,它提供了我们对对象状态的完全控制,TrackGraph遍历图(即遍历图中的每个对象)并将指定的函数应用于每个对象。 该函数是TrackGraph方法的第二个参数。此特性的出现也是为了调用对应方法和手动设置状态而造成的混乱而给。比如我们想实现如EF 6.x一样,当调用Attach方法时不添加实体,那么我们可以如下这样做。
using (var context = new EFCoreDbContext())
{
var blog = GetBlog();
context.ChangeTracker.TrackGraph(blog, node =>
{
if (!node.Entry.IsKeySet)
{
node.Entry.State = EntityState.Unchanged;
}
});
context.Attach(blog);
var result = context.SaveChanges();
}
总结
本文我们详细讲解了EF 6.x和EF Core在实体状态上使用方式的不同且讲解了我们需要注意到二者的不同,接下来我们会继续回顾基础,感谢您的阅读,我们下节再会。
修正
关于本文用EF 6.x添加数据出现旧数据问题是我写的代码有问题,特此致歉,不知道是在什么场景会产生旧数据的问题,之前确实看过一篇文章,但是示例忘记了。
你必须知道的EntityFramework 6.x和EntityFramework Core变更追踪状态的更多相关文章
- Webservice WCF WebApi 前端数据可视化 前端数据可视化 C# asp.net PhoneGap html5 C# Where 网站分布式开发简介 EntityFramework Core依赖注入上下文方式不同造成内存泄漏了解一下? SQL Server之深入理解STUFF 你必须知道的EntityFramework 6.x和EntityFramework Cor
Webservice WCF WebApi 注明:改编加组合 在.net平台下,有大量的技术让你创建一个HTTP服务,像Web Service,WCF,现在又出了Web API.在.net平台下, ...
- (转)【推荐】初级.NET程序员,你必须知道的EF知识和经验
转自:http://www.cnblogs.com/zhaopei/p/5721789.html [推荐]初级.NET程序员,你必须知道的EF知识和经验 阅读目录 [本文已下咒.先顶后看,会涨 ...
- C#刨根究底:《你必须知道的.NET》读书笔记系列
一.此书到底何方神圣? <你必须知道的.NET>来自于微软MVP—王涛(网名:AnyTao,博客园大牛之一,其博客地址为:http://anytao.cnblogs.com/)的最新技术心 ...
- 《你必须知道的.NET》读书笔记一:小OO有大智慧
此篇已收录至<你必须知道的.Net>读书笔记目录贴,点击访问该目录可以获取更多内容. 一.对象 (1)出生:系统首先会在内存中分配一定的存储空间,然后初始化其附加成员,调用构造函数执行初 ...
- 《你必须知道的.NET》读书笔记二:小OO有大原则
此篇已收录至<你必须知道的.Net>读书笔记目录贴,点击访问该目录可以获取更多内容. 一.单一职责原则 (1)核心思想:一个类最好只做一件事,只有一个引起它变化的原因 (2)常用模式:Fa ...
- 《你必须知道的.NET》读书笔记三:体验OO之美
此篇已收录至<你必须知道的.Net>读书笔记目录贴,点击访问该目录可以获取更多内容. 一.依赖也是哲学 (1)本质诠释:“不要调用我们,我们会调用你” (2)依赖和耦合: ①无依赖,无耦合 ...
- 《你必须知道的.NET》读书笔记:从Hello World认识IL
通用的语言基础是.NET运行的基础,当我们对程序运行的结果有异议的时候,如何透过本质看表面,需要我们从底层来入手探索,这时候,IL便是我们必须知道的基础. 一.IL基础概念 1.1 什么是IL? IL ...
- MVC中你必须知道的13个扩展点
MVC中你必须知道的13个扩展点 pasting 转:http://www.cnblogs.com/kirinboy/archive/2009/06/01/13-asp-net-mvc-extensi ...
- 前端开发必须知道的JS(二) 闭包及应用
http://www.cnblogs.com/ljchow/archive/2010/07/06/1768749.html 在前端开发必须知道的JS(一) 原型和继承一文中说过下面写篇闭包,加之最近越 ...
随机推荐
- 粒子系统属性Life,发射速率和总数的关系
提示,粒子系统的life,发射速率以及总粒子数是相互影响的. 如果你要发射器射出粒子流然后停顿一会,你将简单的必须确保lifetime和发射速率相匹配以至于在发射出的粒子达到粒子总数之前一些粒子是活跃 ...
- 套接字编程相关函数(1:套接字地址结构、字节序转换、IP地址转换)
1. 套接字地址结构 1.1 IPv4套接字地址结构 IPv4套接字地址结构通常也称为“网际套接字地址结构”,它以sockaddr_in命名,定义在<netinet/in.h>头文件中.下 ...
- 为多态基类声明virtual析构函数
一个函数的返回值为基类指针,而当指针指向一个派生类对象,接下来派生类对象被这个基类指针删除的时候,就出现了局部销毁的问题.因为C++指出,当派生类经由一个基类指针被删除,而该基类指针带着一个non-v ...
- DB Query Analyzer 6.03, the most excellent Universal DB Access tools on any Microsoft Windows OS
DB Query Analyzer 6.03, the most excellent Universal database Access tools on any Microsoft Wind ...
- C语言之将无符号字符型转化为ascii码值
这个宏是在linux内核中获取的,主要的功能是能够将一个无符号字符型的参数转化为ASCII码值. ASCII : ASCII 编码里包括了128个字符.用 十进制 0 到 127 来表示 .那就对了 ...
- Java中的ReentrantLock和synchronized两种锁机制的对比
原文:http://www.ibm.com/developerworks/cn/java/j-jtp10264/index.html 多线程和并发性并不是什么新内容,但是 Java 语言设计中的创新之 ...
- 多Region下HBase写入问题
最近在集群上发现hbase写入性能受到较大下降,测试环境下没有该问题产生.而生产环境和测试环境的区别之一是生产环境的region数量远远多于测试环境,单台regionserver服务了约3500个re ...
- Java不走弯路教程(5.Client-Server模式(2)-Client)
5.Client-Server模式(2)-Client 在上一章,我们完成一个简单的数据库服务器,并在客户端用telnet方式成功进行通信. 本章将用Java实现客户端程序,来代替telnet. 先看 ...
- jquery 加法 乘法运算 精确计算函数
int类型相加不会出现问题,但小数点相加就会出现问题 //乘法函数 var accMul = function(arg1, arg2){ var m=0,s1=arg1.toString(),s2=a ...
- android + php 后台开发
android+php 安卓与服务器的数据交互 在我们进行android开发的时候,避免不了的要进行登录注册,个人信息获取,数据交互等等这一系列的操作.这样就需要进行android端与服务器端进行数据 ...