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、搭建项目
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()]
public string Name { get; set; } [MaxLength()]
public string Description { get; set; }
public decimal Price { get; set; } [MaxLength()]
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();
如果返回的是IEnumerable<T>类型,当执行AsEnumerable()后,会把所有的数据加载到本地内存,然后取出前3条数据。
IQueryable<T> result = (from t in context.Table
order by t.Id
select c).Take();
如果返回的是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 - ))
.Take(pageSize);
}
else
{
entities = db.Set<T>().Where(whereLambada)
.OrderByDescending(orderBy)
.Skip(pageSize*(pageIndex - ))
.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 = ;
for (int i = ; i < entities.Count(); i++)
{
if(entities[i] == null) continue;
db.Set<T>().Add(entities[i]);
//每累计到10条记录就提交
if (i != && i% == )
{
result += db.SaveChanges();
}
} //可能还有不到10条的记录
if (entities.Count() > )
{
result += db.SaveChanges();
}
return result;
} //删除
public virtual int DeleteEntity(T entity)
{
db.Set<T>().Attach(entity);
db.Entry(entity).State = EntityState.Deleted;
return -;
} //批量删除
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 -;
} //更新
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 = ;
for (int i = ; i < entities.Count(); i++)
{
if(entities[i] == null) continue;
db.Set<T>().Attach(entities[i]);
db.Entry(entities[i]).State = EntityState.Modified;
if (i != && i% == )
{
result += db.SaveChanges();
}
} //可能还存在不到10条的记录
if (entities.Count() > )
{
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实现增删改查
MVC项目实践,在三层架构下实现SportsStore-01,EF Code First建模、DAL层等的更多相关文章
- MVC项目实践,在三层架构下实现SportsStore,从类图看三层架构
在"MVC项目实践,在三层架构下实现SportsStore-02,DbSession层.BLL层"一文的评论中,博友浪花一朵朵建议用类图来理解本项目的三层架构.于是就有了本篇: I ...
- MVC项目实践,在三层架构下实现SportsStore-02,DbSession层、BLL层
SportsStore是<精通ASP.NET MVC3框架(第三版)>中演示的MVC项目,在该项目中涵盖了MVC的众多方面,包括:使用DI容器.URL优化.导航.分页.购物车.订单.产品管 ...
- MVC项目实践,在三层架构下实现SportsStore-03,Ninject控制器工厂等
SportsStore是<精通ASP.NET MVC3框架(第三版)>中演示的MVC项目,在该项目中涵盖了MVC的众多方面,包括:使用DI容器.URL优化.导航.分页.购物车.订单.产品管 ...
- MVC项目实践,在三层架构下实现SportsStore-04,实现分页
SportsStore是<精通ASP.NET MVC3框架(第三版)>中演示的MVC项目,在该项目中涵盖了MVC的众多方面,包括:使用DI容器.URL优化.导航.分页.购物车.订单.产品管 ...
- MVC项目实践,在三层架构下实现SportsStore-05,实现导航
SportsStore是<精通ASP.NET MVC3框架(第三版)>中演示的MVC项目,在该项目中涵盖了MVC的众多方面,包括:使用DI容器.URL优化.导航.分页.购物车.订单.产品管 ...
- MVC项目实践,在三层架构下实现SportsStore-06,实现购物车
SportsStore是<精通ASP.NET MVC3框架(第三版)>中演示的MVC项目,在该项目中涵盖了MVC的众多方面,包括:使用DI容器.URL优化.导航.分页.购物车.订单.产品管 ...
- MVC项目实践,在三层架构下实现SportsStore-07,实现订单提交
SportsStore是<精通ASP.NET MVC3框架(第三版)>中演示的MVC项目,在该项目中涵盖了MVC的众多方面,包括:使用DI容器.URL优化.导航.分页.购物车.订单.产品管 ...
- MVC项目实践,在三层架构下实现SportsStore-08,部署到IIS服务器
SportsStore是<精通ASP.NET MVC3框架(第三版)>中演示的MVC项目,在该项目中涵盖了MVC的众多方面,包括:使用DI容器.URL优化.导航.分页.购物车.订单.产品管 ...
- MVC项目实践,在三层架构下实现SportsStore-09,ASP.NET MVC调用ASP.NET Web API的查询服务
ASP.NET Web API和WCF都体现了REST软件架构风格.在REST中,把一切数据视为资源,所以也是一种面向资源的架构风格.所有的资源都可以通过URI来唯一标识,通过对资源的HTTP操作(G ...
随机推荐
- JS - 超强大文本动画插件Textillate.js
http://www.yyyweb.com/demo/textillate/ Textillate.js AsimplepluginforCSS3textanimations.
- php phpeclipse + xampp 配置安装过程
就想test是否能配置成功,下载apache,php5.3,安装开始 apache的安装,一路next,遇到Server Information,随便填写即可,安装路径自己可选 php的安装,将下载的 ...
- 活跃天数计算用户等级模仿QQ的升级方式
QQ等级的算法:设当前等级为N,达到当前等级最少需要的活跃天数为D,当前活跃天数为Dc,升级剩余天数为Dr,则: 从而推出: 好了,引述完成,懒得写字了,贴出代码: 复制内容到剪贴板 代码: < ...
- 《Lucene实战(第2版)》 配书代码在IDEA下的编译方法
参考: hankcs http://www.hankcs.com/program/java/lucene-combat-2nd-edition-book-with-code-compiled-unde ...
- LeetCode | Regular Expression Matching
Regular Expression Matching Implement regular expression matching with support for '.' and '*'. '.' ...
- BZOJ 2120 数颜色(带修改的莫队)
2120: 数颜色 Time Limit: 6 Sec Memory Limit: 259 MB Submit: 3478 Solved: 1342 [Submit][Status][Discus ...
- 点击li标记中的<a>标记改变li背景图片怎样实现
<div class="nav"><ul><li id="li1" class="dianji" onclic ...
- Yii源码阅读笔记(十二)
Action类,控制器中方法的基类: namespace yii\base; use Yii; /** * Action is the base class for all controller ac ...
- SSH 基础
什么是SSH? 传统的网络服务程序,如:ftp.pop和telnet在本质上都是不安全的,因为它们在网络上用明文传送口令和数据,别有用心的人非常容易就可以截获这些口令和数据.而且,这些服务程序的安全验 ...
- Mysql Cluster配置基本篇
一.下载mysql-cluster 从mysql官网下载对应的mysql-cluster 二.配置管理节点 1.解压mysql-cluster,找到解压文件中的ndb_mgm和ndb_mgmd 2.复 ...