MVC+UnitOfWork+Repository+EF 之我见
UnitOfWork+Repository模式简介:
每次提交数据库都会打开一个连接,造成结果是:多个连接无法共用一个数据库级别的事务,也就无法保证数据的原子性、一致性。解决办法是:在Repository的CRUD操作基础上再包装一层,提供统一的入口,让服务层调用。同一个UnitOfWork实例对象下所有的Repository都共同一个数据库上下文对象(ps:EF用的是DbContext),也就是共用一个事物。提交数据库时,只要有一个操作失败,那么所有的操作都被视为失败。
项目结构:
关键代码:
AggregateRoot.cs:
- using System;
- using System.Collections.Generic;
- namespace CMS.Domain.Core
- {
- /// <summary>
- /// 表示聚合根类型的基类型。
- /// </summary>
- public abstract class AggregateRoot : IAggregateRoot
- {
- #region 方法
- public virtual IEnumerable<BusinessRule> Validate()
- {
- return new BusinessRule[] { };
- }
- #endregion
- #region Object 成员
- public override bool Equals(object obj)
- {
- if (obj == null)
- return false;
- if (ReferenceEquals(this, obj))
- return true;
- IAggregateRoot ar = obj as IAggregateRoot;
- if (ar == null)
- return false;
- return this.Id == ar.Id;
- }
- public override int GetHashCode()
- {
- return this.Id.GetHashCode();
- }
- #endregion
- #region IAggregateRoot 成员
- public Guid Id
- {
- get;
- set;
- }
- #endregion
- }
- }
Channel.cs:
- using CMS.Domain.Core;
- namespace CMS.Domain.Entities
- {
- public class Channel : AggregateRoot
- {
- public string Name
- {
- get;
- set;
- }
- public string CoverPicture
- {
- get;
- set;
- }
- public string Desc
- {
- get;
- set;
- }
- public bool IsActive
- {
- get;
- set;
- }
- public int Hits
- {
- get;
- set;
- }
- }
- }
IUnitOfWork.cs:
- using System;
- namespace CMS.Domain.Core.Repository
- {
- /// <summary>
- /// 工作单元
- /// 提供一个保存方法,它可以对调用层公开,为了减少连库次数
- /// </summary>
- public interface IUnitOfWork : IDisposable
- {
- #region 方法
- IRepository<T> Repository<T>() where T : class, IAggregateRoot;
- void Commit();
- #endregion
- }
- }
UnitOfWork.cs:
- using CMS.Common;
- using CMS.Domain.Core;
- using CMS.Domain.Core.Repository;
- using System;
- using System.Collections;
- using System.Collections.Generic;
- namespace CMS.Infrastructure
- {
- public class UnitOfWork : IUnitOfWork, IDisposable
- {
- #region 变量
- private bool _disposed;
- private readonly IDbContext _dbContext;
- private Hashtable _repositories;
- #endregion
- #region 构造函数
- public UnitOfWork(IDbContext dbContext)
- {
- this._dbContext = dbContext;
- this._repositories = new Hashtable();
- }
- #endregion
- #region 方法
- public virtual void Dispose(bool disposing)
- {
- if (!this._disposed)
- if (disposing)
- this._dbContext.Dispose();
- this._disposed = true;
- }
- #endregion
- #region IUnitOfWork 成员
- public IRepository<T> Repository<T>() where T : class, IAggregateRoot
- {
- var typeName = typeof(T).Name;
- if (!this._repositories.ContainsKey(typeName))
- {
- var paramDict = new Dictionary<string, object>();
- paramDict.Add("context", this._dbContext);
- //Repository接口的实现统一在UnitOfWork中执行,通过Unity来实现IOC,同时把IDbContext的实现通过构造函数参数的方式传入
- var repositoryInstance = UnityConfig.Resolve<IRepository<T>>(paramDict);
- if (repositoryInstance != null)
- this._repositories.Add(typeName, repositoryInstance);
- }
- return (IRepository<T>)this._repositories[typeName];
- }
- public void Commit()
- {
- this._dbContext.SaveChanges();
- }
- #endregion
- #region IDisposable 成员
- public void Dispose()
- {
- this.Dispose(true);
- GC.SuppressFinalize(this);
- }
- #endregion
- }
- }
BaseRepository.cs:
- using CMS.Domain.Core;
- using CMS.Domain.Core.Repository;
- using System;
- using System.Collections.Generic;
- using System.Data.Entity;
- using System.Linq;
- using System.Linq.Expressions;
- namespace CMS.Infrastructure
- {
- public class BaseRepository<T> : IRepository<T> where T : class, IAggregateRoot
- {
- #region 变量
- private readonly DbContext _db;
- private readonly IDbSet<T> _dbset;
- #endregion
- #region 构造函数
- public BaseRepository(IDbContext context)
- {
- this._db = (DbContext)context;
- this._dbset = this._db.Set<T>();
- }
- #endregion
- #region IRepository 成员
- public void Add(T item)
- {
- this._dbset.Add(item);
- }
- public void Remove(T item)
- {
- this._dbset.Remove(item);
- }
- public void Modify(T item)
- {
- this._db.Entry(item).State = EntityState.Modified;
- }
- public T Get(Expression<Func<T, bool>> filter)
- {
- return this._dbset.Where(filter).SingleOrDefault();
- }
- public IEnumerable<T> GetAll()
- {
- return this._dbset.ToList();
- }
- public IEnumerable<T> GetPaged<KProperty>(int pageIndex, int pageSize, out int total, Expression<Func<T, bool>> filter, Expression<Func<T, KProperty>> orderBy, bool ascending = true, string[] includes = null)
- {
- pageIndex = pageIndex > ? pageIndex : ;
- var result = this.GetFiltered(filter, orderBy, ascending, includes);
- total = result.Count();
- return result.Skip((pageIndex - ) * pageSize).Take(pageSize).ToList();
- }
- public IEnumerable<T> GetFiltered<KProperty>(Expression<Func<T, bool>> filter, Expression<Func<T, KProperty>> orderBy, bool ascending = true, string[] includes = null)
- {
- var result = filter == null ? this._dbset : this._dbset.Where(filter);
- if (ascending)
- result = result.OrderBy(orderBy);
- else
- result = result.OrderByDescending(orderBy);
- if (includes != null && includes.Length > )
- {
- foreach (var include in includes)
- {
- result = result.Include(include);
- }
- }
- return result.ToList();
- }
- #endregion
- }
- }
IDbContext.cs:
- namespace CMS.Infrastructure
- {
- public interface IDbContext
- {
- #region 方法
- int SaveChanges();
- void Dispose();
- #endregion
- }
- }
CMSDbContext.cs:
- using CMS.Infrastructures.Mapping;
- using System.Data.Entity;
- using System.Data.Entity.ModelConfiguration.Conventions;
- namespace CMS.Infrastructure
- {
- public class CMSDbContext : DbContext, IDbContext
- {
- #region 构造函数
- public CMSDbContext()
- : base("SqlConnectionString")
- {
- }
- #endregion
- #region DbContext 重写
- protected override void OnModelCreating(DbModelBuilder modelBuilder)
- {
- modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
- modelBuilder.Configurations.Add(new ChannelEntityConfiguration());
- }
- #endregion
- }
- }
UnityConfig.cs:
- using Microsoft.Practices.Unity;
- using Microsoft.Practices.Unity.Configuration;
- using System;
- using System.Collections.Generic;
- namespace CMS.Common
- {
- public class UnityConfig
- {
- #region 属性
- public static IUnityContainer Container
- {
- get
- {
- return container.Value;
- }
- }
- #endregion
- #region 方法
- private static Lazy<IUnityContainer> container = new Lazy<IUnityContainer>(
- () =>
- {
- var container = new UnityContainer();
- RegisterTypes(container);
- return container;
- });
- private static void RegisterTypes(IUnityContainer container)
- {
- container.LoadConfiguration();
- }
- public static T Resolve<T>(IDictionary<string, object> paramDict = null)
- {
- var list = new ParameterOverrides();
- if (paramDict != null && paramDict.Count > )
- {
- foreach (var item in paramDict)
- {
- list.Add(item.Key, item.Value);
- }
- }
- return Container.Resolve<T>(list);
- }
- #endregion
- }
- }
ChannelApplcationService.cs:
- using AutoMapper;
- using CMS.Domain.Core.Repository;
- using CMS.Domain.Entities;
- using CMS.DTO;
- using Microsoft.Practices.Unity;
- namespace CMS.Applcation
- {
- public class ChannelApplcationService
- {
- #region 属性
- [Dependency]
- public IUnitOfWork UnitOfWork { get; set; }
- #endregion
- #region 方法
- public Response<bool> Add(ChannelDTO dto)
- {
- var resp = new Response<bool>();
- var channel = Mapper.Map<Channel>(dto);
- using (this.UnitOfWork)
- {
- var channelAddRepository = this.UnitOfWork.Repository<Channel>();
- channelAddRepository.Add(channel);
- this.UnitOfWork.Commit();
- }
- resp.Result = true;
- return resp;
- }
- #endregion
- }
- }
序列图:
心得体会:
1. Repository的CRUD操作只能作用于继承了AggregateRoot基类的DomainObject(ps:以实际项目情况为准,可以做适当的妥协)。
2. DomainObject中涉及到集合类型(如IList,ISet等)的聚合属性需要加“virtual”关键字,让ORM框架识别做Lazyload处理。
3. 各自独立的业务逻辑写在对应的DomainObject方法中,方法体内只能处理自身以及内部聚合对象的数据和状态等信息,被聚合的对象不建议里面再有方法,只需定义相关属性即可(ps:涉及到对外通知、发布消息等场景以DomainEvent的方式处理,关于DomainEvent的概念和使用会开新章进行简述)。
4. 把需要多个DomainObject交互和协调的业务逻辑放到DomainService中(ps:在Applcation Layer中调用。另外DomainService是否能调用Repository对象我一直很困惑,因为看过有代码是这么写的,但又有人不建议这么做......)。
5. 在AggregateRoot基类中定义验证BusinessRule的虚方法,供子类重写,并在统一的地方执行(比如Applcation Layer)
6. 定义DomainException,用来封装Domain Layer层的异常信息,对上层(Applcation Layer)暴露。
7. Applcation Layer代码的主要作用(可用WCF、WebAPI或直接Dll引用等方式对上层(UI Layer)暴露)
- 接收UI Layer传递的DTO对象。
- 通过AutoMapper组件转换成对应的DomainObject,并调用其方法(ps:内部业务逻辑的封装)。
- 调用Repository对象来实现CRUD操作(ps:这时数据还只是在内存中)。
- 调用UnitOfWork的Commit方法来实现数据的真正提交(ps:事物级别的)。
所以可以看出Applcation Layer主要用来处理业务的执行顺序,而不是关键的业务逻辑。
Applcation Layer如果用WCF或WebAPI的方式对外暴露有个好处,可以针对其作负载均衡,坏处是额外增加了IIS的请求开销。
8. DTO和DomainObject区别
DTO(ps:为了简单起见,这里把DTO和ViewModel放在一块说了):
- 根据实际业务场景加上Required、StringLength等验证特性,结合MVC框架的内部验证机制,可在Controller层做到数据的有效性验证(ps:永远都不要轻易相信浏览器端提交的数据,即使已经有了js脚本验证......)。
- 负责View数据的展现和表单提交时数据的封装。
- 负责把数据从UI Layer传递到Applcation Layer,里面只能有属性,而且是扁平的,结构简单的属性。
DomainObject:通俗点说就是充血模型,包括属性和行为,在DDD整个框架设计体系中占非常重要的地位,其涵盖了整个软件系统的业务逻辑、业务规则、聚合关系等方面。(ps;如果业务很简单,可以只有属性)
9. UI Layer:自我学习UnitOfWork+Repository以来,一直用的是MVC框架做前台展现,选择的理由:1. Unity4MVC的IOC功能非常强大,2. 天生支持AOP的思想,3. 更加传统、原始的Web处理方式,4. Areas模块对插件化设计的支持,5. Ajax、ModelBuilder、JSON、验证,6. 整个Http访问周期内提供的各种扩展点等。
MVC+UnitOfWork+Repository+EF 之我见的更多相关文章
- MVC+UnitOfWork+Repository+EF
MVC+UnitOfWork+Repository+EF UnitOfWork+Repository模式简介: 每次提交数据库都会打开一个连接,造成结果是:多个连接无法共用一个数据库级别的事务,也就无 ...
- MVC中使用EF(2):实现基本的CRUD功能
MVC中使用EF(2):实现基本的CRUD功能 By Tom Dykstra |July 30, 2013 Translated by litdwg Contoso University示例网站 ...
- MVC UnitOfWork EntityFramework架构
MVC UnitOfWork EntityFramework架构,网站速度慢的原因总结! 最近参考使用了郭明峰的一套架构来做新的项目架构,这套架构看起来还是不错的,先向小郭同学的分享精神致敬! (郭同 ...
- ASP.NET MVC 5 with EF 6 上传文件
参考 ASP.NET MVC 5 with EF 6 - Working With Files Rename, Resize, Upload Image (ASP.NET MVC) ASP ...
- 解析ASP.NET Mvc开发之EF延迟加载
目录: 1)从明源动力到创新工场这一路走来 2)解析ASP.NET WebForm和Mvc开发的区别 3)解析ASP.NET Mvc开发之查询数据实例 ------------------------ ...
- MVC 中使用EF
EF 1)简单查询 后台代码 using MvcApplication18.Models; using System; using System.Collections.Generic; using ...
- MVC中使用EF(1):为ASP.NET MVC程序创建Entity Framework数据模型
为ASP.NET MVC程序创建Entity Framework数据模型 (1 of 10) By Tom Dykstra |July 30, 2013 Translated by litdwg ...
- mvc+webapi+dapper+ef codefirst项目搭建
首先项目是mvc5+webapi2.0+orm数据处理(dapper)+ef动态创建数据库. 1.项目框架层次结构: mvc项目根据不同的业务和功能进行不同的区域划分[今后项目维护起来方便],mode ...
- .NET Core2.0 MVC中使用EF访问数据
使用环境:Win7+VS2017 一.新建一个.NET Core2.0的MVC项目 二.使用Nuget添加EF的依赖 输入命令:Install-Package Microsoft.EntityFram ...
随机推荐
- Java自由块(静态和非静态)(转载)
java中的自由块分为两种: 静态块和非静态块 静态块: public class Test { 2 static int x = 10; 3 //静态块:静态块的执行时机是在class文件装载的时候 ...
- Scala 学习笔记(五)
def main(args : Array[String]): Unit = { def add(x:Int,y:Int):Int = { return x+y; } def subtract:(In ...
- Linux系统程序的运行级别
Linux系统有7个运行级别: 运行级别 描述 0 系统停机状态,系统默认运行级别不能设为0,否则不能正常启动 1 但用户工作状态,root权限,用于系统维护,禁止远程登录 2 多用户状态(没有NFS ...
- hdoj 5074
Problem Description Hatsune Miku is a popular virtual singer. It is very popular in both Japan and C ...
- Bootstrap<基础十五> 输入框组
Bootstrap 支持的另一个特性,输入框组.输入框组扩展自 表单控件.使用输入框组,可以很容易地向基于文本的输入框添加作为前缀和后缀的文本或按钮. 通过向输入域添加前缀和后缀的内容,您可以向用户输 ...
- RabbitMQ Step by step(一) 安装
RabbitMQ是一个消息中间件,可以存储转发消息,个人感觉优越于MSMQ RabbitMQ官方网站(http://www.rabbitmq.com)可以获取到安装文件,建议大家详细浏览官方网站,官方 ...
- range()和xrange()
range(): range([start,] stop[, step]) 如: range(10) [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] range()默认起始点为0 且ra ...
- 003-Tuple、Array、Map与文件操作入门实战
003-Tuple.Array.Map与文件操作入门实战 Tuple 各个元素可以类型不同 注意索引的方式 下标从1开始 灵活 Array 注意for循环的until用法 数组的索引方式 上面的for ...
- 使用CSS3动画模拟实现小球自由落体效果
使用纯CSS代码模拟实现小球自由落体效果: html代码如下: <div id="ballDiv"> <div id="ball">&l ...
- Linux网络编程(简单的时间获取服务器)
1.一个简单的服务器时间获取程序 服务器和客户端采用UDP通信的方式,来编写一个简单的时间获取应用. 把过程大致理顺一下,首先是服务器端的编写,使用的是迭代的方式,没有并发 先创建一个socket而后 ...