MVC項目實踐,在三層架構下實現SportsStore-01,EF Code First建模、DAL層等

SportsStore是《精通ASP.NET MVC3框架(第三版)》中演示的MVC項目,在該項目中涵蓋了MVC的衆多方面,包括:使用DI容器、URL優化、導航、分頁、購物車、訂單、產品管理、圖像上傳......是不錯的MVC實踐項目,但該項目不是放在多層框架下開發的,離真實項目還有一段距離。本系列將嘗試在多層框架下實現SportsStore項目,並用本身的方式實現一些功能。html

 

本篇爲系列第一篇,包括:git

■ 一、搭建項目
■ 二、卸載Entity Framework組件,並安裝最新版本
■ 三、使用EF Code First建立領域模型和EF上下文
■ 四、三層架構設計
    □ 4.1 建立DAL層
        ※ 4.1.1 MySportsStore.IDAL詳解
        ※ 4.1.2 MySportsStore.DAL詳解github

 

  一、搭建項目數據庫

1

MySportsStore.Model:類庫,領域模型、Entity Framework上下文所在層
MySportsStore.IDAL:類庫,數據接口層
MySportsStore.DAL:類庫,數據層
MySportsStore.IBLL:類庫,業務邏輯接口層
MySportsStore.BLL:類庫,業務邏輯實現層
MySportsStore.Common:類庫,幫助層,存放各類幫助類,好比加密幫助類、緩存幫助類、JSON序列化類等
MySportsStore.WebUI:MVC4項目,並設置爲"啓動項目"
MySportsStore.Tests:類庫,測試層緩存

 

  二、卸載Entity Framework組件,並安裝最新版本服務器

因爲是在MVC4.0下建立的MySportsStore.WebUI,默認的EF版本是4.0版本,而在其它層,好比MySportsStore.Mode層,也會用到EF,而經過NuGet下載到的是最新版本,這樣很容易形成版本不一致。因此,先把MySportsStore.WebUI中的EF組件卸載掉,統一安裝最新版本的EF。架構

 

打開:工具--程序包管理器--程序包管理器控制檯,默認項目選擇"MySportsStore.WebUI",在控制檯輸入以下命令:app

Uninstall-Package EntityFramework –Force框架

2

再在MySportsStore.WebUI下右鍵"引用",選擇"管理NuGet程序包",下載最新版本的EF。ide

 

  三、使用EF Code First建立領域模型和EF上下文

MySportsStore.Model下右鍵"引用"添加程序集:System.ComponentModel.DataAnnotations

3

添加領域模型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);
        }
    }
}
複製代碼

 

  四、三層架構設計

三層架構

→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」系列包括:

MVC項目實踐,在三層架構下實現SportsStore,從類圖看三層架構

MVC項目實踐,在三層架構下實現SportsStore-01,EF Code First建模、DAL層等

MVC項目實踐,在三層架構下實現SportsStore-02,DbSession層、BLL層

MVC項目實踐,在三層架構下實現SportsStore-03,Ninject控制器工廠等

MVC項目實踐,在三層架構下實現SportsStore-04,實現分頁

MVC項目實踐,在三層架構下實現SportsStore-05,實現導航

MVC項目實踐,在三層架構下實現SportsStore-06,實現購物車

MVC項目實踐,在三層架構下實現SportsStore-07,實現訂單提交

MVC項目實踐,在三層架構下實現SportsStore-08,部署到IIS服務器

MVC項目實踐,在三層架構下實現SportsStore-09,ASP.NET MVC調用ASP.NET Web API的查詢服務

MVC項目實踐,在三層架構下實現SportsStore-10,鏈接字符串的加密和解密

MVC項目實踐,在三層架構下實現SportsStore-11,使用Knockout實現增刪改查

相關文章
相關標籤/搜索