.NET MVC4 实训记录之四(Unit of work + Repository)
今日后开启进阶模式!
谈到MVC与EntityFramework,则不得不说一说事务与仓储(Unit of work + Repository)。
仓储(Repository):领域对象集合。用于操作领域对象与数据库上下文(DbContext)的交互(在此不得不说一声,领域对象和数据库表对象还是有区别的。领域对象实际上是一组有业务关系的数据库对象的抽象。最简单的形式就是主表、关系表在同一个领域对象中进行定义。例如我们前几章看到的UserProfile,它即定义了用户信息,又定义了用户角色关系信息)。
事务(Transaction):多个业务处理有必然的顺序性、依赖性,则默认这些业务为一个原子操作。在这里我们使用工作单元,即Unit of work模式,来维护事务的原子性。
首先,先让我们构建仓储接口。为了更好的使用EntityFramework的延迟加载,所有查询集合的接口我们均使用IQueryable接口类型作为返回值。
1 1 /// <summary> 2 2 /// 基础仓储类型接口 3 3 /// 采用泛型类接口定义 4 4 /// </summary> 5 5 /// <typeparam name="T">泛型参数</typeparam> 6 6 public interface IRepository<T> : IDisposable where T : class 7 7 { 8 8 /// <summary> 9 9 /// 返回当前表的所有记录 10 10 /// </summary> 11 11 /// <returns>T</returns> 12 12 IQueryable<T> Entries(); 13 13 14 14 /// <summary> 15 15 /// 通过过滤条件进行查询 16 16 /// </summary> 17 17 /// <param name="predicate">过滤条件表达式</param> 18 18 /// <returns>T</returns> 19 19 IQueryable<T> Filter(Expression<Func<T, bool>> predicate); 20 20 21 21 /// <summary> 22 22 /// 通过过滤条件进行查询 23 23 /// </summary> 24 24 /// <param name="predicate">过滤条件表达式</param> 25 25 /// <param name="includes">需贪婪加载的属性名称</param> 26 26 /// <returns>IQueryable</returns> 27 27 IQueryable<T> Filter(Expression<Func<T, bool>> predicate, params string[] includes); 28 28 29 29 /// <summary> 30 30 /// 是否存在满足表达式的记录 31 31 /// </summary> 32 32 /// <param name="predicate">过滤条件表达式</param> 33 33 /// <returns>Boolean</returns> 34 34 bool Contains(Expression<Func<T, bool>> predicate); 35 35 36 36 /// <summary> 37 37 /// 按照数据库主键查询特定的实例 38 38 /// </summary> 39 39 /// <param name="keys">主键列表</param> 40 40 /// <returns>T</returns> 41 41 T Single(params object[] keys); 42 42 43 43 /// <summary> 44 44 /// 按照指定表达式查询特定的实例 45 45 /// </summary> 46 46 /// <param name="predicate">过滤条件表达式</param> 47 47 /// <returns>T</returns> 48 48 T FirstOrDefault(Expression<Func<T, bool>> predicate); 49 49 50 50 /// <summary> 51 51 /// 插入一条记录 52 52 /// </summary> 53 53 /// <param name="t">新实例</param> 54 54 /// <param name="submitImmediately">是否直接提交。默认false。</param> 55 55 /// <returns>T</returns> 56 56 T Create(T t, bool submitImmediately = false); 57 57 58 58 /// <summary> 59 59 /// 删除一行记录 60 60 /// </summary> 61 61 /// <param name="t">要删除的实例</param> 62 62 /// <param name="submitImmediately">是否直接提交。默认false。</param> 63 63 void Delete(T t, bool submitImmediately = false); 64 64 65 65 /// <summary> 66 66 /// 删除满足表达式的记录 67 67 /// </summary> 68 68 /// <param name="predicate">过滤条件表达式</param> 69 69 /// <param name="submitImmediately">是否直接提交。默认false。</param> 70 70 void Delete(Expression<Func<T, bool>> predicate, bool submitImmediately = false); 71 71 72 72 /// <summary> 73 73 /// 更新一条记录 74 74 /// </summary> 75 75 /// <param name="t">要更新的实例</param> 76 76 /// <param name="submitImmediately">是否直接提交。默认false。</param> 77 77 void Update(T t, bool submitImmediately = false); 78 78 79 79 /// <summary> 80 80 /// 获取当前实例的主键 81 81 /// </summary> 82 82 /// <param name="t">实例</param> 83 83 /// <returns>Object</returns> 84 84 object GetKeyValue(T t); 85 85 }
接下来是实现这个接口,真正去处理数据查询和操作的时候了。
1 1 /// <summary> 2 2 /// 仓储类型定义 3 3 /// 采用泛型类定义 4 4 /// </summary> 5 5 /// <typeparam name="T">泛型参数</typeparam> 6 6 public class Repository<T> : IRepository<T> where T : class 7 7 { 8 8 /// <summary> 9 9 /// 数据库上下文 10 10 /// </summary> 11 11 public UsersContext Context { get; private set; } 12 12 13 13 /// <summary> 14 14 /// 当前表记录集合 15 15 /// </summary> 16 16 protected DbSet<T> DbSet 17 17 { 18 18 get 19 19 { 20 20 return Context.Set<T>(); 21 21 } 22 22 } 23 23 24 24 /// <summary> 25 25 /// 构造器 26 26 /// </summary> 27 27 public Repository() 28 28 { 29 29 Context = new UsersContext(); 30 30 } 31 31 32 32 /// <summary> 33 33 /// 构造器 34 34 /// </summary> 35 35 /// <param name="connectionString">连接字符串(名称)</param> 36 36 public Repository(string connectionString) 37 37 { 38 38 Context = new UsersContext(connectionString); 39 39 } 40 40 41 41 /// <summary> 42 42 /// 构造器 43 43 /// </summary> 44 44 /// <param name="context">数据库上下文</param> 45 45 public Repository(UsersContext context) 46 46 { 47 47 Context = context; 48 48 } 49 49 50 50 /// <summary> 51 51 /// 析构器 52 52 /// </summary> 53 53 public void Dispose() 54 54 { 55 55 if (Context != null) 56 56 Context.Dispose(); 57 57 } 58 58 59 59 /// <summary> 60 60 /// 返回当前表的所有记录 61 61 /// </summary> 62 62 /// <returns></returns> 63 63 public IQueryable<T> Entries() 64 64 { 65 65 return DbSet.AsQueryable(); 66 66 } 67 67 68 68 /// <summary> 69 69 /// 通过过滤条件进行查询 70 70 /// </summary> 71 71 /// <param name="predicate">过滤条件表达式</param> 72 72 /// <returns>T</returns> 73 73 public IQueryable<T> Filter(Expression<Func<T, bool>> predicate) 74 74 { 75 75 return DbSet.Where(predicate); 76 76 } 77 77 78 78 /// <summary> 79 79 /// 通过过滤条件进行查询 80 80 /// </summary> 81 81 /// <param name="predicate">过滤条件表达式</param> 82 82 /// <param name="includes">需贪婪加载的属性名称</param> 83 83 /// <returns>IQueryable</returns> 84 84 public IQueryable<T> Filter(Expression<Func<T, bool>> predicate, params string[] includes) 85 85 { 86 86 var query = DbSet.Where(predicate); 87 87 if (includes != null) 88 88 { 89 89 foreach (var item in includes) 90 90 { 91 91 query = query.Include(item); 92 92 } 93 93 } 94 94 return query; 95 95 } 96 96 97 97 /// <summary> 98 98 /// 是否存在满足表达式的记录 99 99 /// </summary> 100 100 /// <param name="predicate">过滤条件表达式</param> 101 101 /// <returns>Boolean</returns> 102 102 public bool Contains(Expression<Func<T, bool>> predicate) 103 103 { 104 104 return DbSet.Count(predicate) > 0; 105 105 } 106 106 107 107 /// <summary> 108 108 /// 按照数据库主键查询特定的实例 109 109 /// </summary> 110 110 /// <param name="keys">主键列表</param> 111 111 /// <returns>T</returns> 112 112 public T Single(params object[] keys) 113 113 { 114 114 return DbSet.Find(keys); 115 115 } 116 116 117 117 /// <summary> 118 118 /// 按照指定表达式查询特定的实例 119 119 /// </summary> 120 120 /// <param name="predicate">过滤条件表达式</param> 121 121 /// <returns>T</returns> 122 122 public T FirstOrDefault(Expression<Func<T, bool>> predicate) 123 123 { 124 124 return DbSet.FirstOrDefault(predicate); 125 125 } 126 126 127 127 /// <summary> 128 128 /// 插入一条记录 129 129 /// </summary> 130 130 /// <param name="t">新实例</param> 131 131 /// <param name="submitImmediately">是否直接提交。默认false。</param> 132 132 /// <returns>T</returns> 133 133 public T Create(T t, bool submitImmediately = false) 134 134 { 135 135 var newEntry = DbSet.Add(t); 136 136 if (submitImmediately) 137 137 Context.SaveChanges(); 138 138 return newEntry; 139 139 } 140 140 141 141 /// <summary> 142 142 /// 删除一行记录 143 143 /// </summary> 144 144 /// <param name="t">要删除的实例</param> 145 145 /// <param name="submitImmediately">是否直接提交。默认false。</param> 146 146 public void Delete(T t, bool submitImmediately = false) 147 147 { 148 148 DbSet.Remove(t); 149 149 if (submitImmediately) 150 150 Context.SaveChanges(); 151 151 } 152 152 153 153 /// <summary> 154 154 /// 删除满足表达式的记录 155 155 /// </summary> 156 156 /// <param name="predicate">过滤条件表达式</param> 157 157 /// <param name="submitImmediately">是否直接提交。默认false。</param> 158 158 public void Delete(Expression<Func<T, bool>> predicate, bool submitImmediately = false) 159 159 { 160 160 try 161 161 { 162 162 Context.Configuration.AutoDetectChangesEnabled = false; //关闭数据库上下文的自动更新跟踪功能,可提高批量操作的性能 163 163 164 164 var objects = Filter(predicate); 165 165 foreach (var obj in objects) 166 166 DbSet.Remove(obj); 167 167 if (submitImmediately) 168 168 Context.SaveChanges(); 169 169 } 170 170 finally 171 171 { 172 172 Context.Configuration.AutoDetectChangesEnabled = true; //完成批量操作后,打开数据库上下文的自动更新跟踪功能 173 173 } 174 174 } 175 175 176 176 /// <summary> 177 177 /// 更新一条记录 178 178 /// </summary> 179 179 /// <param name="t">要更新的实例</param> 180 180 /// <param name="submitImmediately">是否直接提交。默认false。</param> 181 181 public void Update(T t, bool submitImmediately = false) 182 182 { 183 183 var key = GetKeyValue(t); 184 184 185 185 var originalEntity = DbSet.Find(key); 186 186 187 187 Context.Entry(originalEntity).CurrentValues.SetValues(t); 188 188 189 189 if (submitImmediately) 190 190 Context.SaveChanges(); 191 191 } 192 192 193 193 /// <summary> 194 194 /// 获取当前实例的主键 195 195 /// </summary> 196 196 /// <param name="t">实例</param> 197 197 /// <returns>Object</returns> 198 198 public object GetKeyValue(T t) 199 199 { 200 200 var key = 201 201 typeof(T).GetProperties().FirstOrDefault( 202 202 p => p.GetCustomAttributes(typeof(System.ComponentModel.DataAnnotations.KeyAttribute), true).Length != 0); 203 203 return (key != null) ? key.GetValue(t, null) : null; 204 204 } 205 205 }
仓储定义完成。它包含了基本的增、删、改、查功能。无需过多的修饰,作为仓储单元,它的任务就是这么四个操作而已。
接下来就是我们的Unit of work的定义了。
1 namespace Framework.Repositories 2 { 3 /// <summary> 4 /// 工作单元接口定义 5 /// </summary> 6 public interface IUnitOfWork: IDisposable 7 { 8 /// <summary> 9 /// 获取数据库上下文 10 /// </summary> 11 UsersContext DbContext { get; } 12 13 /// <summary> 14 /// 执行自定义SQL语句。 15 /// 该方法提供了一个直接操作数据库表的实现。 16 /// </summary> 17 /// <param name="commandText">SQL语句</param> 18 /// <param name="parameters">参数列表</param> 19 /// <returns>Integer</returns> 20 int ExecuteSqlCommand(string commandText, params object[] parameters); 21 22 /// <summary> 23 /// 提交事务 24 /// </summary> 25 /// <returns>Integer</returns> 26 int Commit(); 27 28 /// <summary> 29 /// 获取指定类型的仓储实例 30 /// </summary> 31 /// <typeparam name="T">泛型参数</typeparam> 32 /// <returns>IRepository</returns> 33 IRepository<T> Repositry<T>() where T : class; 34 } 35 36 /// <summary> 37 /// 工作单元实现定义 38 /// </summary> 39 public class UnitOfWork : IUnitOfWork 40 { 41 /// <summary> 42 /// 用户数据库上下文 43 /// </summary> 44 private UsersContext dbContext = null; 45 46 /// <summary> 47 /// 获取数据库上下文 48 /// </summary> 49 public UsersContext DbContext { get { return dbContext; } } 50 51 /// <summary> 52 /// 构造器 53 /// </summary> 54 public UnitOfWork() 55 { 56 dbContext = new UsersContext(); 57 } 58 59 /// <summary> 60 /// 构造器 61 /// </summary> 62 /// <param name="context">数据库上下文</param> 63 public UnitOfWork(UsersContext context) 64 { 65 dbContext = context; 66 } 67 68 /// <summary> 69 /// 析构器 70 /// </summary> 71 public void Dispose() 72 { 73 if (dbContext != null) 74 dbContext.Dispose(); 75 GC.SuppressFinalize(this); 76 } 77 78 /// <summary> 79 /// 获取指定类型的仓储实例 80 /// </summary> 81 /// <typeparam name="T">泛型参数</typeparam> 82 /// <returns>IRepository</returns> 83 public IRepository<T> Repositry<T>() where T : class 84 { 85 return new Repository<T>(DbContext); 86 } 87 88 /// <summary> 89 /// 提交事务 90 /// </summary> 91 /// <returns>Integer</returns> 92 public int Commit() 93 { 94 return dbContext.SaveChanges(); 95 } 96 97 /// <summary> 98 /// 执行自定义SQL语句。 99 /// 该方法提供了一个直接操作数据库表的实现。 100 /// </summary> 101 /// <param name="commandText">SQL语句</param> 102 /// <param name="parameters">参数列表</param> 103 /// <returns>Integer</returns> 104 public int ExecuteSqlCommand(string commandText, params object[] parameters) 105 { 106 return dbContext.Database.ExecuteSqlCommand(commandText, parameters); 107 } 108 } 109 }
OK,基础类型的定义已经完成,看看我们如何使用它吧。先模拟一个场景:当前要添加一个用户,同时在添加用户的时候,要吧该用户增加到指定的部门列表(UserDepartment)下。
1 public void AddNewUser(string userName, string department) 2 { 3 using (IUnitOfWork unit = new UnitOfWork()) 4 { 5 //获取用户类型的仓储 6 var usrRep = unit.Repositry<UserProfile>(); 7 //创建新用户 8 var User = new UserProfile { UserName = userName }; 9 10 //将用户信息添加到数据库。 11 //注意:我们没有使用Create接口的第二个参数,即表示第二个参数默认为false,这表示当前操作暂时不提交到数据库。 12 //如果使用 usrRep.Create(User, true), 则表示直接提交当前记录到数据库。 13 //假如有兴趣,可以尝试第二个参数为true,执行该句之后人为抛出一个异常,看看数据库是如何发生变化的。 14 usrRep.Create(User); 15 16 //throw new Exception(""); 17 18 //获取部门类型的仓储 19 var depRep = unit.Repositry<Department>(); 20 //根据部门名称获取部门信息 21 var Department = depRep.FirstOrDefault(p => p.Name.Equals(department)); 22 //将当前用户添加到该部门内 23 Department.DepartmentUsers.Add(new UserDepartment { UserId = User.UserId, DepartmentId = Department.Id }); 24 25 //提交前面所有的操作。这个才是最关键的。没有这句,一切都是瞎忙活!!! 26 unit.Commit(); 27 } 28 }
当然,通常我们不会这么去做。我们会在UserProfile的定义中添加一个Department集合,同样会在Department中添加一个UserProfile集合,构建用户与部门的多对多关系(EntityFramework的多种数据映射关系以后我们会提到,网上相应的资料也很多),这样就可以很容易的只是用用户仓储实例进行操作,只需一个usrRep.Create(User, true)操作就可以完成上面的业务。不过我们只是为了说明Unitofwork如何工作,大家不必太较真。
好了,今天就此结束。希望我能坚持不懈,也希望大家能一起见证我们努力的结果。