https://docs.microsoft.com/en-us/aspnet/mvc/overview/older-versions/getting-started-with-ef-5-using-mvc-4/implementing-the-repository-and-unit-of-work-patterns-in-an-asp-net-mvc-application
理论基础
仓储和工作单元模式是用来在数据访问层和业务逻辑层之间创建一个抽象层。
应用这些模式,可以帮助用来隔离你的程序在数据存储变化。
下图比较了不使用库模式和使用库模式时controller和context 交互方式的差异。
说明:库模式的实现有多种做法,下图是其中一种。
模型Model
public class User { public int ID { get; set; } [DisplayName("部门名称")] public int? DepartmentID { get; set; } public virtual Department Department { get; set; } [DataType(DataType.Date),DisplayFormat(DataFormatString ="{0:yyyy-MM-dd}",ApplyFormatInEditMode =true)] public DateTime CreateDate { get; set; } [DisplayName("用户名称"),Required(ErrorMessage = "* Required")] [StringLength(50, MinimumLength = 3, ErrorMessage = "* Part numbers must be between 3 and 50 character in length.")] public string Name { get; set; } public string CName { get; set; } public string Description { get; set; } public DateTime ModifiedDate { get; set; } [DisplayName("邮箱地址")] public string Email { get; set; } [Required , MaxLength(50), DisplayName("密码"), DataType(DataType.Password)] public string Password { get; set; } [DisplayName("用户规则")] public virtual ICollection<UserRole> UserRoles { get; set; } // 一个用户有多条Role }
public class Role { public int ID { get; set; } [MaxLength(20, ErrorMessage = "长度不能超过20个字符")] public string Name { set; get; } [MaxLength(20, ErrorMessage = "长度不能超过20个字符")] public string CName { set; get; } [MaxLength(200, ErrorMessage = "长度不能超过200个字符")] public string Desc { get; set; } public DateTime ModifiedDate { get; set; } public virtual ICollection<UserRole> UserRoles { get; set; } }
public class UserRole { //public int ID { get; set; } [Key, Column(Order = 0), ForeignKey("User")] public int UserID { get; set; } [Key, Column(Order = 1), ForeignKey("Role")] public int RoleID { get; set; } public virtual User User { get; set; } public virtual Role Role { get; set; } }
建立DAL文件夹
建立类:
using MVCDemo.Models; using MVCDemo.ViewModels; using System; using System.Collections.Generic; using System.Data.Entity; using System.Linq; using System.Web; namespace MVCDemo.DAL { public class DemoContext:DbContext { public DemoContext():base("name = DbConStr") { //诊断当前数据库的连接 System.Diagnostics.Debug.Write(Database.Connection.ConnectionString); } public DbSet<Department> Departments { get; set; } public DbSet<Test> Tests { get; set;} public DbSet<User> Users { get; set; } public DbSet<Role> Roles { get; set; } public DbSet<UserRole> UserRoles { get; set; } protected override void OnModelCreating(DbModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); } } }
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Data.Entity; using MVCDemo.Models; namespace MVCDemo.DAL { // DropCreateDatabaseAlways DropCreateDatabaseIfModelChanges CreateDatabaseIfNotExists public class AccountInitializer : DropCreateDatabaseAlways<DemoContext> { protected override void Seed(DemoContext context) { var Users = new List<User> { new User {ID=1, Name="zs",CName="张三",Email="tom@139.com",Password="123456",ModifiedDate=DateTime.Now }, new User {ID=2, Name="ls",CName="李四",Email="tom@139.com",Password="123456",ModifiedDate=DateTime.Now }, new User {ID=3, Name="ww",CName="王五",Email="tom@139.com",Password="123456",ModifiedDate=DateTime.Now } }; Users.ForEach(s => context.Users.Add(s)); context.SaveChanges(); var Roles = new List<Role> { new Role {ID=1, Name="Administrators",Description="Administrators具有系统管理的所有权限" }, new Role {ID=2, Name="General Users",Description="General Users 可以访问已经授权的数据" } }; Roles.ForEach(s => context.Roles.Add(s)); context.SaveChanges(); var UserRoles = new List<UserRole> { new UserRole {UserID=1,RoleID=1, ModifiedDate=DateTime.Now }, new UserRole {UserID=1,RoleID=2, ModifiedDate=DateTime.Now }, new UserRole {UserID=2,RoleID=1, ModifiedDate=DateTime.Now }, new UserRole {UserID=3,RoleID=2, ModifiedDate=DateTime.Now } }; UserRoles.ForEach(s => context.UserRoles.Add(s)); context.SaveChanges(); base.Seed(context); } } }
解耦Controller和数据层
建文件夹 Repositories, 放在仓储类
以User为例:
1.创建接口 IUserRepository
接口中声明了一组典型的CRUD方法。
其中查找方法有两个:返回全部和根据ID返回单个。
public interface IUserRepository:IDisposable { //查询所有用户 IEnumerable<User> GetAllUsers(); //返回全部 User GetUserById(int userId); void InsertUser(User user); void UpdateUser(User user); void DeleteUser(int userId); void Save(); }
3.创建对应的仓储类 UserRepository
实现接口 IUserRepository
实现接口中的方法
public class UserRepository : IUserRepository { private DemoContext context; public UserRepository(DemoContext context) { this.context = context; } public IEnumerable<User> GetAllUsers() { return context.Users.ToList(); } public User GetUserById(int userId) { return context.Users.Find(userId); } public void InsertUser(User user) { context.Users.Add(user); } public void DeleteUser(int userId) { User user = context.Users.Find(userId); context.Users.Remove(user); } public void UpdateUser(User user) { context.Entry(user).State = EntityState.Modified; } public void Save() { context.SaveChanges(); } 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); } }
说明:
GC.SuppressFinalize(this);
因为对象会被Dispose释放,所以需要调用GC.SuppressFinalize来让对象脱离终止队列,防止对象终止被执行两次。
4.Controller中使用User仓储类
新建个Controller : UserController,添加下面的Index方法
public ActionResult Index() { return View(); }
右点该方法生产视图,用 List 模板生成视图。
修改控制器Index方法:
public class UserController : Controller { // 建立User仓储对象 , 这里方法中不涉及数据访问层 private IUserRepository userRepository = new UserRepository(new DAL.DemoContext()); // GET: User public ActionResult Index() { var users = userRepository.GetAllUsers(); return View(users); } }
运行Index视图
上面增加了一个抽象层,将数据连接的部分移到Repository中去,这样实现了Controller和数据层的解耦。
进一步抽象,定义一个泛型接口:
1. 创建泛型接口 IGenericRepository
下图中右边为IGenericRepository, 大家观察下两者的区别
2.创建对应的泛型仓储类 GenericRepository
using MVCDemo.DAL; using System; using System.Collections.Generic; using System.Data.Entity; using System.Linq; using System.Web; namespace MVCDemo.Repositories { public class GenericRepository<TEntity>: IGenericRepository<TEntity> where TEntity : class { internal DemoContext context; internal DbSet<TEntity> dbSet; public GenericRepository(DemoContext context) { this.context = context; this.dbSet = context.Set<TEntity>(); // 返回实体集 } public IEnumerable<TEntity> Get() { return dbSet.ToList(); } public TEntity GetById(object id) { return dbSet.Find(id); // 查找带主键值的实体 } public void Insert(TEntity entity) { dbSet.Add(entity); // 添加到集的上下文,SaveChanges保存到数据库 } public void Delete(object id) { TEntity entity = dbSet.Find(id); Delete(entity); } public virtual void Delete(TEntity entity) { if(context.Entry(entity).State == EntityState.Detached) { dbSet.Attach(entity); //附加到集的基础上下文,好像从数据库读取一样 } dbSet.Remove(entity); } public virtual void Update(TEntity entity) { dbSet.Attach(entity); context.Entry(entity).State = EntityState.Modified; } public void Save() { context.SaveChanges(); } private bool disposed = false; /// <summary> /// 为了防止忘记显式的调用Dispose方法 /// </summary> ~GenericRepository() { //必须为false Dispose(false); } /// <summary> /// 非必需的,只是为了更符合其他语言的规范,如C++、java /// </summary> public void Close() { Dispose(); } public void Dispose() { //必须为true Dispose(true); //通知垃圾回收器不再调用终结器 GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (disposed) { return; } //清理托管资源 if (disposing) { context.Dispose(); } //清理非托管资源 //告诉自己已经被释放 disposed = true; } } }
3、修改UserController的函数
using MVCDemo.Models; using MVCDemo.Repositories; using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; namespace MVCDemo.Controllers { public class UserController : Controller { //// 建立User仓储对象 , 这里方法中不涉及数据访问层 //private IUserRepository userRepository = new UserRepository(new DAL.DemoContext()); //// GET: User //public ActionResult Index() //{ // var users = userRepository.GetAllUsers(); // return View(users); //} private IGenericRepository<User> userRepository = new GenericRepository<User>(new DAL.DemoContext()); public ActionResult Index() { var users = userRepository.Get(); return View(users); } } }
5、接下来解决第二个问题:context的一致性
我们在DAL文件夹中新建一个类UnitOfWork用来负责context的一致性:
当使用多个repositories时,共享同一个context
我们把使用多个repositories的一系列操作称为一个 unit of work
当一个unit of work完成时,我们调用context的SaveChanges方法来完成实际的更改。由于是同一个context, 所有相关的操作将会被协调好。
这个类只需要一个Save方法和一组repository属性。
每个repository属性返回一个repository实例,所有这些实例都会共享同样的context.
using MVCDemo.Models; using MVCDemo.Repositories; using System; using System.Collections.Generic; using System.Linq; using System.Web; namespace MVCDemo.DAL { public class UnitOfWork:IDisposable { private DemoContext dbContext = new DemoContext(); // 注释掉的方法是正常的 //private GenericRepository<User> mUserRepository; //private GenericRepository<Role> mRoleRepository; //private GenericRepository<UserRole> mUserRoleRepository; //public GenericRepository<User> UserRepository //{ // get // { // if (mUserRepository == null) // mUserRepository = new GenericRepository<User>(dbContext); // return mUserRepository; // } //} public GenericRepository<User> UserRepository { get { return GenericRepository<User>(); } } public GenericRepository<Role> RoleRepository { get { return GenericRepository<Role>(); } } public GenericRepository<UserRole> UserRoleRepository { get { return GenericRepository<UserRole>(); } } private Dictionary<Type, object> repositories = new Dictionary<Type, object>(); public GenericRepository<T> GenericRepository<T>() where T : class { if (repositories.Keys.Contains(typeof(T)) == true) //仓储类T已经建立 { return repositories[typeof(T)] as GenericRepository<T>; } GenericRepository<T> repo = new GenericRepository<T>(dbContext); repositories.Add(typeof(T), repo); return repo; } #region Save & Dispose public void SaveChanges() { dbContext.SaveChanges(); } private bool disposed = false; protected virtual void Dispose(bool disposing) { if (!this.disposed) { if (disposing) { dbContext.Dispose(); } } this.disposed = true; } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } #endregion } }
把 GenericRepository.cs 中的Save 和 Dispose 删除, 移到UnitOfWork中。
将IGenericRepository 中的IDisposable接口继承也去掉.
Save & Dispose 的工作统一在UnitOfWork中完成。
在UserController中使用UnitOfWork, 修改如下:
public ActionResult Index() { //return View(unitOfWork.UserRepository().GetAll().ToList()); User user = unitOfWork.UserRepository.Get(new Func<User, bool>(u => u.Name == "zs")); List<User> list = new List<Models.User> { user }; return View(list); }
6、添加查询、排序、预先载入条件
public IEnumerable<T> Get(Expression<Func<T, bool>> filter = null, Func<IQueryable<T>, IOrderedQueryable<T>> orderBy = null, string includeProperties = "") { //creates an IQueryable object IQueryable<T> query = dbSet; if (filter != null) { query = query.Where(filter); } //eager-loading expressions after parsing the comma-delimited list foreach (var includeProperty in includeProperties.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries)) { query = query.Include(includeProperty); } if (orderBy != null) { return orderBy(query).ToList(); } else { return query.ToList(); } }
The code Expression<Func<TEntity, bool>> filter means the caller will provide a lambda expression based on the TEntity type, and this expression will return a Boolean value.For example, if the repository is instantiated for the Student entity type, the code in the calling method might specify student => student.LastName == "Smith" for the filter parameter.
The code Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy also means the caller will provide a lambda expression.But in this case, the input to the expression is an IQueryable object for the TEntity type.The expression will return an ordered version of that IQueryable object. For example, if the repository is instantiated for the Student entity type, the code in the calling method might specify q => q.OrderBy(s => s.LastName) for the orderBy parameter
public IEnumerable<T> GetAll(Expression<Func<T, bool>> predicate = null) { if (predicate != null) { return dbSet.Where(predicate); } return dbSet.AsEnumerable(); }
实际predicate 就是查询条件,有无代码使用的是Where