Charley Blog 代码剖析【数据层】


阅读须知

阅读难度:高

所须背景知识:懂C#、了解ORM和简单的设计模式思想


上期回顾

上一篇中,我介绍了自己在开发伊始时所做的一些事情,主要包括需求分析、进度安排和概要设计。在这个过程中有过迷茫和纠结,但很庆幸最后得到了想要的结果。


在本篇中,我会重点介绍下数据层的搭建。该层位于整个架构的最底层,只有确保数据层的健壮性,才能为整个项目提供正常运行的保证。OK,废话少说,Let's go!


创建项目,建立框架

先新建一个空白的solution文件,然后创建三个文件夹,分别叫Charley.Blog.UICharley.Blog.BLLCharley.Blog.DAL。顾名思义,UI文件夹主要用于放和表示层相关的代码,BLL文件夹则用于存放业务逻辑相关的代码,而与数据层相关的一切代码则保存在DAL文件夹中。整体的效果如下图所示:

图 1:解决方案


我在上一篇中曾提到采用ASP.NET MVC模式来构建表示层,所以在UI文件夹中直接创建ASP.NET MVC 3 Web应用程序。BLL.ServiceCore中存储业务逻辑的接口代码,而具体的实现则由BLL.Service负责。DAL层是今天的主角,稍后将详细介绍。


数据层

啰嗦了这么多,终于要开始介绍数据层了。在进入正题前,还是请大家先思考两个问题:1. 数据层提供哪些服务?   2. 说说你心目中良好设计的数据层应该符合哪些条件呢?


答1:CRUD操作、事务管理、延迟加载

答2:数据库的独立性(不依赖于任何的数据库,可以在多个数据库间任意切换)、OO(能够使用OO的方式进行CRUD的操作)

我的答案,欢迎拍砖(只要大家选中上面的空白区域,答案便将奇迹般地出现)


什么是数据库的独立性?试想你去菜场买菜,如果让你直接和种菜的农民伯伯联系,价格上可能便宜了,但是万一哪天这个农民伯伯有事儿,那恐怕那天的饭桌上就要冷冷清清了。因此为了减小这种潜在的风险,我就会选择找卖菜的小贩。中间多了一层,价格上虽然上去了,但是我再也不用去担心农民伯伯的事情了(这里有个前提:小贩是相对固定的,可以保证永远提供服务)。回到数据库的话题上来,农民伯伯相当于具体的数据库,消费者则是上层的业务层,小贩则是这两者之前的抽象层。业务层永远不需要去担心数据库是MySql,还是Sql Server,更或者是NoSql等,它只需要告诉抽象层:“我要100条用户数据!”,然后就等着接收吧。

图 2:数据库的独立性,虚线框起来的就是抽象层,大家也可以查看Repository的官方定义


OO这个东东,相信只要是做过软件开发都认识,用这种方式去操作数据库更直观也更容易维护,实现这种OO的方式就是采用ORM(对象关系映射)。


图 3:加上了ORM之后


事务也是必不可少的。Repository主要提供的是CRUD这几个最基本的原子操作,而在实际项目中,为实现一个功能,往往就需要使用多个原子操作。比如“删除文章”这个功能,就至少需要“删除文章"、“删除相关评论”这两个操作。试想我们在成功的完成了第一个操作,而第二个操作却失败了,会得到什么结果? 这也就是为什么我们需要提供事务的功能。事务为我们提供了逻辑上的原子性,只要其中有一个操作失败,整个事务就会回滚到最初的状况,这样我们就可以确保不会向数据库提交脏数据了。但是,“事务”本身的实现逻辑高度依赖于具体数据库,这样就违反了上面说的数据库独立性。因此,我尝试使用了UnitofWork(工作单元)模式,如下图所示:


图 4:加上UnitofWork之后


通过上面的叙述,希望大家已经基本清楚了在Charley Blog项目中数据层各项目之间的联系。接下来,我通过代码展示下实际使用过程中的应用:


ORM:目前在.NET家族中存在的ORM框架有EntityFramework、NHibernat、LinqToSql等等,在本项目中使用的是EntityFramework 5.0。代码位于Model模块中:

public class BaseModel
{
    [Key]
    public Guid ID { get; set; }
}
public class Comment : BaseModel
{
    [ForeignKey("Author")]
    public Guid AuthorID { get; set; }
    [ForeignKey("AuthorID")]
    [Required]
    public virtual Author Author { get; set; }
    [Required]
    public string Content { get; set; }
    //省略部分代码 
}

public class BlogContext : DbContext
{
    public DbSet<Post> Posts { get; set; }
    public DbSet<Comment> Comments { get; set; }
    public DbSet<Author> Authors { get; set; }
    //...省略部分代码
    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        Database.SetInitializer<BlogContext>(new BlogInitializer());
    }
}


Repository:在实际代码中,使用泛型接口来为每个实体提供了一个属于该实体的Repository。又因为ORM框架有许多,所以通过依赖抽象而非具体的EntityFramework来进行解耦。IGenericRepository便是这个抽象。


public interface IGenericRepository<T> : IDisposable where T : class{
    IQueryable<T> GetAll();
    IQueryable<T> GetAll(Expression<Func<T, bool>> predicate);
    T GetSingle(Expression<Func<T, bool>> predicate);
    T GetSingleByKey(params object[] keys);
    void Add(T entity); void Delete(params object[] keys);
    void Delete(T entity);
    void DeleteAll();
    int DeleteBy(Expression<Func<T, bool>> predicate);
    void Update(T entity);
}

public abstract class GenericRepositoryForEF<C, T> : IGenericRepository<T>
    where T : BaseModel
    where C : DbContext, new()
{
    protected C _context; protected DbSet<T> _dbSet;
    public GenericRepositoryForEF(C context)
    {
        if (context == null)
        { 
             throw new ArgumentNullException("context");
        } 
        
        _context = context; _dbSet = _context.Set<T>();
    }
    public virtual IQueryable<T> GetAll() { return _dbSet.AsNoTracking(); }
    public virtual void Add(T entity) { _dbSet.Add(entity); }
    public virtual void Delete(params object[] keys) { T entity = _dbSet.Find(keys); Delete(entity); }
    public virtual void Save() { _context.SaveChanges(); }
    //...省略部分代码
}


UnitofWork:UnitofWork通过持有对DBContext的引用,来统一控制数据的提交和回滚。


public interface IUnitofWork : IDisposable
{
    bool CommitChangesWhenDisposing { get; }
    void RollbackChanges();
    IGenericRepository<T> GetRepository<T>() where T : BaseModel;
    void Commit();
} 

public class UnitofWorkForEF : IUnitofWork{    

    #region Fields
    private BlogContext _context;
    private Dictionary<Type, object> _repositories;   
    #endregion
    
    #region Properties
    public bool CommitChangesWhenDisposing { get; set; }    
    #endregion
    
    public UnitofWorkForEF() { _context = new BlogContext(); _repositories = new Dictionary<Type, object>(); } 
     
    #region Methods
    public virtual IGenericRepository<T> GetRepository<T>() where T : BaseModel
    {
        Type type = typeof(T);
        if (!_repositories.ContainsKey(type))
        {
            if (type == typeof(Post))
            {
                IGenericRepository<Post> _repo = new PostRepository<BlogContext>(_context);
                _repositories[type] = _repo;
            }
            else            {
                IGenericRepository<T> _repo = new BaseRepository<BlogContext, T>(_context);
                _repositories[type] = _repo;
            }
        }
        return _repositories[type] as IGenericRepository<T>;
    }
 
    public virtual void Commit()
    {
        _context.SaveChanges();//提交到数据库
    }
 
    public virtual void RollbackChanges()
    {
        _context.ChangeTracker.Entries().ToList().ForEach(entity => entity.State = System.Data.EntityState.Unchanged); //回滚
    } 
    #endregion
}


最后,再给出一段业务层的示例来看一下,是如何使用UnitofWork和Repository的:


public InformationCode DeleteArticle(Guid id)
{
    try    
    {
        Post article = _repoPost.GetSingle(p => p.ID == id);
        _repoCmt.DeleteBy(cmt => cmt.PostID == id); //删除评论           
        _repoPost.DeleteBy(post => post.ID == id); //删除文章              
        UoW.Commit(); //提交到数据库             
        return InformationCode.MSG_ARTICLE_DELETE_SUCCESS;
    }
    catch    
    {
        UoW.RollbackChanges(); //如果提交失败,则回滚            
        throw;
    }
}


上面的代码中,大家会发现多个Class合并在一个文件中,这个是为了演示所需。在实际开发中,建议大家每个类写在单独的文件中,以便于维护。


如果大家在阅读过程中发现任何问题,欢迎留言,本人一定会在第一时间给予答复。


文章索引

[隐 藏]

本站采用知识共享署名 3.0 中国大陆许可协议进行许可。 ©2014 Charley Box | 关于本站 | 浙ICP备13014059号