Angular2是一个前端开发框架,在引入ts之后使得我们这些C#开发者能够更快的熟悉该框架,angular2开发首先要知道这是一个SPA(单页应用),我们要摆脱以往的asp.net中的MVC模式的固有思路,angular2开发重点是组件(Component),所以开发之前尤其要能清楚这个概念。
在与angular2配合使用的后端(RestFul)我采用的是基于asp.net的web api2.0,这也是目前比较流行的一种方式。我在webapi开发中在数据层使用的是基于NHibernate的Repository+UnitWork模式,自然也会用到依赖注入,我选的依赖注入工具是AutoFac。在前端与后端的数据传递使用DTO,为了方便,使用的是AutoMapper做数据映射。下面从底层开始,以一个简单实例演示整个过程。
领域模型
在建模时曾考虑使用EF6,目前也有很多关于EF6的争议就是EF中实际已包含了Repository和UnitOfWork模式,那么在使用EF时就不必再使用这种模式了,为了能够熟练这种模式我选用了NHibernate作为ORM框架。
数据实体类
public class TGroup : EntityBase {
public TGroup() {
Users = new List<TUser>();
}
public virtual string ID { get; set; }
public virtual string GROUPNAME { get; set; }
public virtual decimal? NORDER { get; set; }
public virtual string PARENTID { get; set; }
public virtual DateTime? CREATED { get; set; }
public virtual string GROUPTYPE { get; set; }
public virtual string GROUPCODE { get; set; }
public virtual IList<TUser> Users { get; set; }
}
数据实体类都继承于EntityBase,EntityBase是一个空的类,目前没有任何属性及方法,只是为了在使用Repositroy模式时方便,其实可以抽象出诸如ID这样的公共属性出来。
关系映射
<?xml version="1.0" encoding="utf-8"?>
<hibernate-mapping assembly="URS.Data" namespace="URS.Data.Model" xmlns="urn:nhibernate-mapping-2.2">
<class name="TGroup" table="T_GROUP" lazy="true" >
<id name="ID" column="ID" />
<property name="GROUPNAME">
<column name="GROUPNAME" sql-type="VARCHAR2" not-null="false" />
</property>
<property name="NORDER">
<column name="NORDER" sql-type="NUMBER" not-null="false" />
</property>
<property name="PARENTID">
<column name="PARENTID" sql-type="VARCHAR2" not-null="false" />
</property>
<property name="CREATED">
<column name="CREATED" sql-type="DATE" not-null="false" />
</property>
<property name="GROUPTYPE">
<column name="GROUPTYPE" sql-type="VARCHAR2" not-null="false" />
</property>
<property name="GROUPCODE">
<column name="GROUPCODE" sql-type="VARCHAR2" not-null="true" />
</property>
<bag name="Users" inverse="true" cascade="all">
<key column="GROUPID" />
<one-to-many class="TUser" />
</bag>
</class>
</hibernate-mapping>
在实体类中就可以看出这是一个一对多关系,TGroup包含了一个TUser集合,所以在映射时使用的bag中描绘了这个一对多关系,这里需要说明的是我所使用的是oracle数据库。
NHibernate配置
<?xml version="1.0" encoding="utf-8"?>
<hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
<session-factory>
<property name="connection.driver_class">NHibernate.Driver.OracleManagedDataClientDriver</property>
<property name="connection.connection_string">User Id=urs;Password=urs;Data Source=(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=localhost)(PORT=1521))(CONNECT_DATA=(SERVICE_NAME=ORA11G)))</property>
<property name="show_sql">true</property>
<property name="dialect">NHibernate.Dialect.Oracle10gDialect</property>
<property name="query.substitutions">true 1, false 0, yes 'Y', no 'N'</property>
<property name="current_session_context_class">call</property>
<mapping assembly="URS.Data"/>
</session-factory>
</hibernate-configuration>
这是一个使用oracle的odp的连接配置,这个配置文件需要放在WebAPI解决方案下
Repository模式
IRepository接口
public interface IRepository<TEntity> :IDependency where TEntity : EntityBase
{
TEntity Get(object id);
IList<TEntity> GetAll();
void Add( TEntity entity);
void Update(TEntity entity);
void Delete(TEntity entity);
void Delete(object id);
}
这里的IRepository接口只提取了部分通用的CURD操作,这里需要注意的是该接口继承了IDependency,这是为了我们下面使用依赖注入的一个空接口,下面会说到这样的目的。
Repository基类
public class NHRepositoryBase<TEntity> : IRepository<TEntity> where TEntity : EntityBase
{
protected virtual ISession Session
{
get
{
return SessionBuilder.GetCurrentSession();
}
}
public virtual TEntity Get(object id)
{
return Session.Get<TEntity>(id);
}
public virtual IList<TEntity> GetAll()
{
return Session.CreateCriteria<TEntity>()
.List<TEntity>();
}
public virtual void Add(TEntity entity)
{
try
{
Session.Save(entity);
}
catch (Exception ex)
{
throw ex;
}
}
public virtual void Update(TEntity entity)
{
try
{
Session.SaveOrUpdate(entity);
}
catch (Exception ex)
{
throw ex;
}
}
public virtual void Delete(TEntity entity)
{
try
{
Session.Delete(entity);
}
catch (Exception ex)
{
throw ex;
}
}
public virtual void Delete(object id)
{
try
{
TEntity entity=this.Get(id);
Session.Delete(entity);
}
catch (Exception ex)
{
throw ex;
}
}
}
由于我们使用的是NHibernate,所以我们需要一个全局的ISession,SessionBuilder是我封装的一个类,用来处理ISession,保障一个应用中只有一个ISession实例。
业务Repository
public class GroupRepository : NHRepositoryBase<TGroup>,IGroupRepository
{
}
由于Repository基类中包含了通用的CURD操作,所以这个GroupRepository 只需继承这个基类即可,如果还有其他操作或属性则再进一步抽象出IGroupRepository,这里我还没打算加入其他操作所以IGroupRepository是个空的接口,以后也许会加入其他方法或属性。
UnitOfWork模式
unitofwork模式重点是处理事务,对变化的数据一次提交保证数据完整性。
IUnitOfWork接口
public interface IUnitOfWork : IDisposable, IDependency
{
IRepository<TEntity> BaseRepository<TEntity>() where TEntity : EntityBase;
void BeginTransaction();
void Commit();
void Rollback();
}
这个接口同样也继承了IDependency,也是为了后面的依赖注入。在这个接口中我还定义了一个IRepository<TEntity>的属性,这是一个能够根据实体类来获取其对应的Repository。
UnitOfWork实现
public class NHUnitOfWork:IUnitOfWork
{
private ITransaction _transaction;
private Hashtable _repositories;
public ISession Session
{
get
{
return SessionBuilder.GetCurrentSession();
}
}
public NHUnitOfWork()
{
}
public void BeginTransaction()
{
this._transaction = Session.BeginTransaction() ;
}
public void Commit()
{
try
{
// commit transaction if there is one active
if (_transaction != null && _transaction.IsActive)
_transaction.Commit();
}
catch
{
// rollback if there was an exception
if (_transaction != null && _transaction.IsActive)
_transaction.Rollback();
throw;
}
//finally
//{
// Session.Dispose();
//}
}
public void Rollback()
{
//try
{
if (_transaction != null && _transaction.IsActive)
_transaction.Rollback();
}
//finally
//{
// Session.Dispose();
//}
}
public void Dispose()
{
Session.Dispose();
}
public IRepository<T> BaseRepository<T>() where T : EntityBase
{
if (_repositories == null)
_repositories = new Hashtable();
var type = typeof(T).Name;
if (!_repositories.ContainsKey(type))
{
var repositoryType = typeof(NHRepositoryBase<>);
var repositoryInstance =
Activator.CreateInstance(repositoryType
.MakeGenericType(typeof(T)));
_repositories.Add(type, repositoryInstance);
}
return (IRepository<T>)_repositories[type];
}
}
这里使用了反射创建Repository实例,这里是创建的通用基类型的Repository实例,好处就是当我们在业务逻辑层使用UnitOfWork时如果是通用的操作,则不用注入Repository了,否则可能要构造中可能要注入很多业务Repository,比较麻烦,当然如果不是通用操作的话,还是要注入的,下面会讲到。
业务逻辑层应用
public class GroupService:IGroupService
{
private IUnitOfWork _unitOfWork;
private IGroupRepository _groupRepository;
public GroupService(IUnitOfWork unitOfWork,IGroupRepository groupRepository)
{
_unitOfWork = unitOfWork;
_groupRepository = groupRepository;
}
public IList<TGroup> GetAll()
{
IRepository<TGroup> repo = _unitOfWork.BaseRepository<TGroup>();
return repo.GetAll();
}
public TGroup GetByID(string id)
{
IRepository<TGroup> repo = _unitOfWork.BaseRepository<TGroup>();
return repo.Get(id);
}
public void Add(TGroup entity)
{
_unitOfWork.BeginTransaction();
IRepository<TGroup> repo = _unitOfWork.BaseRepository<TGroup>();
repo.Add(entity);
_unitOfWork.Commit();
}
public void Delete(string id)
{
_unitOfWork.BeginTransaction();
IRepository<TGroup> repo = _unitOfWork.BaseRepository<TGroup>();
repo.Delete(id);
_unitOfWork.Commit();
}
public void Update(TGroup entity)
{
_unitOfWork.BeginTransaction();
IRepository<TGroup> repo = _unitOfWork.BaseRepository<TGroup>();
repo.Update(entity);
_unitOfWork.Commit();
}
}
这里通过在构造函数中注入IUnitOfWork ,IGroupRepository,这里可以看到,由于在本例中使用的都是通用的数据CURD,所以实际上IGroupRepository并没有使用,而实际使用的是IUnitOfWork中创建的IRepository<TGroup>,由于这个例子比较简单,如果是由复杂业务逻辑的话,那么需要在_unitOfWork.BeginTransaction()到_unitOfWork.Commit()之间完成业务处理即可保障数据提交的一致性,如果这中间有异常,IUnitOfWork会回滚,这就意味着要么全部提交,要不都不提交。
这里的GroupService继承了IGroupService,而IGroupService也会继承IDependency,下面将在WebApi的解决方案中介绍AutoFac的依赖注入使用方法。