一起谈.NET技术,Entity Framework 4.1 Code First 学习之路(二)

简介:   写系列的上一篇已经是很久之前的事儿了= =在此期间,EF 4.1的RTW都已经出来了,NH 3.2的Alpha已经2了。。。其实不是我懒,工作中也在一直使用EF 4.1。主要是上次承诺过的一个Update功能搞不定= =  总之这一次的目标是:实现一个完整的IRepository(添加增删...

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

  总之这一次的目标是:

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

  首先来看IRepository

  我的接口如下:

 
   
public interface IRepository < TEntity >
where TEntity : IEntity
{
IEnumerable
< TEntity > FindAll();
TEntity FindById(
int id);
void Add(TEntity entity);
void Delete(TEntity entity);
void Update(TEntity entity);
}
应该算是一个最基本的仓储接口了。

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

 
 
protected DbSet < TEntity > DbSet
{
get { return m_dbContext.Set < TEntity > (); }
}

public IEnumerable < TEntity > FindAll()
{
return DbSet;
}

public TEntity FindById( int id)
{
return DbSet.SingleOrDefault(entity => entity.Id == id);
}

public void Add(TEntity entity)
{
DbSet.Add(entity);
m_dbContext.SaveChanges();
}

public void Delete(TEntity entity)
{
DbSet.Remove(entity);
m_dbContext.SaveChanges();
}

  关键问题是最后的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)

 
 
[Flags]
public enum EntityState
{
Detached
= 1 ,
Unchanged
= 2 ,
Added
= 4 ,
Deleted
= 8 ,
Modified
= 16 ,
}

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

 
 
public void Update(TEntity entity)
{
var entry
= m_dbContext.Entry(entity);
if (entry.State == EntityState.Detached)
{
// DbSet.Attach(entity);
entry.State = EntityState.Modified;
}
m_dbContext.SaveChanges();
}

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

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

  不过这个实现有一个缺陷

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

 
 
var heros = repository.FindAll();
var hero
= heros.First(h => h.Id == 1 );
var heroNew
= new Hero
{
Id
= hero.Id,
Name
= hero.Name,
Race
= hero.Race
};
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还没有加载过!我们看上面那篇微软的文章里是如何保证这一点的:

 
   
public class BlogController : Controller
{
BlogContext db
= new BlogContext();

// ...

[HttpPost]
public ActionResult Edit( int id, Blog blog)
{
try
{
db.Entry(blog).State
= EntityState.Modified;
db.SaveChanges();
return RedirectToAction( " Index " );
}
catch
{
return View();
}
}
}
很明显,在执行Edit这个Action之前,DbSet没有加载过,因为MVC帮我们保证了DbContext实例是request结束就被销毁的。

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

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

  让我们来给出另一种实现

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

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

 
 
public void Update(Hero entity)
{
var entry
= m_dbContext.Entry(entity);
if (entry.State == EntityState.Detached)
{
Hero entityToUpdate
= FindById(entity.Id);
entityToUpdate.Id
= entity.Id;
entityToUpdate.Name
= entity.Name;
entityToUpdate.Race
= entity.Race;
}
m_dbContext.SaveChanges();
}

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

 
   
public void Update(TEntity entity)
{
var entry
= m_dbContext.Entry(entity);
if (entry.State == EntityState.Detached)
{
var entityToUpdate
= FindById(entity.Id);
EmitMapper.ObjectMapperManager.DefaultInstance.GetMapper
< TEntity, TEntity > ().Map(entity, entityToUpdate);
}
m_dbContext.SaveChanges();
}
当然这个实现也有不好的地方例如说当domain里有一些跟ORM没关系的property时也会被EmitMapper改写掉。

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

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

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

  回想我们上一次的类:

 
 
public class Hero : IEntity
{
public int Id { get ; set ; }
public string Name { get ; set ; }
public bool IsSuperHero { get ; set ; }
public virtual Race Race { get ; set ; }
}

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

 
   
public class Hero : IEntity
{
public int Id { get ; set ; }
public string Name { get ; set ; }
// public bool IsSuperHero { get; set; }
public virtual Race Race { get ; set ; }
}
public class SuperHero : Hero
{
}
在EF Code First中这种单表继承的映射关系是这样来写的:
 
 
Map < Hero > (hero => hero.Requires(ColumnNameMappingStrategy.Value.To( " IsSuperHero " )).HasValue( false )).ToTable(tableNameMappingStrategy.To( " Hero " ));
Map
< SuperHero > (hero => hero.Requires(ColumnNameMappingStrategy.Value.To( " IsSuperHero " )).HasValue( true )).ToTable(tableNameMappingStrategy.To( " Hero " ));

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

  今天最后的议题是事物

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

 
 
[HttpPost]
public ActionResult Edit(TEntity entity)
{
try
{
using (var scope = new TransactionScope())
{
ModelRepository.Update(entity);
scope.Complete();
}
return RedirectToAction( " Index " );
}
catch
{
return View();
}
}

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

  代码下载

  本次的代码请参考这个changeset

  今天就到这里-v-

目录
相关文章
|
4月前
|
API C++ Windows
Visual C++运行库、.NET Framework和DirectX运行库的作用及常见问题解决方案,涵盖MSVCP140.dll丢失、0xc000007b错误等典型故障的修复方法
本文介绍Visual C++运行库、.NET Framework和DirectX运行库的作用及常见问题解决方案,涵盖MSVCP140.dll丢失、0xc000007b错误等典型故障的修复方法,提供官方下载链接与系统修复工具使用指南。
1062 2
|
4月前
|
监控 Cloud Native 测试技术
.NET技术深度解析:现代企业级开发指南
每日激励:“不要一直责怪过去的自己,他曾经站在雾里也很迷茫”。我是蒋星熠Jaxonic,一名在代码宇宙中探索的极客旅人。从.NET Framework到.NET 8,我深耕跨平台、高性能、云原生开发,践行领域驱动设计与微服务架构,用代码书写技术诗篇。分享架构演进、性能优化与AI融合前沿,助力开发者在二进制星河中逐光前行。关注我,共探技术无限可能!
.NET技术深度解析:现代企业级开发指南
|
4月前
|
人工智能 API 数据库
Semantic Kernel .NET 架构学习指南
本指南系统解析微软Semantic Kernel .NET架构,涵盖核心组件、设计模式与源码结构,结合实战路径与调试技巧,助你从入门到贡献开源,掌握AI编排开发全栈技能。
456 2
|
4月前
|
开发框架 安全 .NET
Microsoft .NET Framework 3.5、4.5.2、4.8.1,适用于 Windows 版本的 .NET,Microsoft C Runtime等下载
.NET Framework是Windows平台的开发框架,包含CLR和FCL,支持多种语言开发桌面、Web应用。常用版本有3.5、4.5.2、4.8.1,系统可同时安装多个版本,确保软件兼容运行。
1050 0
Microsoft .NET Framework 3.5、4.5.2、4.8.1,适用于 Windows 版本的 .NET,Microsoft C Runtime等下载
|
5月前
|
C++
提示缺少.NET Framework 3.5 安装错误:0x80070002、0x800F0950\0x80004002
.NET Framework常见问题及解决方法汇总,
722 0
|
6月前
.NET Framework 3.5离线安装包合集下载
本文介绍了如何获取和安装.NET Framework运行库离线合集包。用户可通过提供的链接下载安装包,安装过程简单,按提示逐步操作即可完成。安装时可选择所需版本,工具会自动适配架构,无需手动判断,方便高效。
5355 0
|
监控 前端开发 API
一款基于 .NET MVC 框架开发、功能全面的MES系统
一款基于 .NET MVC 框架开发、功能全面的MES系统
520 5
|
开发框架 前端开发 JavaScript
ASP.NET MVC 教程
ASP.NET 是一个使用 HTML、CSS、JavaScript 和服务器脚本创建网页和网站的开发框架。
310 7