写系列的上一篇已经是很久之前的事儿了= =在此期间,EF 4.1的RTW都已经出来了,NH 3.2的Alpha已经2了。。。其实不是我懒,工作中也在一直使用EF 4.1。主要是上次承诺过的一个Update功能搞不定= =

总之这一次的目标是

  • 实现一个完整的IRepository(添加增删改能力)
  • 领域对象的继承
  • 事物

首先来看IRepository

我的接口如下

   1: public interface IRepository<TEntity>
   2:     where TEntity : IEntity
   3: {
   4:     IEnumerable<TEntity> FindAll();
   5:     TEntity FindById(int id);
   6:     void Add(TEntity entity);
   7:     void Delete(TEntity entity);
   8:     void Update(TEntity entity);
   9: }

应该算是一个最基本的仓储接口了。

其中前几个接口都是很好实现的,上次提及的DbSet对象提供了相应的接口,直接调用即可,代码是类似这样的。

   1: protected DbSet<TEntity> DbSet
   2: {
   3:     get { return m_dbContext.Set<TEntity>(); }
   4: }
   5:  
   6: public IEnumerable<TEntity> FindAll()
   7: {
   8:     return DbSet;
   9: }
  10:  
  11: public TEntity FindById(int id)
  12: {
  13:     return DbSet.SingleOrDefault(entity => entity.Id == id);
  14: }
  15:  
  16: public void Add(TEntity entity)
  17: {
  18:     DbSet.Add(entity);
  19:     m_dbContext.SaveChanges();
  20: }
  21:  
  22: public void Delete(TEntity entity)
  23: {
  24:     DbSet.Remove(entity);
  25:     m_dbContext.SaveChanges();
  26: }

关键问题是最后的Update方法

DbSet对象并没有提供相应的接口,为什么呢?因为EF相信自己的Self Tracking能力。也就是说,EF认为把一个entity从context中加载出来,做一些变更,然后直接SaveChanges就可以了,不需要特意提供一个Update方法。

但是这里有一个前提,就是“entity是从context中加载出来”。如果entity是新new出来的呢?比如在MVC里,entity很可能是ModelBinder帮我们new出来的,context对它一无所知,直接SaveChanges显然不会有任何效果。

那么如何让context可以理解一个新new出来的entity呢?这要从EF处理entity状态开始说起。

EF定义了如下几种State(注意这个枚举是Flag)

   1: [Flags]
   2: public enum EntityState
   3: {
   4:     Detached = 1,
   5:     Unchanged = 2,
   6:     Added = 4,
   7:     Deleted = 8,
   8:     Modified = 16,
   9: }

其中Detached状态,就是entity还没有attach到context(实际上是Attach到某个DbSet上)的状态。具体怎么做呢?直接上代码

   1: public void Update(TEntity entity)
   2: {
   3:     var entry = m_dbContext.Entry(entity);
   4:     if (entry.State == EntityState.Detached)
   5:     {
   6:         //DbSet.Attach(entity);
   7:         entry.State = EntityState.Modified;
   8:     }
   9:     m_dbContext.SaveChanges();
  10: }

可以看到上面的代码给出了两种办法,一种是直接修改entry的State,另一种是调用DbSet对象的Attach方法。

注意到DbContext.Entry方法取出的DbEntityEntry对象。利用这个对象可以做很多有用的事哦~~园子里的EF专家LingzhiSun有一篇blog,大家可以去读读。

不过这个实现有一个缺陷

我们上面谈到过,上面这个实现实际上是把entity attach到了对应的DbSet上。但是如果你的代码是类似如下的,就可能产生问题(没有亲试,感觉上是这样的= =)

   1: var heros = repository.FindAll();
   2: var hero = heros.First(h => h.Id == 1);
   3: var heroNew = new Hero
   4: {
   5:     Id = hero.Id,
   6:     Name = hero.Name,
   7:     Race = hero.Race
   8: };
   9: repository.Update(heroNew);

应该是会抛出来一个异常说“An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key.”

异常说的很明白,你的DbSet已经加载过一次id为1的对象了,当试图去attach另一个id为1的对象的时候EF就会无所适从。

那是不是说刚才给出的那个实现根本就行不通呢?不是的!事实上微软官方的文章上就是采用这种方法的。关键就在于当你尝试去attach一个entity的时候,要保证DbSet还没有加载过!我们看上面那篇微软的文章里是如何保证这一点的

   1: public class BlogController : Controller
   2: {
   3:   BlogContext db = new BlogContext();
   4:  
   5:     //...
   6:  
   7:     [HttpPost]
   8:     public ActionResult Edit(int id, Blog blog)
   9:     {
  10:         try
  11:         {
  12:           db.Entry(blog).State = EntityState.Modified;
  13:           db.SaveChanges();
  14:  
  15:             return RedirectToAction("Index");
  16:         }
  17:         catch
  18:         {
  19:             return View();
  20:         }
  21:     }
  22: }

很明显,在执行Edit这个Action之前,DbSet没有加载过,因为MVC帮我们保证了DbContext实例是request结束就被销毁的.

也就是说,结论是使用这种Update实现方式对context的生命周期是有要求的.当然我的例子中context的生命周期也是per-request的所以没关系。

那么如果我们想使用其他的context生命周期管理方式呢?比如希望整个application只有一个context实例?

让我们来给出另一种实现

回过头来想一想在实现Update这个方法的时候我们最初遇到的问题:entity不是从context中加载的而是直接new出来的

那么我们手动的来加载一次就好了么,代码类似于这样

   1: public void Update(Hero entity)
   2: {
   3:     var entry = m_dbContext.Entry(entity);
   4:     if (entry.State == EntityState.Detached)
   5:     {
   6:         Hero entityToUpdate = FindById(entity.Id);
   7:         entityToUpdate.Id = entity.Id;
   8:         entityToUpdate.Name = entity.Name;
   9:         entityToUpdate.Race = entity.Race;
  10:     }
  11:     m_dbContext.SaveChanges();
  12: }

不过由于失去了泛型的优势,给每个domain model都要实现一个Update方法比较烦,可以用一些框架来解决这个问题,例如EmitMapper(园子里也讨论过这个东西)

   1: public void Update(TEntity entity)
   2: {
   3:     var entry = m_dbContext.Entry(entity);
   4:     if (entry.State == EntityState.Detached)
   5:     {
   6:         var entityToUpdate = FindById(entity.Id);
   7:         EmitMapper.ObjectMapperManager.DefaultInstance.GetMapper<TEntity, TEntity>().Map(entity, entityToUpdate);
   8:     }
   9:     m_dbContext.SaveChanges();
  10: }

当然这个实现也有不好的地方例如说当domain里有一些跟ORM没关系的property时也会被EmitMapper改写掉。

下一个议题是领域对象的继承

让领域对象实现继承的好处是不言而喻的,可以使用到多态等OO带来的好处。相对的就对ORM提出了更高的要求。

我们知道映射对象树到数据库有三种经典的实现方式:Table Per Type、Table Per Hierarchy和Table Per Concrete class,这次我们来实践最简单的一种:Table Per Hierarchy。

回想我们上一次的类

   1: public class Hero : IEntity
   2: {
   3:     public int Id { get; set; }
   4:     public string Name { get; set; }
   5:     public bool IsSuperHero { get; set; }
   6:     public virtual Race Race { get; set; }
   7: }

把它拆成两个有继承关系的类

   1: public class Hero : IEntity
   2: {
   3:     public int Id { get; set; }
   4:     public string Name { get; set; }
   5:     //public bool IsSuperHero { get; set; }
   6:     public virtual Race Race { get; set; }
   7: }
   8: public class SuperHero : Hero
   9: {
  10:     
  11: }

在EF Code First中这种单表继承的映射关系是这样来写的

   1: Map<Hero>(hero => hero.Requires(ColumnNameMappingStrategy.Value.To("IsSuperHero")).HasValue(false)).ToTable(tableNameMappingStrategy.To("Hero"));
   2: Map<SuperHero>(hero => hero.Requires(ColumnNameMappingStrategy.Value.To("IsSuperHero")).HasValue(true)).ToTable(tableNameMappingStrategy.To("Hero"));

另外两种方式的实现也不复杂,可以参考这里。这个实例还是CTP5的API,跟4.1最终版有些区别不过应该影响不大。

今天最后的议题是事物

可以用TransactionScope来管理,虽然看起来有些浪费,毕竟例子中不涉及Transaction传播,连DbContext都只有一个实例。代码如下

   1: [HttpPost]
   2: public ActionResult Edit(TEntity entity)
   3: {
   4:     try
   5:     {
   6:         using (var scope = new TransactionScope())
   7:         {
   8:             ModelRepository.Update(entity);
   9:             scope.Complete();
  10:         }
  11:         return RedirectToAction("Index");
  12:     }
  13:     catch
  14:     {
  15:         return View();
  16:     }
  17: }

Spring实际上也可以用AOP的方式管理TransactionScope。不过我倾向于手动管理Transaction。

代码下载

本次的代码请参考这个changeset

今天就到这里-v-

转载于:https://www.cnblogs.com/jiaxingseng/archive/2011/04/22/2024934.html

Entity Framework 4.1 Code First学习之路(二)相关推荐

  1. MVC中使用Entity Framework 基于方法的查询学习笔记 (二)

    解释,不解释: 紧接上文,我们在Visual Studio2012中看到系统为我们自动创建的视图(View)文件Index.cshtml中,开头有如下这句话: @model IEnumerable&l ...

  2. EFMVC - ASP.NET MVC 3 and Entity Framework 4.1 Code First 项目介绍

    项目概述 使用ASP.NET MVC 3.Razor.EF Code First.Unity 2.0 等等技术,演示如何创建一个ASP.NET MVC 3 的范例应用程序. 相关技术帖子: 中文: 使 ...

  3. 【转】学习Entity Framework 中的Code First

    这是上周就写好的文章,是在公司浩哥的建议下写的,本来是部门里面分享求创新用的,这里贴出来分享给大家. 最近在对MVC的学习过程中,接触到了Code First这种新的设计模式,感觉很新颖,并且也体验到 ...

  4. Entity Framework(EF的Code First方法)

    EntityFramework,是Microsoft的一款ORM(Object-Relation-Mapping)框架.同其它ORM(如,NHibernate,Hibernate)一样, 一是为了使开 ...

  5. Entity Framework 6以Code First方式搭建Sqlite数据库环境

    对于EF支持Sqlite数据库映射,网上似乎说得都不是很清楚,自己研究了会儿,现在给大家分享下~ 所使用的库版本 EntityFramework.6.1.0 SQLite.1.0.92.0 以上两个库 ...

  6. Entity Framework 6.x Code First 基础

    安装扩展工具 "Entity Framework Power Tools Beta4" 可选, 主要用于数据库变结构反向生成C#的对象和对应的mapping类.如果你熟悉mappi ...

  7. python 剑指offer 学习之路(二)

    剑指offer 学习之路 合并两个排序的链表 树的子结构 顺时针打印矩阵 包含min函数的栈 从上往下打印二叉树 二叉搜索树的后序遍历序列 二叉树中和为某一值的路径 复杂链表的复制 数组中出现次数超过 ...

  8. 前端Vue学习之路(二)-Vue-router路由

    Vue学习之路 (二) Vue-router(基础版) 一.增加静态路由 二.动态路由+路由嵌套+404页面 三. 编程式导航 四.命名路由 五.命名视图 六.重定向和起别名 1.重定向 2.起别名 ...

  9. Hive学习之路(二):Hive表操作详讲

    操作内容简介 一.操作前的准备 二.Hive表操作详讲 1. 创建数据库 2. 查看所有数据库/表 3. 在Hive上直接操作HDFS 4. 在Hive上直接执行终端命令 5. 创建数据表/查看表的信 ...

  10. Entity Framework 5.0 Code First全面学习

    Code First 约定 借助 CodeFirst,可通过使用 C# 或Visual Basic .NET 类来描述模型.模型的基本形状可通过约定来检测.约定是规则集,用于在使用 Code Firs ...

最新文章

  1. 计算机内存作图多大合适3d,【2人回答】你好,我想问问电脑256G内存对于学生画图设计够吗?-3D溜溜网...
  2. HashSet中的add()方法( 四 )(详尽版)
  3. android 插补器Interpolator的使用
  4. tensorflow学习笔记:tf.control_dependencies,tf.GraphKeys.UPDATE_OPS,tf.get_collection
  5. Js拼接嵌套php代码,分享一个js文件中嵌套php会出错的问题
  6. “程序员千万不要选全栈开发”
  7. unity隔一段时间再显示_Unity3D内置倒计时!从此再不拖延!
  8. 记字符编码与转义符的纠缠
  9. 实验题集4:函数R6-1 面积计算器(函数重载) (10 分)
  10. python不是内部命令或外部命令,也不是可执行程序解决方案”解决方法
  11. 常用相机投影及畸变模型(针孔|广角|鱼眼)
  12. android铃声代码,Android之来电铃声设置(示例代码)
  13. 190426网络编程
  14. HIF转16位TIF或者PNG
  15. MySQL闪回工具之my2sql
  16. 深入计算机组成原理(四)穿越功耗墙,我们该从哪些方面提升“性能”?
  17. iOS 改变图片颜色
  18. 一键修复wpcap.dll文件丢失或出错
  19. python selenium unittest_使用python学习selenium2--使用unittest进行测试
  20. 塔望 用食品改变世界

热门文章

  1. 【学习笔记】平衡二叉树(AVL树)简介及其查找、插入、建立操作的实现
  2. SVM支持向量和逻辑回归的decision_function用法详解
  3. cart算法 java_决策树学习笔记(三):CART算法,决策树总结
  4. [codeup 1128]出租车费
  5. C/C++[codeup 1926]EXCEL排序
  6. 睡后收益 -- CSDN博客打赏功能及自定义模块以及代码分享
  7. 阿里云云计算 13 OSS的优势和使用场景
  8. shell脚本编写笔记
  9. c:forEach无法显示信息的可能原因以及需要注意的地方
  10. 2018_09_25_参加医学人工智能大会的个人思考