本系列將使用zTree來建立、編輯關於品牌、車系、車型的無限級分類,使用datagrid顯示,原始碼在github。先上最終效果:
datagrid顯示所有記錄、分頁,提供新增、修改、刪除按鈕,並提供簡單搜尋:
建立分類,彈出模態視窗,zTree顯示所有分類,點選勾選按鈕或節點,所選節點名稱顯示到文字框:
建立資料模型並生成到資料庫
→建立CarModel.edmx,建立模型,無限級分類的一切"神奇"從ParentID欄位開始。
→右鍵介面,選擇"根據模型生成資料庫"
→配置資料庫連線,執行sql語句......等等,最終生成如下檔案:
● App.config中的連線字串需要複製到MVC主站點的web.config中。
● CarModel.Context.cs中包含了繼承DbContext的上下文。
● CarModel.tt下包含了所有的領域模型Domain Models。
● CarModel.edmx.sql每次對映完執行裡面的sql語句,將把資料同步到資料庫。
架構設計
由於主要是體驗無限級分類的增刪改查,架構做得相對簡單一些。其它元件列舉的只是示意,實際只用到了快取和序列化json的幫助類。如下:
□ IBaseRepository是介面的基類,提供了所有介面的泛型實現
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的使用,在這裡。
下篇進入無限級分類的增刪改查環節。