Web开发之ORM框架:(1)EF
1.EDM
1.1 EDM简介
EDM是实体数据关系映射的XML文件,主要有三部分构成CSDL(Conceptual schema definition language),SSDL(store schema definition language),MSL(mapping specification language)。CSDL表明的是实体数据模型结构,SSDL表示对应的数据存储的架构,CSDL实体与SSDL数据结构的关系通过MSL映射实现。
EDM是通过ADO.NET 实体数据模型生成的,生成EDM文件的方式有两种,一种基于是数据库(DataBase First),一种是创建空EDM模型(Model First)。
一个包含区域和类型两个表的EF实体模型,打开方式选用记事本,打开Model.edmx文件,可以看到如下代码:
<?xml version="1.0" encoding="utf-8"?>
<edmx:Edmx Version="2.0" xmlns:edmx="http://schemas.microsoft.com/ado/2008/10/edmx">
<!-- EF Runtime content -->
<edmx:Runtime>
<!-- SSDL content -->
<edmx:StorageModels>
<Schema Namespace="KL.Store" Alias="Self" Provider="System.Data.SqlClient" ProviderManifestToken="2008" xmlns:store="http://schemas.microsoft.com/ado/2007/12/edm/EntityStoreSchemaGenerator" xmlns="http://schemas.microsoft.com/ado/2009/02/edm/ssdl">
<EntityContainer Name="KLStoreContainer">
<EntitySet Name="RegionInfo" EntityType="KL.Store.RegionInfo" store:Type="Tables" Schema="dbo" />
<EntitySet Name="RegionType" EntityType="KL.Store.RegionType" store:Type="Tables" Schema="dbo" />
</EntityContainer>
<EntityType Name="RegionInfo">
<Key>
<PropertyRef Name="RegionId" />
</Key>
<Property Name="P_RegionId" Type="varchar" MaxLength="64" />
<Property Name="RegionId" Type="varchar" Nullable="false" MaxLength="32" />
<Property Name="RegionName" Type="varchar" Nullable="false" MaxLength="64" />
</EntityType>
<EntityType Name="RegionType">
<Key>
<PropertyRef Name="RegionTypeId" />
</Key>
<Property Name="RegionTypeId" Type="int" Nullable="false" StoreGeneratedPattern="Identity" />
<Property Name="RegionTypeDes" Type="varchar" MaxLength="128" />
</EntityType>
</Schema>
</edmx:StorageModels>
<!-- CSDL content -->
<edmx:ConceptualModels>
<Schema Namespace="KL" Alias="Self" xmlns:annotation="http://schemas.microsoft.com/ado/2009/02/edm/annotation" xmlns="http://schemas.microsoft.com/ado/2008/09/edm">
<EntityContainer Name="KLEntities" annotation:LazyLoadingEnabled="true">
<EntitySet Name="RegionInfo" EntityType="KL.RegionInfo" />
<EntitySet Name="RegionType" EntityType="KL.RegionType" />
</EntityContainer>
<EntityType Name="RegionInfo">
<Key>
<PropertyRef Name="RegionId" />
</Key>
<Property Name="P_RegionId" Type="String" MaxLength="64" Unicode="false" FixedLength="false" />
<Property Name="RegionId" Type="String" Nullable="false" MaxLength="32" Unicode="false" FixedLength="false" />
<Property Name="RegionName" Type="String" Nullable="false" MaxLength="64" Unicode="false" FixedLength="false" />
</EntityType>
<EntityType Name="RegionType">
<Key>
<PropertyRef Name="RegionTypeId" />
</Key>
<Property Name="RegionTypeId" Type="Int32" Nullable="false" annotation:StoreGeneratedPattern="Identity" />
<Property Name="RegionTypeDes" Type="String" MaxLength="128" Unicode="false" FixedLength="false" />
</EntityType>
</Schema>
</edmx:ConceptualModels>
<!-- C-S mapping content -->
<edmx:Mappings>
<Mapping Space="C-S" xmlns="http://schemas.microsoft.com/ado/2008/09/mapping/cs">
<EntityContainerMapping StorageEntityContainer="KLStoreContainer" CdmEntityContainer="KLEntities">
<EntitySetMapping Name="RegionInfo">
<EntityTypeMapping TypeName="KL.RegionInfo">
<MappingFragment StoreEntitySet="RegionInfo">
<ScalarProperty Name="RegionId" ColumnName="RegionId" />
<ScalarProperty Name="ProvinceId" ColumnName="ProvinceId" />
<ScalarProperty Name="Caption" ColumnName="Caption" />
<ScalarProperty Name="P_RegionId" ColumnName="P_RegionId" />
<ScalarProperty Name="REGION_LEVEL" ColumnName="REGION_LEVEL" />
</MappingFragment>
</EntityTypeMapping>
</EntitySetMapping>
<EntitySetMapping Name="RegionType">
<EntityTypeMapping TypeName="KL.RegionType">
<MappingFragment StoreEntitySet="RegionType">
<ScalarProperty Name="RegionTypeId" ColumnName="RegionTypeId" />
<ScalarProperty Name="RegionTypeDes" ColumnName="RegionTypeDes" />
<ScalarProperty Name="IsValid" ColumnName="IsValid" />
</MappingFragment>
</EntityTypeMapping>
</EntitySetMapping>
</EntityContainerMapping>
</Mapping>
</edmx:Mappings>
</edmx:Runtime>
<!-- EF Designer content (DO NOT EDIT MANUALLY BELOW HERE) -->
<Designer xmlns="http://schemas.microsoft.com/ado/2008/10/edmx">
......
</Designer>
</edmx:Edmx>
1.2 EDM生成方式
ADO.NET 实体数据模型 最初EF的方式,实体模型EntityObject与ObjectContext耦合在一起(如1.1节中的示例代码,都在一个文件),不适合分层使用。
ADO.NET 自跟踪实体生成器 分离生成基于POCO的SelfTrackingEnityObject模型和ObjectContext (这种方试即使设置了延迟加载也无法加载关联导航属性,要在使用时手动加载)
ADO.NET DbContext Generator 分离生成纯POCO模型和轻型DbContext。DbContext较之ObjectContext比较简洁,并且POCO可以充分利用。
1.3 EF框架的划分的模式
DataBase First 传统的表驱动方式创建edm,然后通过edm生成模型和数据层代码。除生成实体模型和自跟踪实现模型,还支持生成纯POCO模型和轻型DbContext。
Model First 先创建EDM模型,再生成DDL数据库脚本和模型和数据层代码。除生成实体模型和自跟踪实现模型,支持生成纯POCO模型和轻型DbContext。
Code First 手动创建POCO模型,数据层DbContext及映射关系,通过Database.SetInitializer生成数据库,这种方式较灵活,但是代码工作较多。
理解POCO(Plain Old CLR Object / POCO):Entity Framework 4.0为实体提供了简单传统CLR对象支持。实体对象可以独立于EF存在,由此EF更好地支持了测试驱动开发(test-driven development)和 领域驱动设计(domain-driven design)。
基于Model First方式 通过ADO.NET DbContext Generator生成基于Code First方式代码 ,这种方式基本上和Nhibernate是一致的,有着广泛的项目基础。
Model First方式 主要解决构建模型和EDM映射文件工作
ADO.NET DbContext Generator 基于EDM文件生成POCO模型,DbContext代码以及DDL数据库脚本。因为Code First你要自己实现POCO,DbContext的代码,这部分工作如果不借助工具实现代码量还是很大的。
2.EF查询框架
2.1 EF的查询方式
2.1.1 原始SQL查询
在EF 4.1 新增加的DbContext 除了支持LINQ与Lambda查询外,新增了支持原始SQL查询,但是不支持ESQL与ObjectQuery查询。
CMSDBEntities context = new CMSDBEntities (); DbSet set = context.Set();List list = set.SqlQuery("select *from Article where UId='7'").ToList(); List listAll = context.Database.SqlQuery ("select *from Article").ToList();
原始SQL查询,灵活方便,结合DbContext的泛型处理,可以将查询的数据集映射成对象集合。
2.1.2 LINQ To Entity
简洁方便,在内存中解决这些数据的合并筛选,效率要远高于SQL。
CMSDBEntities context = new CMSDBEntities (); DbSet set = context.Set();//条件查询 var result = from a in set.ToList() where a.UId == 7 select u; //查询部分列 var result = from a in set.ToList() select new { UId = a.UId ,Name=a.Name,Content=a.Content}; //分组 var resultGroup = from a in set.ToList() group a by a.UId into g select g; //分组求和 var resultGroup = from a in set.ToList() group a by a.UId into g select new{ //对每一组结果查询得到一个对象 Id=g.key.UId, Total=g.sum(p=>p.readCount) }; //扩展 对datatable的linq查询实现多字段分组 var result =from c in t.AsEnumerable() group c by new { name= c.Field ("name"), spec= c.Field ("spec") } into s
select new { //对每一组结果查询得到一个对象 name= s.Select(p => p.Field ("name")).First(), spec= s.Select(p => p.Field ("spec")).First(),
count= s.Sum(p => Convert.ToInt32(p.Field ("count"))) };
2.1.3 Lambda
var list = set.Where(o => o.UId == 3);
var listGroup = set.GroupBy(o => o.UId);
2.2 EF的CRUD操作
2.2.1 添加实体
//第一步:创建 EF访问数据库的上下文对象
StudentEntities db = new StudentEntities ();
//第二步:操作实体,做插入操作:先创建一个要插入数据的实体,并赋值
S s = new S();
s.SNO = 111;
s.SNAME = "test";
//第三步:告诉上下文要对实体做什么操作
db .S.AddObject(s);
//第四步:命令 上下文保存所有实体的变化到数据库
db .SaveChanges();
2.2.2 修改实体
S s1 = new S();
s1.SNO = 10;
s1.SNAME = "testModify";
//让ef上下文 跟踪当前要修改的实体
db.S.Attach(s1);
//告诉EF上下文 对此实体做一个修改操作
db.ObjectStateManager.ChangeObjectState(s1,EntityState.Modified);
//命令上下文 保存实体的变化到数据库中去。
db.SaveChanges();
2.2.3 删除实体
//删除实体
S s1 = new S();
s1.SNO = 10;
//让ef上下文 跟踪当前删除的实体
db.S.Attach(s1);
//告诉EF上下文 我要对此实体做一个修改操作
db.ObjectStateManager.ChangeObjectState(s1, EntityState.Deleted);
//命令上下文 保存实体的变化到数据库中去。
db.SaveChanges();
2.2.4 简单查询
foreach (S s in db.S)
{
Console.WriteLine(s.SNO s.SNAME);
}
2.2.5 修改查询的实体
var result = (from c in db.Cutomter
where c.ID == 2
select c).FirstOrDefault();
if(result!= null)
{
//如果是查询出来的实体,不再进行附加操作。默认就是被追踪管理的
result.CusName = "查询出来直接修改的";
db.SaveChanges();
}
2.2.6 Linq查询
Linq To Entity Framework(语句的顺序就是其执行的顺序)
IQueryable<S> result = from s in db.S
where s.SNO > 1 && s.SNO <10
select s;
2.2.7 Lambda 查询
var result = db.S.Where<S>(c => c.ID ==2).FirstOrDefault();
2.2.8分页 查询
第b页 a*(b-1)+1 -- a*b (设a=10,b=4)
//命令式编程 离线型的集合
var pageData = db.UserInfo.Where<UserInfo>(u => u.ID > 0)
//.OrderBy<UserInfo,int>(u=>u.ID)//按照ID来进行升序排序
.OrderByDescending<UserInfo, int>(u => u.ID)
//按照降序排序。var 语法糖,orderby泛型约束可以不用写,
//最后编译成中间代码的时候,自动补充上类型,所以orderby 排序的方法泛型约束可以不写
.Skip<UserInfo>(30) //越过多少条数据
.Take<UserInfo>(10) //取
.ToList();//list是内存型的集合
//命令式的编程 Ruby 命令式,开发速度非常快。Ruby on Rails
分页注意事项:
内存中分页:效率低,会先把数据从数据库中查出来,然后才分页.:
query.ToList().Skip((pageInfo.PageIndex-1)*pageInfo.PageSize).Take(pageInfo.PageSize);
数据库中分页,正确的写法应如下:
query.Skip((pageInfo.PageIndex -1) * pageInfo.PageSize).Take(pageInfo.PageSize).ToList();
3.EF的延迟加载
3.1 两种延迟加载机制
尤其第二种,取代表连接查询方式,解决大数据量的表连接压力问题。
var result = from c in db.Cutomter
where c.ID > 0
select c;
//第一种延迟加载机制:当用到查询的结果时,才会去数据库查询
foreach (var cutomter in result)
{
Console.WriteLine(cutomter.ID);
}
//第二种延迟加载机制:导航属性使用时候,检测导航属性有没有值,如没有值自动到数据库中查询数据
var customer = (from c in db.Cutomter
where c.ID == 2
select c).FirstOrDefault();//取集合的第一条数据
if (customer != null)
{
foreach (var order in customer.Order)//按常理的话,这时候应该报null异常
{
Console.WriteLine(order.ID);
}
}
3.2延迟加载机制应用
//延迟加载机制:
var allCustomers = from c in db.Cutomter.Include("Order")//连接查询
select c;
foreach (var cus in allCustomers)//查询一次 20个顾客
{
foreach (var order in cus.Order)//查询一次 100订单 21次
{
Console.WriteLine(order.ID + cus.ID + cus.CusName);
}
}
foreach (var cus in allCustomers)//又一次使用这个集合,这时候再去查询数据库
{
foreach (var order in cus.Order)//查询一次
{
Console.WriteLine(order.ID + cus.ID + cus.CusName);
}
}
3.3 EF查询在大数据量时的坑
EF是直接从表中取出所有的数据到内存中,然后在内存中根据条件再做一次检索。显然,这样的方式在数据量小的时候并不太影响性能。但是在大数据量,高并发访问的时候,这种方式简直就是噩梦,如以下代码。
public IQueryable<T> LoadEntities(Func<T, bool> whereLambda)
{
//return db.CreateObjectSet<T>().Where<T>(whereLambda).AsQueryable();
return db.Set<T>().Where<T>(whereLambda).AsQueryable();
}
System.Linq.Expressions命名空间的Expression可以帮我们解决以上问题。
Expression<TDelegate>类可以以表达式目录树的形式将强类型 lambda 表达式表示为数据结构,从而在编译阶段产生我们想要的SQL代码。
将以上代码中的Func<T, bool>改为Expression<Func<T, bool> 即可解决该问题。
/// <summary>
/// Expression<TDelegate>类可以以表达式目录树的形式将强类型 lambda 表达式表示为数据结构,在编译阶段产生我们想要的SQL代码
/// </summary>
/// <param name="whereLambda"></param>
/// <returns></returns>
public IQueryable<T> LoadEntities(Expression<Func<T, bool>> whereLambda)
{
return db.Set<T>().Where<T>(whereLambda).AsQueryable();
}