AppBox 是基于 FineUI 的通用权限管理框架,包括用户管理、职称管理、部门管理、角色管理、角色权限管理等模块。

Attach方法

前面我们已经多次使用Attach方法,上一次使用Attach方法修改用户所属部门的代码如下所示:

if (String.IsNullOrEmpty(hfSelectedDept.Text))
{
item.Dept = null;
}
else
{
int newDeptID = Convert.ToInt32(hfSelectedDept.Text);
if (item.Dept.DeptID != newDeptID)
{
Dept newDept = new Dept { DeptID = newDeptID };
DB.Depts.Attach(newDept);
item.Dept = newDept;
}
}

其中 newDeptID 是通过弹出窗口或者下拉列表选择的部门ID,也就是说这个 newDeptID 其实是存在于数据库中的,只不过还没有被加载到内存中。

这种情况非常适合使用 Attach 方法,从而避免了一次数据库查询来生成Dept对象:

Dept dept = DB.Depts.Where(d => d.ID == newDeptID).FirstOrDefault();

取而代之,我么使用Attach方法,就好像这个部门已经被加载到内存中一样:

Dept newDept = new Dept { DeptID = newDeptID };
DB.Depts.Attach(newDept);

官方对Attach的解释:http://msdn.microsoft.com/en-us/library/system.data.entity.dbset.attach(v=vs.103).aspx

Attach is used to repopulate a context with an entity that is known to already exist in the database. SaveChanges will therefore not attempt to insert an attached entity into the database because it is assumed to already be there. Entities that are already in the context in some other state will have their state set to unchanged. Attach is a no-op if the entity is already in the context in the unchanged state.  

简单的翻译:Attach用来将某个已知存在于数据库中的实体重新加载到上下文中。SaveChanges不会尝试将Attached的实体插入到数据库中,因为这个实体假设已经存在于数据库中。  

  

Attach陷阱

为了更好的说明使用Attach过程中可能会遇到的问题,我么从如下简单的例子入手:

User admin = DB.Users.Where(u => u.Name == "admin").FirstOrDefault();

Dept dept = new Dept { ID = 1 };
DB.Depts.Attach(dept); admin.Dept = dept;
DB.SaveChanges();

这个示例完成了如下操作:

1. 从数据库中加载用户名为 admin 的用户;

2. 将ID为1的部门附加到EF上下文中;

3. 设置admin用户所属的部门为上述部门;

4. 保存改变。

如果第一次执行这段代码,会有两个对数据库的操作,如下图所示:

  

如果第二次执行这段代码,由于已经将用户数据加载到EF上下文,所以对用户部门的更新不会做任何改变,数据库操作只有一次查询操作:

  

一切看似没有任何问题,直到我们遇到如下代码:

DB.Depts.Find(1);

User admin = DB.Users.Where(u => u.Name == "admin").FirstOrDefault();

Dept dept = new Dept { ID = 1 };
DB.Depts.Attach(dept); admin.Dept = dept;
DB.SaveChanges();

在这段代码中,ID为1的部门已经被加载到EF上下文中,此时再次Attach同一个对象,就会出错:

  

跳出Attach陷阱

解决办法也很简单,我们需要现在EF的Local缓存中查找对象,如果找不到再Attach新对象。关于Local对象的详细信息:http://msdn.microsoft.com/en-us/data/jj592872

为了方便代码调用,我们在页面基类PageBase中增加了一个Attach方法:

// 附加实体到数据库上下文中(首先在Local中查找实体是否存在,不存在才Attach,否则会报错)
protected T Attach<T>(int keyID) where T : class, IKeyID, new()
{
T t = DB.Set<T>().Local.Where(x => x.ID == keyID).FirstOrDefault();
if (t == null)
{
t = new T { ID = keyID };
DB.Set<T>().Attach(t);
}
return t;
}

  

因此完成上述示例正确的代码为:

DB.Depts.Find(1);

User admin = DB.Users.Where(u => u.Name == "admin").FirstOrDefault();

Dept dept = Attach<Dept>(1);
admin.Dept = dept;
DB.SaveChanges();

  

你可能也注意到了,我们为实体类增加了一个 IKeyID 的接口,这是我们手工增加的:

public interface IKeyID
{
int ID { get; set; } }

Dept实体类实现了 IKeyID 接口,定义如下所示:

public class Dept : IKeyID
{
[Key]
public int ID { get; set; } [Required, StringLength(50)]
public string Name { get; set; } [Required]
public int SortIndex { get; set; } [StringLength(500)]
public string Remark { get; set; } public virtual Dept Parent { get; set; }
public virtual ICollection<Dept> Children { get; set; } public virtual ICollection<User> Users { get; set; } }

  

深入理解Attach

1. DBContext的作用域 

大家要理解一点,之所以出现上述异常,是因为我们将 DBContext 的实例保存在 HttpContext 中,在本系列的第一篇文章就有描述(One DbContext per Request)。

因此,我们很难在Attach时得知此对象是否已经被加载到EF的上下文中。相反,如果使用如下代码,则可能就不会遇到那个异常了(我们明确知道DBContext的作用域,并知道其中加载了哪些实体对象):

using(var db = new AppBoxContext())
{
db.Depts.Find(1);
} using(var db = new AppBoxContext())
{
User admin = db.Users.Where(u => u.Name == "admin").FirstOrDefault(); Dept dept = new Dept { ID = 1 };
db.Depts.Attach(dept);
admin.Dept = dept; db.SaveChanges();
}

  

2. 不要对Attach抱有过多幻想

有些时候,我们可能对Attach的期望值过高了,比如下面代码:

User admin = DB.Users.Where(u => u.Name == "admin").FirstOrDefault();

Dept dept = new Dept { Name = "研发部" };
DB.Depts.Attach(dept); admin.Dept = dept;
DB.SaveChanges();

我们本来希望是将admin用户的部门设为“研发部”,可惜这段代码会报错:

看错误提示,我们知道是执行的SQL语句违反了外键约束。

查看执行的SQL语句,我们会发现EF试图将ID为0的部门更新到用户表。实际上,Depts表不存在ID为0的部门!

其实,我们创建部门的代码:

Dept dept = new Dept { Name = "研发部" };

创建了一个ID为0的部门。EF并不会对此有效性进行检查,更不会查询数据库获取此部门的ID。EF的会假设这个实体已经存在于数据库中了,而我们开发人员需要保证这个假设成立!  

 

下载或捐赠AppBox

1. AppBox v2.0 是免费软件,免费提供下载:http://fineui.com/bbs/forum.php?mod=viewthread&tid=3788

2. AppBox v3.0 是捐赠软件,你可以通过捐赠作者来获取AppBox v3.0的全部源代码(http://fineui.com/donate/)。

AppBox升级进行时 - Attach陷阱(Entity Framework)的更多相关文章

  1. AppBox升级进行时 - 拥抱Entity Framework的Code First开发模式

    AppBox 是基于 FineUI 的通用权限管理框架,包括用户管理.职称管理.部门管理.角色管理.角色权限管理等模块. 从Subsonic到Entity Framework Subsonic最早发布 ...

  2. AppBox升级进行时 - 扁平化的权限设计

    AppBox 是基于 FineUI 的通用权限管理框架,包括用户管理.职称管理.部门管理.角色管理.角色权限管理等模块. AppBox v2.0中的权限实现 AppBox v2.0中权限管理中涉及三个 ...

  3. AppBox升级进行时 - Entity Framework的增删改查

    AppBox 是基于 FineUI 的通用权限管理框架,包括用户管理.职称管理.部门管理.角色管理.角色权限管理等模块. Entity Framework新增数据 以新增用户为例,作为对比,先来看下使 ...

  4. AppBox升级进行时 - 关联表查询与更新(Entity Framework)

    AppBox 是基于 FineUI 的通用权限管理框架,包括用户管理.职称管理.部门管理.角色管理.角色权限管理等模块. 关联表的查询操作 使用 Include 方法,我们可以在一次数据库查询中将关联 ...

  5. AppBox升级进行时 - 如何向OrderBy传递字符串参数(Entity Framework)

    AppBox 是基于 FineUI 的通用权限管理框架,包括用户管理.职称管理.部门管理.角色管理.角色权限管理等模块. Entity Framework提供的排序功能 再来回顾一下上篇文章,加载用户 ...

  6. AppBox升级进行时 - Any与All的用法(Entity Framework)

    AppBox 是基于 FineUI 的通用权限管理框架,包括用户管理.职称管理.部门管理.角色管理.角色权限管理等模块. 属于某个角色的用户列表(Any的用法) 使用Subsonic,我们有两种方法获 ...

  7. ASP.NET Core 快速入门(Razor Pages + Entity Framework Core)

    引子 自从 2009 年开始在博客园写文章,这是目前我写的最长的一篇文章了. 前前后后,我总共花了 5 天的时间,每天超过 3 小时不间断写作和代码调试.总共有 8 篇文章,每篇 5~6 个小结,总截 ...

  8. Entity Framework Code First实体对象变动跟踪

    Entity Framework Code First通过DbContext.ChangeTracker对实体对象的变动进行跟踪,实现跟踪的方式有两种:变动跟踪快照和变动跟踪代理. 变动跟踪快照:前面 ...

  9. Entity Framework 支持 DataTable

    转载:http://www.cnblogs.com/wlflovenet/archive/2011/12/30/EF11.html https://www.cnblogs.com/hanjun0612 ...

随机推荐

  1. javascript中的scrollTop

    含义:滚动条高度 作用:滚动加载(ajax),滚动导航固定定位,滚动弹框定位等等. 展示滚动导航和侧边栏滚动固定定位的效果:查看效果 1.chrome浏览器 document.body.scrollT ...

  2. 第一次react-native项目实践要点总结

    今天完成了我的第一个react-native项目的封包,当然其间各种环境各种坑,同时,成就感也是满满的.这里总结一下使用react-native的一些入门级重要点(不涉及环境).注意:阅读需要语法基础 ...

  3. ae GP制作缓冲区分析

    private void barButtonItem1_ItemClick(object sender, DevExpress.XtraBars.ItemClickEventArgs e) { // ...

  4. [Android]从Launcher开始启动App流程源码分析

    以下内容为原创,欢迎转载,转载请注明 来自天天博客:http://www.cnblogs.com/tiantianbyconan/p/5017056.html 从Launcher开始启动App流程源码 ...

  5. SqlServer--模糊查询-通配符

    查询所有姓张的同学Select * from student where left(sName,1)='张'   看上去很美,如果改成查询名字中带亮的学生怎么做?换一种做法 like  Select  ...

  6. Redhat Server 5.7 安装配置PHP

    PHP的简介 PHP于1994年由Rasmus Lerdorf创建,刚刚开始是Rasmus Lerdorf 为了要维护个人网页而制作的一个简单的用Perl语言编写的程序.这些工具程序用来显示 Rasm ...

  7. SQLite学习笔记(六)&&共享缓存

    介绍 通常情况下,sqlite中每个连接都会一个独立的pager对象,pager对象中管理了该连接的缓存信息,通过pragma cache_size指令可以设置缓存大小,默认是2000个page,每个 ...

  8. MongoDB学习笔记~索引提高查询效率

    回到目录 索引这个东西大家不会陌生,只要接触到稍微大一点的数据,都会用到这东西,它可以提升查询的速度,相当代价就是占用了更多的存储空间,这也是正常的,符合“能量守恒定理”,哈哈!今天说的是MongoD ...

  9. Asp.Net MVC+BootStrap+EF6.0实现简单的用户角色权限管理3

    首先在webconfig中加入下面这句代码,这个主要是用来生成数据库的连接字符串 <connectionStrings> <add name="AuthorDesignCo ...

  10. Memcached学习笔记

    [TOC] 前言 此为学习笔记汇总,如有纰漏之处,还望不吝指出,谢谢. 启动流程 调用settings_init()设定初始化参数 从启动命令中读取参数来设置setting值 设定LIMIT参数 开始 ...