zoukankan      html  css  js  c++  java
  • MVC项目实践,在三层架构下实现SportsStore-01,EF Code First建模、DAL层等

    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、搭建项目

    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

    2

    再在MySportsStore.WebUI下右键"引用",选择"管理NuGet程序包",下载最新版本的EF。

      3、使用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);
            }
        }
    }
    复制代码

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

    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实现增删改查

  • 相关阅读:
    收集起来先
    asp .net 页面回车触发button 按钮事件
    关于SQL 数据库表中的聚集索引和非聚集索引等
    WinForm换肤操作(用IrisSkin2.dll)
    生成Word文档的相关操作
    API自动化测试测试数据集
    API文档实践
    使用eolinker对API测试的响应结果进行断言
    API自动化定时测试
    接口测试之对数据进行RSA加解密
  • 原文地址:https://www.cnblogs.com/Alex80/p/10552382.html
Copyright © 2011-2022 走看看