写这篇博文只是介绍下思路,主要目的是为了找工作,所以细节不是很详细,请大家勿喷
第一章架构介绍
1,架构介绍
先看个整体图
Infrastructure解决方案中使用的基础工具。
Application站点应用层,组织业务逻辑管理业务事务的地方。比如登录,菜单等。Models是用来放UI中所使用的ViewModel即DTO类,以及利用value.injecter进行实体和viewmodel转换的映射类。
Core 核心层,实现ORM的实体类和映射,以及仓储,业务服务类等。
Demo.site.web 即web层。在这里使用了jqgrid表格UI,bootstrap,和jquery验证等增加用户体验的富客户端技术。
2,采用面向接口开发模式,
面向接口开发:好处不用说了,最大的理由,方便解耦。仓储和业务服务层都采用接口设计,有个很大的不便,每个类都要搞个接口。有点小麻烦。下面详细说下这种设计。
在基类NHibernateRepositoryBase中设置共用的增删改(CUD)操作,采用泛型设计,这样更加通用,查询先不设置,因为这里又牵涉到一个知识点。等下再说。下面是基类代码。
//----------------------------------------------------------------------- // <copyright file="EFRepositoryBase.cs" company="Company"> // Copyright (C) Company. All Rights Reserved. // </copyright> // <author></author> // <summary></summary> //----------------------------------------------------------------------- using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using Component.Tools; using NHibernate; namespace Component.Data { /// <summary> /// NH仓储操作基类 /// </summary> /// <typeparam name="TEntity">动态实体类型</typeparam> /// <typeparam name="TKey">实体主键类型</typeparam> public class NHibernateRepositoryBase<TEntity, TKey> : IRepository<TEntity, TKey> where TEntity : Entity { #region 属性 /// <summary> /// 作为基类,若被继承则必须有无参构造函数,否则报错。或者在继承类中显示调用 有参构造。或者用属性来ioc则不需显示定义无参构造函数。 /// </summary> public NHibernateRepositoryBase() { } /// <summary> /// 获取 仓储上下文的实例 /// </summary> private IUnitOfWorkContext NHContext; public NHibernateRepositoryBase(IUnitOfWorkContext UnitOfWork) { NHContext = UnitOfWork; } public virtual ISession Session { get { return NHContext.Session; } } /// <summary> /// 获取 当前实体的查询数据集 /// </summary> public virtual IQueryable<TEntity> Entities { get { return NHContext.Set<TEntity, TKey>(); } } #endregion #region 公共方法 /// <summary> /// 插入实体记录 /// </summary> /// <param name="entity"> 实体对象 </param> /// <param name="isSave"> 是否执行保存 </param> /// <returns> 操作影响的行数 </returns> public virtual object Insert(TEntity entity, bool isSave = true) { PublicHelper.CheckArgument(entity, "entity"); object obj = NHContext.RegisterNew<TEntity, TKey>(entity); if (isSave) NHContext.Commit(); return obj; } /// <summary> /// 批量插入实体记录集合 /// </summary> /// <param name="entities"> 实体记录集合 </param> /// <param name="isSave"> 是否执行保存 </param> /// <returns> 操作影响的行数 </returns> public virtual void Insert(IEnumerable<TEntity> entities, bool IsUsedStatelessSession) { PublicHelper.CheckArgument(entities, "entities"); NHContext.RegisterNew<TEntity, TKey>(entities,IsUsedStatelessSession); } /// <summary> /// 删除指定编号的记录 /// </summary> /// <param name="id"> 实体记录编号 </param> /// <param name="isSave"> 是否执行保存 </param> /// <returns> 操作影响的行数 </returns> public virtual int Delete(TKey id, bool isSave = true) { int isTrue = 0; PublicHelper.CheckArgument(id, "id"); try { TEntity entity = NHContext.Set<TEntity, TKey>().Single<TEntity>(t=>t.Id.ToString()==id.ToString()); if (entity != null) isTrue = Delete(entity, isSave); } catch(Exception e) { PublicHelper.ThrowDataAccessException(e.Message, e); } return isTrue; } /// <summary> /// /// </summary> /// <param name="ids"></param> /// <param name="IsUsedStatelessSession">不可以级联删除</param> /// <returns></returns> public int Delete(string ids, bool IsUsedStatelessSession) { PublicHelper.CheckArgument(ids, "ids"); return NHContext.RegisterIdsDeleted<TEntity, TKey>(ids, IsUsedStatelessSession); } /// <summary> /// 删除实体记录 /// </summary> /// <param name="entity"> 实体对象 </param> /// <param name="isSave"> 是否执行保存 </param> /// <returns> 操作影响的行数 </returns> public virtual int Delete(TEntity entity, bool isSave = true) { PublicHelper.CheckArgument(entity, "entity"); NHContext.RegisterDeleted<TEntity, TKey>(entity); if(isSave) return NHContext.Commit(); else return 0; } /// <summary> /// 删除实体记录集合 /// </summary> /// <param name="entities"> 实体记录集合 </param> /// <param name="isSave"> 是否执行保存 </param> /// <returns> 操作影响的行数 </returns> public virtual void Delete(IEnumerable<TEntity> entities, bool IsUsedStatelessSession) { PublicHelper.CheckArgument(entities, "entities"); NHContext.RegisterDeleted<TEntity, TKey>(entities, IsUsedStatelessSession); } /// <summary> /// 删除所有符合特定表达式的数据 /// </summary> /// <param name="predicate"> 查询条件谓语表达式 </param> /// <param name="isSave"> 是否执行保存 </param> /// <returns> 操作影响的行数 </returns> public virtual int Delete(Expression<Func<TEntity, bool>> predicate, bool IsUsedStatelessSession) { PublicHelper.CheckArgument(predicate, "predicate"); List<TEntity> entities = NHContext.Set<TEntity, TKey>().Where(predicate).ToList(); if (entities.Count > 0) { Delete(entities, IsUsedStatelessSession); return 1; } else return 0; } /// <summary> /// 更新实体记录 /// </summary> /// <param name="entity"> 实体对象 </param> /// <param name="isSave"> 是否执行保存 </param> /// <returns> 操作影响的行数 </returns> public virtual int Update(TEntity entity, bool isSave = true) { PublicHelper.CheckArgument(entity, "entity"); NHContext.RegisterModified<TEntity, TKey>(entity); if (isSave) return NHContext.Commit(); else return 0; } /// <summary> /// 使用附带新值的实体信息更新指定实体属性的值 /// </summary> /// <param name="propertyExpression">属性表达式</param> /// <param name="isSave">是否执行保存</param> /// <param name="entity">附带新值的实体信息,必须包含主键</param> /// <returns>操作影响的行数</returns> public void Update(IEnumerable<TEntity> entities, bool IsUsedStatelessSession) { PublicHelper.CheckArgument(entities, "entities"); NHContext.RegisterModified<TEntity, TKey>(entities, IsUsedStatelessSession); } /// <summary> ///使用session时用和用linq查询集合时用 /// </summary> /// <param name="args">0为是否用session,1为session对象,剩下的是查询参数</param> /// <returns></returns> public IQueryable<TEntity> FindBySpecification(Specification<TEntity>[] specifications, params object[] args) { bool isSuccess = false; IQueryable<TEntity> querys; if (specifications == null || specifications.Any(s => s == null)) throw new ArgumentNullException("specifications"); if (args.Count()!=0&&bool.TryParse(args[0].ToString(), out isSuccess)) { args[1] = Session; querys = specifications[0].SatisfyingElementsFrom(args); } else{ querys = specifications.Aggregate(Entities, (current, specification) => specification.SatisfyingElementsFrom(current, args)); } return querys; } #endregion /// <summary> /// 用linq查询单个实体时用,注意返回集合实体的重载 /// </summary> /// <param name="candidates"></param> /// <param name="args"></param> /// <returns></returns> public TEntity FindBySpecification(Specification<TEntity> specification,params object[] args) { if (specification == null) throw new ArgumentNullException("specification"); return specification.SatisfyingElementsFrom(Entities.AsEnumerable(), args); } } }
有了基类NHibernateRepositoryBase 就可以设置储了。所有的仓储都实现接口,每个接口继承基类NHibernateRepositoryBase 的接口,这样如果需求有所变化只需要在仓储接口上加上方法即可。如图所示
业务层同样如此,设计一个业务基类,然后如上所示继承即可。在Demo.site是实现视图逻辑的地方,调用业务层的业务类实现如登录等业务逻辑。然后在Controller中引用接口。进行调用。非常方便。如需修改只需找到相应的接口和实现类即可,其他地方不用修改,从而实现了一定程度上解耦的目的。
上面说了,查询还没有介绍,我在这架构中,把查询放在了业务层方面。利用规约模式,资料在这里http://www.cnblogs.com/daxnet/archive/2010/07/19/1780764.html,仓储基类中只需放置一个调用函数即可。如图。
specification.SatisfyingElementsFrom 即是利用规约调用查询类的代码。规约类即一个查询设置一个类。这样做到了解耦。维护方便,但会出现大量的这种类,使项目臃肿,解决方法上面的连接已经给出。
利用规约和延迟加载将查询在业务类中实现,可以按需查询,不必像以前那样,把所有的公共查询如加载所有数据load,getbyname等定义都放在基类中,如果没有用到,就会使代码臃肿,还有一个不便就是在基类中执行查询再把结果传递到业务层,会把所有结果都加载到内存中,这会大大降低查询效率,解决方案就是利用廷迟加载,传递IQueryable<T>泛型实体集或session类到业务层,进行按需查询,这样就可避免上述情况。
在这个架构中我设计了根据业务层需要,把IQueryable<T>或session传到规约类中进行查询。简单查询可以用Iqueryable复杂一些的用session,如下图
业务层根据args[]第一个参数来决定是否传递session
代码如下
/// <summary> ///使用session时用和用linq查询集合时用 /// </summary> /// <param name="args">0为是否用session,1为session对象,剩下的是查询参数</param> /// <returns></returns> public IQueryable<TEntity> FindBySpecification(Specification<TEntity>[] specifications, params object[] args) { bool isSuccess = false; IQueryable<TEntity> querys; if (specifications == null || specifications.Any(s => s == null)) throw new ArgumentNullException("specifications"); if (args.Count()!=0&&bool.TryParse(args[0].ToString(), out isSuccess)) {//使用session查询 args[1] = Session; querys = specifications[0].SatisfyingElementsFrom(args); } else{//使用linq查询 querys = specifications.Aggregate(Entities, (current, specification) => specification.SatisfyingElementsFrom(current, args)); } return querys; }
然后在规约类中进行查询即可。重写specification基类方法即可,代码如下。
using System.Collections.Generic; using System.Linq; using NHibernate; namespace Component.Data { public class Specification<T> where T : Entity { public ISession _session{get;set;} /// <summary> ///使用session时用 /// </summary> /// <param name="args">0为是否用session,1为session对象,剩下的是查询参数</param> /// <returns></returns> public virtual IQueryable<T> SatisfyingElementsFrom(params object[] args) { return null; } /// <summary> /// 用linq查询集合时用 /// </summary> /// <param name="candidates"></param> /// <param name="args"></param> /// <returns></returns> public virtual IQueryable<T> SatisfyingElementsFrom(IQueryable<T> candidates, params object[] args) { return null; } /// <summary> /// 用linq查询单个实体时用 /// </summary> /// <param name="candidates"></param> /// <param name="args"></param> /// <returns></returns> public virtual T SatisfyingElementsFrom(IEnumerable<T> candidates, params object[] args) { return candidates.Where<T>(t => t.Id.ToString() == args[0].ToString()).FirstOrDefault(); } /// <summary> /// id值取回记录 /// </summary> /// <param name="args[0]">id值</param> /// <returns>根据id返回的实体</returns> //public TEntity GetElementsById<TEntity>(IQueryable<TEntity> candidates, params object[] args) where TEntity : Entity //{ // PublicHelper.CheckArgument(args, "args"); // return candidates.Single<TEntity>(t => t.Id.ToString() == args[0].ToString()); //} } }
3,领域驱动(DDD)
这是一个很大的话题,有兴趣的可以看这里。http://www.cnblogs.com/xishuai/p/ddd-entity-value-object-follow-up.html。。在这个架构中介绍下DDD驱动中怎样体现聚合和聚合根的实现。先看下实体关系类图。
userAccount和roleUserAccount、Role这三个实体是一个聚合,通过聚合根进行数据操作。
Capability,Menu,RoleCapability,和role,可以划为一个聚合,role划为聚合根,通过它进行数据操作
Capability,Menu,可以分别做为他们聚合的聚合根,进行数据操作。
至于怎样实现数据操作的,当然要靠orm了。因为这里只是为了展现我所会的技术,找工作用,具体详细的DDD可以拜读博客园中一些大牛的文章。
4,nhibernate(orm)
现在大多数公司都使用了EF code first,我学习Nhibernate不知能不能派上用场很是忐忑。nh相信大家已经很熟悉了,不多介绍再这里说一下,他的映射方式
使用fluent nhibernate来进行映射,使用它完全不再用xml了,有智能提示,不再担心手美女飘过时手抖出错的问题了。资料在这里,http://www.fluentnhibernate.org/当然园子里也有中文文档,不过还是原文来的详细些。这里用automapping和fluentmapping结合使用,大大减少编码量,需要特别处理的,如对某个类的cascade级别的修改,id命名策略的处理等,可以通过实现conventions接口来处理。非常方便。看代码
using NHibernate.Cfg; using FluentNHibernate.Conventions; using FluentNHibernate.Cfg; using FluentNHibernate.Cfg.Db; using FluentNHibernate.Automapping; using Demo.Core.NHFluentConfiguring.Conventions; using FluentNHibernate.Conventions.Helpers; using System.Reflection; using System.Data; using Demo.Core.Models; namespace Demo.Core.NHFluentConfiguring { public enum CurrentSessionContextClass { managed_web, call, thread_static, web } public static class FluentNhCfg { public static Configuration GetNHConfiguration() { //也可以用ioc注入 var cfg = new AutoMappingConfig(); var model = AutoMap.Assembly(Assembly.Load("Demo.Core.Models")) .Where(t => t.Namespace == "Demo.Core.Models" && t.BaseType.Name == "Entity" && t.Name.IsNotAny(new[] { "RoleCapability", "RoleUserAccount" })) .IgnoreBase<Component.Data.Entity>() .Override<Menu>(map =>map.IgnoreProperty(ig => ig.Children)) .Conventions.Add(new EnumConvention(), new InverseConvention(), new HiloIdConvention(), DefaultCascade.All(), new MenuCascadeCapabilityConvention(), new MenuCascadeConvention(), DefaultLazy.Always(), DynamicInsert.AlwaysTrue(), DynamicUpdate.AlwaysTrue(), OptimisticLock.Is(x => x.Dirty()) ); // Cache.Is(x => x.ReadWrite()) var nhConfig = Fluently.Configure() .Database(MsSqlConfiguration.MsSql2008 .ConnectionString("Data Source=.;Initial Catalog=FrameworkShan;Integrated Security=True") //fnh api中没有的属性可以用raw来设置nh中的属性。 //•validate:在程序启动时,检查持久化类与映射文件的改变。 // •none:不产生任何操作。 //•create-drop:在程序启动时,自动生成数据库架构并导入到数据库中,在程序关闭时,删除数据库架构,常用在测试中。 //update 加载hibernate自动更新数据库结构 .Raw("hbm2ddl.auto", "update") .ShowSql() .IsolationLevel(IsolationLevel.ReadCommitted) .AdoNetBatchSize(20) ) //.CurrentSessionContext("web")在web项目和test项目中分别各自设置 .Mappings(mappings => { mappings.FluentMappings .AddFromAssemblyOf<Demo.Core.Models.Mapping.RoleCapabilityMapping>() .Conventions.Add(new EnumConvention()) .ExportTo(@"C:\Users\qishan\Desktop\fnh"); mappings.AutoMappings.Add(model) .ExportTo(@"C:\Users\qishan\Desktop\fnh"); //Conventions若放在fluentMappings中各设定将不起作用。只有放在autoMapping中才起作用 //在此设置的默认conventions将被在Mapping文件中设置的属性替换。 }) .BuildConfiguration(); //string[] nhd = nhConfig.GenerateSchemaCreationScript(new NHibernate.Dialect.MsSql2008Dialect()); return nhConfig; } } }
5,控制反转(ioc)
在选择ioc时很纠结是用spring,ninject,还是其他,最后想想还是以效率为优吧,就选了这个autofac,官方推荐是以构造函数注入的,不过也支持属性注入
对很多功能都进行了集成,比如,对controller,modelbinder等直接在global.cs中一行代码就搞定,还有注入模块支持,一些功能所需要的注入可以写在一个模块中统一注入,这个功能很优雅。还有聚合服务注入,这个在controller中最好用,把controller中所用的服处,放在一个聚合接口中,调用时直接调用接口,就可以调用服务,官方文档这里http://autofac.readthedocs.org/en/latest/,使用方式,看下代码就好了。
protected void Application_Start() { HibernatingRhinos.Profiler.Appender.NHibernate.NHibernateProfiler.Initialize(); var builder = new ContainerBuilder(); builder.RegisterModelBinders(Assembly.GetExecutingAssembly()); builder.RegisterModelBinderProvider(); builder.RegisterControllers(Assembly.GetExecutingAssembly()).PropertiesAutowired(); //To make use of property injection for your filter attributes builder.RegisterFilterProvider(); builder.RegisterModule<AutofacWebTypesModule>(); builder.RegisterModule<NHibernateModule>(); // builder.RegisterModule<ControllersModule>(); builder.RegisterAssemblyTypes(Assembly.GetAssembly(typeof(RoleRepository))) .Where(t => t.Name.EndsWith("Repository")) .AsImplementedInterfaces(); builder.RegisterAssemblyTypes(Assembly.GetAssembly(typeof(UserAccountMapper))) .Where(t => t.Name.EndsWith("Mapper")) .AsSelf(); builder.RegisterAssemblyTypes(Assembly.GetAssembly(typeof(RoleService))) .Where(t => t.Name.EndsWith("Service")) .AsImplementedInterfaces(); builder.RegisterAssemblyTypes(Assembly.GetAssembly(typeof(DataPagingViewService))) .Where(t => t.Name.EndsWith("ViewService")) .AsImplementedInterfaces(); builder.RegisterType<ExtensibleActionInvoker>().As<IActionInvoker>().InstancePerRequest(); IContainer Container = builder.Build(); IoC.Container = new AutofacDependencyResolver(Container); DependencyResolver.SetResolver(IoC.Container); AreaRegistration.RegisterAllAreas(); WebApiConfig.Register(GlobalConfiguration.Configuration); FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); RouteConfig.RegisterRoutes(RouteTable.Routes); BundleConfig.RegisterBundles(BundleTable.Bundles); ValueProviderFactories.Factories.Insert(0, new JsonNetValueProviderFactory()); ModelBinders.Binders.Add(typeof(Component.Data.Entity), new JsonNetModelBinder()); // Component.Data.Entity }
这里注意 IoC.Container = new AutofacDependencyResolver(Container);是使用依赖解析器注入的,不是用工厂模式。当业务需要使用controller工厂时在返回controller实例方法中要这样调用:定义一个ioc静态类
using System; using Autofac; using Autofac.Integration.Mvc; namespace Component.Tools { public static class IoC { public static AutofacDependencyResolver Container { get;set; } public static object Resolve(Type type) { return Container.GetService(type); } } }
在global.cs中设置Container属性,然后在工厂方法里调用。IoC.Resolve(controllerType)来得到实例。
好了,架构这节先说到这里吧