本系列将使用zTree来创建、编辑关于品牌、车系、车型的无限级分类,使用datagrid显示,源码在github。先上最终效果:
datagrid显示所有记录、分页,提供添加、修改、删除按钮,并提供简单搜索: 
创建分类,弹出模态窗口,zTree显示所有分类,点击勾选按钮或节点,所选节点名称显示到文本框:       
      
提供客户端和服务端验证,验证不通过显示出错信息:      
修改分类,弹出模态窗口,zTree显示当前选中的节点名称:     
创建数据模型并生成到数据库
→创建CarModel.edmx,创建模型,无限级分类的一切"神奇"从ParentID字段开始。

→右键界面,选择"根据模型生成数据库"

→配置数据库连接,运行sql语句......等等,最终生成如下文件:

● App.config中的连接字符串需要复制到MVC主站点的web.config中。    
● CarModel.Context.cs中包含了继承DbContext的上下文。     
● CarModel.tt下包含了所有的领域模型Domain Models。     
● CarModel.edmx.sql每次映射完执行里面的sql语句,将把数据同步到数据库。
架构设计
由于主要是体验无限级分类的增删改查,架构做得相对简单一些。其它组件列举的只是示意,实际只用到了缓存和序列化json的帮助类。如下:

□ IBaseRepository是接口的基类,提供了所有接口的泛型实现
class="alt">using System; 
using System.Collections.Generic; 
using System.Linq.Expressions;
namespace Car.Test.DAL 
{ 
public interface IBaseRepository<T> where T : class,new()
    { 
        IEnumerable<T> LoadEntities(Expression<Func<T, bool>> whereLambda); 
        IEnumerable<T> LoadEntitiesByCache(Expression<Func<T, bool>> whereLambda); 
T AddEntity(T entity);
T UpdateEntity(T entity);
        void ClearCache(); 
        int SaveChanges(); 
}
}
□ 其它接口只需要继承该基类接口就可以
namespace Car.Test.DAL 
{ 
public interface ICarCategoryRepository : IBaseRepository<Car.Test.Model.CarCategory>
    { 
}
}
当然,如果Domain Model很多的话,这样写方便tt模版自动生成。
□ BaseRepository是Repository的基类,提供了所有Repository的泛型实现
提供了针对泛型的增删改查,还包括缓存查询,提交变化。
using System.Data; 
using System.Linq; 
using Car.Test.Cache; 
using Car.Test.Model; 
using System; 
using System.Collections.Generic; 
using System.Linq.Expressions;
namespace Car.Test.DAL 
{ 
public class BaseRepository<T> where T:class,new()
    { 
protected CarModelContainer DataContext { get; private set; }
        public ICacheProvider Cache { get; set; }
        public BaseRepository(ICacheProvider cacheProvider) 
        { 
this.DataContext = new CarModelContainer();
            this.Cache = cacheProvider; 
}
public BaseRepository():this(new DefaultCacheProvider()){}
public virtual IEnumerable<T> LoadEntities(Expression<Func<T, bool>> whereLambda)
        { 
IEnumerable<T> temp = DataContext.Set<T>().Where(whereLambda).AsEnumerable();
            return temp; 
}
public virtual IEnumerable<T> LoadEntitiesByCache(Expression<Func<T, bool>> whereLambda)
        { 
IEnumerable<T> entities = Cache.Get(typeof(T).ToString()) as IEnumerable<T>;
if (entities == null)
            { 
entities = DataContext.Set<T>().Where(whereLambda).AsEnumerable();
                if (entities.Any()) 
                { 
                    Cache.Set(typeof(T).ToString(),entities,3); 
}
}
            return entities; 
}
public virtual T AddEntity(T entity)
        { 
DataContext.Set<T>().Add(entity);
            return entity; 
}
public virtual T UpdateEntity(T entity)
        { 
if (entity != null)
            { 
DataContext.Set<T>().Attach(entity);
DataContext.Entry(entity).State = EntityState.Modified;
}
            return entity; 
}
public void ClearCache()
        { 
            Cache.InValidate(typeof(T).ToString()); 
}
public int SaveChanges()
        { 
            return DataContext.SaveChanges(); 
}
}
}
□ 其它Repository只需继承BaseRepository并实现各自的接口
namespace Car.Test.DAL 
{ 
public class CarCategoryRepository : BaseRepository<Car.Test.Model.CarCategory>,ICarCategoryRepository
    { 
}
}
□ 缓存接口
namespace Car.Test.Cache 
{ 
public interface ICacheProvider
    { 
object Get(string key);
void Set(string key, object data, int cacheTime);
bool IsSet(string key);
void InValidate(string key);
}
}
□ 缓存实现,需要引入System.Runtime.Caching
using System; 
using System.Runtime.Caching;
namespace Car.Test.Cache 
{ 
public class DefaultCacheProvider : ICacheProvider
    { 
        private ObjectCache Cache 
        { 
            get { return MemoryCache.Default; } 
}
public object Get(string key)
        { 
            return Cache[key]; 
}
public void Set(string key, object data, int cacheTime)
        { 
            CacheItemPolicy policy = new CacheItemPolicy(); 
policy.AbsoluteExpiration = DateTime.Now + TimeSpan.FromMinutes(cacheTime);
            Cache.Add(new CacheItem(key, data), policy); 
}
public bool IsSet(string key)
        { 
return Cache[key] != null;
}
public void InValidate(string key)
        { 
Cache.Remove(key);
}
}
}
使用AutoMapper映射领域模型和视图模型
□ 视图模型
using DataAnnotationsExtensions;
using System.ComponentModel.DataAnnotations;
namespace Car.Test.Portal.Models 
{ 
public class CarCategoryVm
    { 
public int ID { get; set; }
        [Display(Name = "类名")] 
        [Required(ErrorMessage = "必填")] 
        [StringLength(10, MinimumLength = 2,ErrorMessage = "长度2-10位")] 
public string Name { get; set; }
        [Display(Name = "前缀字母")] 
        [Required(ErrorMessage = "必填")] 
        [StringLength(1,ErrorMessage = "长度1位")] 
public string PreLetter { get; set; }
        [Display(Name = "所属父级")] 
        [Required(ErrorMessage = "必填")] 
public int ParentID { get; set; }
        public System.DateTime SubTime { get; set; }
        [Display(Name = "层级(根节点为0级)")] 
        [Required(ErrorMessage = "必填")] 
        [Min(1, ErrorMessage = "至少为1")] 
public int Level { get; set; }
        [Display(Name = "是否为页节点")] 
        [Required(ErrorMessage = "必填")] 
public bool IsLeaf { get; set; }
public short Status { get; set; }
public short DelFlag { get; set; }
}
}
引入DataAnnotationsExtensions组件,通过它可以设置最小值,关于DataAnnotationsExtensions的使用,在这里。
□ 继承AutoMapper的Profile类,创建领域模型→视图模型映射
using AutoMapper; 
using Car.Test.Model; 
using Car.Test.Portal.Models;
namespace Car.Test.Portal.Helpers.AutoMapper 
{ 
public class DomainToVmProfile : Profile
    { 
protected override void Configure()
        { 
Mapper.CreateMap<CarCategory, CarCategoryVm>();
}
}
}
□ 继承AutoMapper的Profile类,创建视图模型→领域模型映射
using AutoMapper; 
using Car.Test.Model; 
using Car.Test.Portal.Models;
namespace Car.Test.Portal.Helpers.AutoMapper 
{ 
public class VmToDomainProfile : Profile
    { 
protected override void Configure()
        { 
Mapper.CreateMap<CarCategoryVm, CarCategory>()
                .ForMember("Car", opt => opt.Ignore()); 
}
}
}
□ 初始化所有的Profile
using AutoMapper;
namespace Car.Test.Portal.Helpers.AutoMapper 
{ 
public static class AutoMapperConfiguration
    { 
public static void Configure()
        { 
Mapper.Initialize(x => x.AddProfile<VmToDomainProfile>());
Mapper.Initialize(x => x.AddProfile<DomainToVmProfile>());
}
}
}
□ 全局注册
protected void Application_Start()
        { 
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
            //全局配置映射 
AutoMapperConfiguration.Configure();
}
关于AutoMapper的使用,在这里。
下篇进入无限级分类的增删改查环节。