SportsStore是《精通ASP.NET MVC3框架(第三版)》中演示的MVC專案,在該專案中涵蓋了MVC的眾多方面,包括:使用DI容器、URL優化、導航、分頁、購物車、訂單、產品管理、影象上傳......是不錯的MVC實踐專案,但該專案不是放在多層框架下開發的,離真實專案還有一段距離。本系列將嘗試在多層框架下實現SportsStore專案,並用自己的方式實現一些功能。
本篇為系列第一篇,包括:
■ 1、搭建專案
■ 2、解除安裝Entity Framework元件,並安裝最新版本
■ 3、使用EF Code First建立領域模型和EF上下文
■ 4、三層架構設計
□ 4.1 建立DAL層
※ 4.1.1 MySportsStore.IDAL詳解
※ 4.1.2 MySportsStore.DAL詳解
1、搭建專案
MySportsStore.Model:類庫,領域模型、Entity Framework上下文所在層
MySportsStore.IDAL:類庫,資料介面層
MySportsStore.DAL:類庫,資料層
MySportsStore.IBLL:類庫,業務邏輯介面層
MySportsStore.BLL:類庫,業務邏輯實現層
MySportsStore.Common:類庫,幫助層,存放各種幫助類,比如加密幫助類、快取幫助類、JSON序列化類等
MySportsStore.WebUI:MVC4專案,並設定為"啟動專案"
MySportsStore.Tests:類庫,測試層
2、解除安裝Entity Framework元件,並安裝最新版本
由於是在MVC4.0下建立的MySportsStore.WebUI,預設的EF版本是4.0版本,而在其它層,比如MySportsStore.Mode層,也會用到EF,而通過NuGet下載到的是最新版本,這樣很容易造成版本不一致。所以,先把MySportsStore.WebUI中的EF元件解除安裝掉,統一安裝最新版本的EF。
開啟:工具--程式包管理器--程式包管理器控制檯,預設專案選擇"MySportsStore.WebUI",在控制檯輸入如下命令:
Uninstall-Package EntityFramework –Force
再在MySportsStore.WebUI下右鍵"引用",選擇"管理NuGet程式包",下載最新版本的EF。
3、使用EF Code First建立領域模型和EF上下文
MySportsStore.Model下右鍵"引用"新增程式集:System.ComponentModel.DataAnnotations
新增領域模型Product:
using System.ComponentModel.DataAnnotations; namespace MySportsStore.Model { public class Product { [Key] public int Id { get; set; } [MaxLength(100)] public string Name { get; set; } [MaxLength(500)] public string Description { get; set; } public decimal Price { get; set; } [MaxLength(50)] public string Category { get; set; } } }
下載最新版本的EF。安裝完後,在MySportsStore.Model下會多出一個App.config檔案。
建立EF上下文類:
using System.Data.Entity; namespace MySportsStore.Model { public class EfDbContext : DbContext { public EfDbContext() : base("conn") { Database.SetInitializer(new EfDbInitializer()); //Database.SetInitializer(new CreateDatabaseIfNotExists<EfDbContext>()); //Database.SetInitializer(new DropCreateDatabaseIfModelChanges<EfDbContext>()); //Database.SetInitializer(new DropCreateDatabaseAlways<EfDbContext>()); } public DbSet<Product> Products { get; set; } } }
建立資料庫的種子資料:
using System.Collections.Generic; using System.Data.Entity; namespace MySportsStore.Model { public class EfDbInitializer : CreateDatabaseIfNotExists<EfDbContext> { protected override void Seed(EfDbContext context) { IList<Product> defaultProducts = new List<Product>(); defaultProducts.Add(new Product(){Name = "Kayak", Description = "A boat for one person", Category = "Watersports", Price = 275.00M}); defaultProducts.Add(new Product() { Name = "Lifejacket", Description = "Protective and fashionable", Category = "Watersports", Price = 48.95M }); defaultProducts.Add(new Product() { Name = "Soccer ball", Description = "FIFA-approved size and weight", Category = "Soccer", Price = 19.50M }); defaultProducts.Add(new Product() { Name = "Corneer flags", Description = "Giving your playing field that professional touch", Category = "Soccer", Price = 34.95M }); defaultProducts.Add(new Product() { Name = "Stadium", Description = "Flat-packed 35,000-seat stadium", Category = "Soccer", Price = 79500.00M }); defaultProducts.Add(new Product() { Name = "Thinking cap", Description = "Improve your brain efficiency by 75%", Category = "Chess", Price = 16.00M }); defaultProducts.Add(new Product() { Name = "Unsteady Chair", Description = "Secretly give your opponent a disadvantage", Category = "Chess", Price = 29.95M }); defaultProducts.Add(new Product() { Name = "Human Chess", Description = "A fun game for the whole family", Category = "Chess", Price = 75.00M }); defaultProducts.Add(new Product() { Name = "Bling-bling King", Description = "Gold-plated, diamond-studded King", Category = "Chess", Price = 1200.00M }); foreach (Product p in defaultProducts) { context.Products.Add(p); } base.Seed(context); } } }
4、三層架構設計
→DAL層:資料庫訪問層,負責和資料庫互動
● IBaseRepository:是所有IXXXRepository介面的基類,提供了各個IXXXRepository泛型基介面的實現,避免了各個IXXXRepository介面的程式碼重複
● IProductRepository:實現IBaseRepository介面
● BaseRepository:是所有XXXRepository的基類,提供各個XXXRepository的泛型基類實現,避免了各個XXXRepository的程式碼重複
● ProductRepository:實現IProductRepository介面,派生於BaseRepository
→DbSession層:資料庫訪問層的統一入口
● 從中可以拿到各個IXXXRepository介面型別
● 在這裡儲存EF的所有變化
● 在這裡執行SQL語句
→BLL層:業務邏輯層,藉助資料庫訪問層統一入口執行業務邏輯
● IBaseService:是所有IXXXService的基類,提供了各個IXXXService的泛型基介面的實現,避免了各個IXXXService介面的程式碼重複
● IProductService:實現IBaseService介面
● BaseService:是所有XXXService的基類,提供了各個XXXService的泛型基類實現,避免了各個XXXService的程式碼重複
● ProductService:實現IProductService介面,派生於BaseService
→UI層:控制器、檢視、檢視模型
→Domain Model領域模型:與資料庫互動相關的模型
→Common:一些幫助類和幫助方法,比如加密、快取、JSON序列化等
→DTO:負責把領域模型轉換成檢視模型,比如使用AutoMappeer自動對映
4.1 建立DAL層
4.1.1 MySportsStore.IDAL詳解
→IBaseRepository介面
所有的資料介面層的方法基本上是一樣的,包括查詢、分頁查詢、新增、批量新增、更新、批量更新、刪除、批量刪除等。所以,有必要針對所有的資料介面層提煉出一個泛型資料介面基類:
using System; using System.Linq; using System.Linq.Expressions; namespace MySportsStore.IDAL { public interface IBaseRepository<T> where T : class,new() { //查詢 IQueryable<T> LoadEntities(Expression<Func<T, bool>> whereLambda); //分頁查詢 IQueryable<T> LoadPageEntities<S>( Expression<Func<T, bool>> whereLambad, Expression<Func<T, S>> orderBy, int pageSize, int pageIndex, out int totalCount, bool isASC); //查詢總數量 int Count(Expression<Func<T, bool>> predicate); //新增 T AddEntity(T entity); //批量新增 int AddEntities(params T[] entities); //刪除 int DeleteEntity(T entity); //批量刪除 int DeleteBy(Expression<Func<T, bool>> whereLambda); //更新 T UpdateEntity(T entity); //批量更新 int UpdateEntities(params T[] entities); } }
查詢返回型別為什麼用IQueryable<T>,而不用IEnumerable<T>型別?
IQueryable介面實現IEnumerable介面,IQueryable介面擁有IEnumerable的所有功能。
兩者的區別可以從以下例子看出端倪:
IEnumerable<T> result = (from t in context.Table order by t.Id select c).AsEnumerable().Take(3);
如果返回的是IEnumerable<T>型別,當執行AsEnumerable()後,會把所有的資料載入到本地記憶體,然後取出前3條資料。
IQueryable<T> result = (from t in context.Table order by t.Id select c).Take(3);
如果返回的是IQueryable<T>型別,只是在資料庫端取出前3條資料。
在這裡,為了減少頻寬的消耗,選擇返回IQuerayble介面型別,當然如果記憶體足夠,需要更快的響應速度,也可以選擇返回IEnumerable介面型別。
為什麼選擇Expression<Func<T, bool>>型別引數而不是Func<T, bool>?
從最終效果來講,兩者並沒有區別,都是委託型別引數。兩者的區別在於:Func<T, bool>是靜態的多播委託,Expression<Func<T, bool>>中,Expression表示式樹把靜態委託看作是它的資料型別,在編譯前使用Expression的靜態方法把Func<T, bool>賦值給表示式樹的各個屬性,在執行時編譯的時候,內部呼叫compile()方法把表示式樹轉換成靜態委託Func<T, bool>。
簡而言之,使用Expression<Func<T, bool>>有更強的靈活性,最終也會轉換成委託型別。
→IProductRepository介面
所有的資料介面都用引用MySportsStore.Model的領域模型,所以需要引用MySportsStore.Model。
針對領域模型Product,其對應的倉儲介面為:
using MySportsStore.Model; namespace MySportsStore.IDAL { public interface IProductRepository : IBaseRepository<Product> { } }
使用資料介面的基類介面的好處顯而易見。
→IDbContextFactory介面,當前EF上下文的抽象工廠
在BaseRepository中會用到EF上下文的例項,我們藉助"抽象工廠"生產DbContext的例項。
從NuGet安裝最新版本的EF。
using System.Data.Entity; namespace MySportsStore.IDAL { public interface IDbContextFactory { //獲取當前上下文的唯一例項 DbContext GetCurrentThreadInstance(); } }
4.1.2 MySportsStore.DAL詳解
→新增引用
● 新增對最新版EF的引用
● 新增對MySportsStore.IDAL的引用
● 新增對MySportsStore.Model的引用
→DbContextFactory,實現抽象工廠IDbContextFactory介面,用來生產EF上下文例項
using System.Data.Entity; using System.Runtime.Remoting.Messaging; using MySportsStore.IDAL; using MySportsStore.Model; namespace MySportsStore.DAL { public class DbContextFactory : IDbContextFactory { //獲取當前EF上下文的唯一例項 public System.Data.Entity.DbContext GetCurrentThreadInstance() { DbContext obj = CallContext.GetData(typeof (EfDbContext).FullName) as DbContext; if (obj == null) { obj = new EfDbContext(); CallContext.SetData(typeof(EfDbContext).FullName, obj); } return obj; } } }
通過CallContext執行緒槽可以獲取到當前執行緒內的唯一EF上下文例項。
→BaseRepository,所有XXXRepository的泛型基類實現
using System; using System.Data.Entity; using System.Linq; using System.Linq.Expressions; using MySportsStore.IDAL; namespace MySportsStore.DAL { public class BaseRepository<T> : IDisposable where T : class, new() { private DbContext db; public BaseRepository() { IDbContextFactory dbFactory = new DbContextFactory(); db = dbFactory.GetCurrentThreadInstance(); } //查詢 public virtual IQueryable<T> LoadEntities(Expression<Func<T, bool>> whereLambda) { IQueryable<T> result = db.Set<T>().Where(whereLambda); return result; } //分頁查詢 public virtual IQueryable<T> LoadPageEntities<S>( Expression<Func<T, bool>> whereLambada, Expression<Func<T, S>> orderBy, int pageSize, int pageIndex, out int totalCount, bool isASC) { totalCount = db.Set<T>().Where(whereLambada).Count(); IQueryable<T> entities = null; if (isASC) { entities = db.Set<T>().Where(whereLambada) .OrderBy(orderBy) .Skip(pageSize*(pageIndex - 1)) .Take(pageSize); } else { entities = db.Set<T>().Where(whereLambada) .OrderByDescending(orderBy) .Skip(pageSize*(pageIndex - 1)) .Take(pageSize); } return entities; } //查詢總數量 public virtual int Count(Expression<Func<T, bool>> predicate) { return db.Set<T>().Where(predicate).Count(); } //新增 public virtual T AddEntity(T entity) { db.Set<T>().Add(entity); return entity; } //批量新增 每10條記錄提交一次 public virtual int AddEntities(params T[] entities) { int result = 0; for (int i = 0; i < entities.Count(); i++) { if(entities[i] == null) continue; db.Set<T>().Add(entities[i]); //每累計到10條記錄就提交 if (i != 0 && i%10 == 0) { result += db.SaveChanges(); } } //可能還有不到10條的記錄 if (entities.Count() > 0) { result += db.SaveChanges(); } return result; } //刪除 public virtual int DeleteEntity(T entity) { db.Set<T>().Attach(entity); db.Entry(entity).State = EntityState.Deleted; return -1; } //批量刪除 public virtual int DeleteBy(Expression<Func<T, bool>> whereLambda) { var entitiesToDelete = db.Set<T>().Where(whereLambda); foreach (var item in entitiesToDelete) { db.Entry(item).State = EntityState.Deleted; } return -1; } //更新 public virtual T UpdateEntity(T entity) { if (entity != null) { db.Set<T>().Attach(entity); db.Entry(entity).State = EntityState.Modified; } return entity; } //批量更新 每10條記錄更新一次 public virtual int UpdateEntities(params T[] entities) { int result = 0; for (int i = 0; i < entities.Count(); i++) { if(entities[i] == null) continue; db.Set<T>().Attach(entities[i]); db.Entry(entities[i]).State = EntityState.Modified; if (i != 0 && i%10 == 0) { result += db.SaveChanges(); } } //可能還存在不到10條的記錄 if (entities.Count() > 0) { result += db.SaveChanges(); } return result; } //釋放EF上下文 public void Dispose() { db.Dispose(); } } }
為什麼BaseRepository沒有實現IBaseRepository介面?
--的確,BaseRepository的絕大多數方法是IBaseRepository介面的實現,但BaseRepository是所有XXXRepository的基類泛型實現,它的存在是為了避免所有XXXRepository中重複程式碼。
為什麼要實現IDisposable介面?
--的確,DbContext有預設的垃圾回收機制,但通過BaseRepository實現IDisposable介面,可以在不用EF上下文的時候手動回收,時效性更強。
→ProductRepository
using MySportsStore.IDAL; using MySportsStore.Model; namespace MySportsStore.DAL { public class ProductRepository : BaseRepository<Product>, IProductRepository { } }
ProductRepository派生於BaseRepository<Product>完成方法的實現。
ProductRepository的行為受IProductRepository約束。
原始碼在這裡。
“MVC專案實踐,在三層架構下實現SportsStore”系列包括: