zoukankan      html  css  js  c++  java
  • Entity Fr“.NET技术”amework 4.1 Code First 学习之路(二) 狼人:

      写系列的上一篇已经是很久之前的事儿了= =在此期间,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);上海网站建设r /> 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-

  • 相关阅读:
    win10 UWP button
    内网分享资源
    内网分享资源
    CF724F Uniformly Branched Trees
    win10 UWP FlipView
    win10 UWP FlipView
    win10 UWP FlipView
    搭建阿里云 centos mysql tomcat jdk
    搭建阿里云 centos mysql tomcat jdk
    win10 UWP 申请微软开发者
  • 原文地址:https://www.cnblogs.com/waw/p/2210672.html
Copyright © 2011-2022 走看看