Web开发之ORM框架:(1)EF

作者:陆金龙    发表时间:2016-07-30 12:30   


1.EDM

1.1 EDM简介

EDM是实体数据关系映射的XML文件,主要有三部分构成CSDLConceptual schema definition language,SSDLstore schema definition language,MSLmapping 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();

    }