EntityFramework(Core)中使用Repository | Unit Of Work模式

本文github地址,内有测试惊喜彩蛋

数据访问层设计的好坏直接影响到写代码的心情:)--- 其实直接影响的是测试。

个人以为,开始一个新工程时首先应该完成数据访问层(Data Access Layer,DAL),这一层应该完全独立。原则:除了DAL,任何层都不应该操作数据库。

我们首先实现Repository (仓储)模式:
DAL层结构:
DataAccessLayer
|——Contacts(接口和抽象类)
|——具体实现类

1. IRepository :仓储类接口

    public interface IRepository<TEntity> where TEntity : class
    {
        TEntity Add(TEntity t);
        Task<TEntity> AddAsyn(TEntity t); 
        int Count();
        Task<int> CountAsync();
        void Delete(TEntity entity);
        Task<int> DeleteAsyn(TEntity entity);
        void Dispose();
        TEntity Find(Expression<Func<TEntity, bool>> match);
        IEnumerable<TEntity> FindAll(Expression<Func<TEntity, bool>> match);
        Task<IEnumerable<TEntity>> FindAllAsync(Expression<Func<TEntity, bool>> match);
        Task<TEntity> FindAsync(Expression<Func<TEntity, bool>> match);
        IEnumerable<TEntity> FindBy(Expression<Func<TEntity, bool>> predicate);
        Task<IEnumerable<TEntity>> FindByAsyn(Expression<Func<TEntity, bool>> predicate);
        TEntity Get(int id);
        IEnumerable<TEntity> GetAll();
        Task<IEnumerable<TEntity>> GetAllAsyn(); 
        Task<TEntity> GetAsync(int id);
        void Save();
        Task<int> SaveAsync();
        TEntity Update(TEntity t, object key);
        Task<TEntity> UpdateAsyn(TEntity t, object key);
    }

这是所有Repository类的父类,其中的方法根据需要增减;注意其中集合类的返回类型都是IEnumerable,而不是ICollection,因为我们不想让外部类操作数据库。IEnumerable不能Query。

2. Repository:所有仓储类的基类

 public class Repository<T> : IRepository<T> where T : class
    {
        protected DbContext _context;

        public Repository(DbContext dbContext)
        {
            _context = dbContext;
        }

        public IEnumerable<T> GetAll()
        {
            return _context.Set<T>();
        }

        public virtual async Task<IEnumerable<T>> GetAllAsyn()
        {

            return await _context.Set<T>().ToListAsync();
        }

        public virtual T Get(int id)
        {
            return _context.Set<T>().Find(id);
        }

        public virtual async Task<T> GetAsync(int id)
        {
            return await _context.Set<T>().FindAsync(id);
        }

        public virtual T Add(T t)
        {

            _context.Set<T>().Add(t);
            _context.SaveChanges();
            return t;
        }

        public virtual async Task<T> AddAsyn(T t)
        {
            _context.Set<T>().Add(t);
            await _context.SaveChangesAsync();
            return t;

        }

        public virtual T Find(Expression<Func<T, bool>> match)
        {
            return _context.Set<T>().SingleOrDefault(match);
        }

        public virtual async Task<T> FindAsync(Expression<Func<T, bool>> match)
        {
            return await _context.Set<T>().SingleOrDefaultAsync(match);
        }

        public IEnumerable<T> FindAll(Expression<Func<T, bool>> match)
        {
            return _context.Set<T>().Where(match).ToList();
        }

        public async Task<IEnumerable<T>> FindAllAsync(Expression<Func<T, bool>> match)
        {
            return await _context.Set<T>().Where(match).ToListAsync();
        }

        public virtual void Delete(T entity)
        {
            _context.Set<T>().Remove(entity);
            _context.SaveChanges();
            
        }

        public virtual async Task<int> DeleteAsyn(T entity)
        {
            _context.Set<T>().Remove(entity);
            return await _context.SaveChangesAsync();
        }

        public virtual T Update(T t, object key)
        {
            if (t == null)
                return null;
            T exist = _context.Set<T>().Find(key);
            if (exist != null)
            {
                _context.Entry(exist).CurrentValues.SetValues(t);
                _context.SaveChanges();
            }
            return exist;
        }

        public virtual async Task<T> UpdateAsyn(T t, object key)
        {
            if (t == null)
                return null;
            T exist = await _context.Set<T>().FindAsync(key);
            if (exist != null)
            {
                _context.Entry(exist).CurrentValues.SetValues(t);
                await _context.SaveChangesAsync();
            }
            return exist;
        }

        public int Count()
        {
            return _context.Set<T>().Count();
        }

        public async Task<int> CountAsync()
        {
            return await _context.Set<T>().CountAsync();
        }

        public virtual void Save()
        {

            _context.SaveChanges();
        }

        public async virtual Task<int> SaveAsync()
        {
            return await _context.SaveChangesAsync();
        }

        public virtual IEnumerable<T> FindBy(Expression<Func<T, bool>> predicate)
        {
            IEnumerable<T> query = _context.Set<T>().Where(predicate);
            return query;
        }

        public virtual async Task<IEnumerable<T>> FindByAsyn(Expression<Func<T, bool>> predicate)
        {
            return await _context.Set<T>().Where(predicate).ToListAsync();
        }
        private bool disposed = false;
        protected virtual void Dispose(bool disposing)
        {
            if (!this.disposed)
            {
                if (disposing)
                {
                    _context.Dispose();
                }
                this.disposed = true;
            }
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
    }

此类的主要作用是对接口方法进行集中实现,工程中具体仓储类继承此类后就不用再实现这些方法了。

3. 具体仓储类

假设我们的领域类中有个Blog:

    public class Blog
    {
        [Key]
        public int BlogId { get; set; }
        public string Title { get; set; } 
        public string CreatedBy { get; set; } 
        public DateTime? CreatedOn { get; set; }
        public string UpdatedBy { get; set; }
        public DateTime? UpdatedOn { get; set; } 
        public virtual List<Post> Posts { get; set; }
    }

我们应该对其实现一个仓储类,在此之前,应该设计此具体仓储类的接口(面向接口编程)

    public interface IBlogRepository : IRepository<Blog>
    {     
        Blog GetByTitle(string  blogTitle);
    }

添加了一个根据标题获取数据的方法

 public class BlogRepository : Repository<Blog>, IBlogRepository
    {
        public BlogRepository(DbContext dbContext) : base(dbContext) { }
        public Blog GetByTitle(string blogTitle)
        {
            return GetAll().FirstOrDefault(x => x.Title == blogTitle);
        }
        /*
        add override methods
         */
        public async Task<Blog> GetSingleAsyn(int blogId)
        {
            return await _context.Set<Blog>().FindAsync(blogId);
        }

        public override Blog Update(Blog t, object key)
        {
            Blog exist = _context.Set<Blog>().Find(key);
            if (exist != null)
            {
                t.CreatedBy = exist.CreatedBy;
                t.CreatedOn = exist.CreatedOn;
            }
            return base.Update(t, key);
        }

        public async override Task<Blog> UpdateAsyn(Blog t, object key)
        {
            Blog exist = await _context.Set<Blog>().FindAsync(key);
            if (exist != null)
            {
                t.CreatedBy = exist.CreatedBy;
                t.CreatedOn = exist.CreatedOn;
            }
            return await base.UpdateAsyn(t, key);
        }
    }

可以看到,它继承了仓储基类和接口,这样基类中的方法就自动实现了;然后实现接口中添加的方法就行。同时,由于基类中的方法都是虚方法,这里也可以对其进行重写。

4. RepositoryWrapper(可选)

随着领域类越来越多,我们就要写越来越多的仓储类,每个都注入或者new很麻烦,这时,我们可以将工程中的仓储类包装一下:
首先是接口类:

    public interface IRepositoryWrapper
    {
        IBlogRepository Blog { get; } 
    }

实现类:

  public class RepositoryWrapper : IRepositoryWrapper
    {
        private DataContext _dataContext;
        private IBlogRepository _blogRepository;

        public RepositoryWrapper(DataContext dataContext)
        {
            _dataContext = dataContext;
        }
        public IBlogRepository Blog
        {
            get
            {
                if (_blogRepository is null)
                {
                    _blogRepository = new BlogRepository(_dataContext);
                }
                return _blogRepository;
            }
        }
    }

这就是Repository模式的全部内容。

5. 在Asp.net Core项目中消费仓储类

首先在Startup中注册IOC:

  public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
           services.AddSingleton<IRepositoryWrapper, RepositoryWrapper>();
        }

我们在Controller中消费仓储类:

[Route("api/[controller]")]
[ApiController]
  public class BlogController : Controller
    {
        private readonly IRepositoryWrapper _repositoryWrapper;

        public BlogController(IRepositoryWrapper repositoryWrapper)
        {
            _repositoryWrapper = repositoryWrapper;

        }
        [Route("~/api/GetBlogs")]
        [HttpGet]
        public async Task<IEnumerable<Blog>> Index()
        {
            return await _repositoryWrapper.Blog.GetAllAsyn();
        }

        [Route("~/api/AddBlog")]
        [HttpPost]
        public async Task<Blog> AddBlog([FromBody]Blog blog)
        {
            await _repositoryWrapper.Blog.AddAsyn(blog);
            await _repositoryWrapper.Blog.SaveAsync();
            return blog;
        }

        [Route("~/api/UpdateBlog")]
        [HttpPut]
        public async Task<Blog> UpdateBlog([FromBody]Blog blog)
        {
            var updated = await _repositoryWrapper.Blog.UpdateAsyn(blog, blog.BlogId);
            return updated;
        }

        [Route("~/api/DeleteBlog/{id}")]
        [HttpDelete]
        public string Delete(int id)
        {
            _repositoryWrapper.Blog.Delete(_repositoryWrapper.Blog.Get(id));
            return "Employee deleted successfully!";
        }

        protected override void Dispose(bool disposing)
        {
            _repositoryWrapper.Blog.Dispose();
            base.Dispose(disposing);
        }

    }

6. Unit Of Work(UOW)

Unit Of Work

可以看到,UOW就是在Repository外部包了薄薄的一层,进一步进行了代码隔离。所以Repository是UOW的前提。个人感觉,UOW可做可不做,上面介绍的Repository Pattern已经能满足大部分开发要求。并且很方便测试。

首先是IUnitOfWork接口:

    public interface IUnitOfWork:IDisposable
    {
        IRepository<User> Users { get; }
        Task Commit();
    }

UOW类:

 public class UnitOfWork : IUnitOfWork
    {
        private bool disposed = false;

        private IRepository<User> _userRepo; 

        private readonly RepositoryContext _repositoryContext;
        public UnitOfWork(RepositoryContext repositoryContext)
        {
            _repositoryContext = repositoryContext;
        }

        public IRepository<User> Users
        {
            get
            {
                if (_userRepo is null)
                {
                    _userRepo = new UserReposiory(_repositoryContext);
                }
                return _userRepo;
            }
        }

        public async Task Commit()
        {
           await _repositoryContext.SaveChangesAsync();
        }
        protected virtual void Dispose(bool disposing)
        {
            if (!this.disposed && disposing)
            {
                _repositoryContext.Dispose();
            }
            this.disposed = true;
        }
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
    }

StartUp(IOC)

  services.AddTransient<IUnitOfWork, UnitOfWork>();

Controller

    [Route("api/user")]
    [ApiController]
    public class UserController : ControllerBase
    {
        private readonly DAL.UnitOfWork _unitOfWork;
        public UserController(DAL.UnitOfWork unitOfWork)
        {
            _unitOfWork = unitOfWork;
        }

        public async Task<IActionResult> GetAll()
        {
            return Ok(await _unitOfWork.Users.GetAll());
        }
    }

贴出工程DAL层结构:


image.png

本文github地址,内有测试惊喜彩蛋

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,591评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,448评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,823评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,204评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,228评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,190评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,078评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,923评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,334评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,550评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,727评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,428评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,022评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,672评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,826评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,734评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,619评论 2 354

推荐阅读更多精彩内容

  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,097评论 1 32
  • 本文为转载,原文:asp.net core 实战项目(一)——ef core的使用 数据库设计 数据结构图如下: ...
    ChainZhang阅读 9,856评论 0 3
  • 1.从传统三层架构与DDD分层架构的编程演变其实是思想的演变。 传统三层架构,即用户界面层UI、业务逻辑层BAL、...
    咖啡电视阅读 8,208评论 0 6
  • “你好,我是秦布茴,你还记得我吗?”满眼期待地询问却最终换来了他的一句“Sorry,小姐,我好像并不认识你。” 我...
    夙梓茼阅读 201评论 0 0
  • Redis被配置为保存数据库快照,但它目前不能持久化到硬盘。用来修改集合数据的命令不能用。请查看Redis日志的详...
    Shokka阅读 162评论 0 1