ASP.NET MVC01

本文总阅读量:
  1. 1. Entity Framework
    1. 1.1. 前期准备
    2. 1.2. 介绍
  2. 2. database first
  3. 3. EF增删改
  4. 4. EF生成流程
  5. 5. model first
  6. 6. 包含外键的数据插入
  7. 7. Linq查询
  8. 8. IQueryable接口即linq实现机制
  9. 9. 延迟加载
  10. 10. 分页

Entity Framework

前期准备

  • 自动属性
  • var
  • 对象、集合初始化器
  • 匿名类
  • 拓展方法
  • lambda表达式

介绍

托管于非托管代码

C,c++编译 –》dll动态链接库 –》 二进制机器码,直接交给操作系统运行

C# 编译dll(程序集) –》 IL –》CLR 即时编译JIT –》 二进制机器码

介绍EF

ADO NET Entity Framework是以ADO NET为基础所发展出来的对象关系对应O/R Mapping(ORM)解决方案

什么是0 / R Mapping

广义上,ORM指的是面向对象的对象模型和关系型数据库的数据结构之间的相互转换。

狭义上,ORM可以被认为是,基于关系型数据厍的数据存储,实现一个虚拟的面向对象的数据访问接口。理想情况下,基于这样一个面向对象的接口,持久化一个00对象应该不需要要了解任何关系型数据库存储数据的实现细节。

O(object 面向对象的对象模型)M(mapping xml文件)R(related 关系表)

database first

添加新项–》数据–》ADO.NET实体数据模型–》生成操作数据库上下文对象,model.cs,designer设计器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public partial class GraduationProjectEntities : DbContext
{
public GraduationProjectEntities()
: base("name=GraduationProjectEntities")//base app.config里面的连接字符串
{
}

//创建模型时的配置,这里database first不管
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}

//表对应的DbSet
public virtual DbSet<C__MigrationHistory> C__MigrationHistory { get; set; }
public virtual DbSet<ImageIndexes> ImageIndexes { get; set; }
public virtual DbSet<TreeIndexes> TreeIndexes { get; set; }
public virtual DbSet<TreeDetail> TreeDetail { get; set; }
}

Context:上下文,entity framework所有有关数据库的操作都封装在此类中

访问表两种方式dbContext.TreeDetail或者dbContext.Set<TreeDetail>()

创建好的edmx文件以xml形式打开

1
2
3
4
5
<!-- SSDL content -->
<edmx:StorageModels>。。</edmx:StorageModels>//存储模型
<!-- CSDL content -->
<edmx:ConceptualModels>。。</edmx:ConceptualModels>//抽象概念模型对应着实体也就是模型类
<!-- C-S mapping content -->//mapping信息

EF增删改

插入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//创建访问数据库统一入口
var dbContext = new Entities();
//直接add添加记录
dbContext.Set<Person>().Add(new Person
{
Id = 10,
Name = "luox",
Age = 11
});
//等同于
dbContext.Entry(new Person
{
Id = 10,
Name = "luox",
Age = 11
}).State = System.Data.Entity.EntityState.Added;
//保存所有有标记的实体到数据库
dbContext.SaveChanges();

更新

1
2
3
4
5
6
7
8
9
10
11
12
var dbContext = new Entities();
//先获取再更新
//修改必须要有主键,否则会报错
var person = new Person
{
Id = 2,
Name = "luox78",
Age = 18
};
//entity framework会根据状态生成对应的sql
dbContext.Entry(person).State = System.Data.Entity.EntityState.Modified;
dbContext.SaveChanges();

删除

1
2
3
4
5
6
7
8
9
var dbContext = new Entities();
//删除只要有主键就行了
var person = new Person
{
Id = 2,
};
//entity framework会根据状态生成对应的sql
dbContext.Entry(person).State = System.Data.Entity.EntityState.Deleted;
dbContext.SaveChanges();

dbContext.Entry(person).State = System.Data.Entity.EntityState.Deleted;标记原理

在ef保存改变里面的最后几步里面会遍历所有标记为删除,插入,更新的实体,将他们都执行相应的sql

1
2
3
4
5
6
7
8
9
10
/// <summary>接受在对象上下文中对对象所做的所有更改。</summary>
public virtual void AcceptAllChanges()
{
if (this.ObjectStateManager.SomeEntryWithConceptualNullExists())
throw new InvalidOperationException(Strings.ObjectContext_CommitWithConceptualNull);
foreach (ObjectStateEntry objectStateEntry in this.ObjectStateManager.GetObjectStateEntries(EntityState.Deleted))
objectStateEntry.AcceptChanges();
foreach (ObjectStateEntry objectStateEntry in this.ObjectStateManager.GetObjectStateEntries(EntityState.Added | EntityState.Modified))
objectStateEntry.AcceptChanges();
}

报错:

对一个或多个实体的校验失败:一般是插入的数据格式与期望格式不匹配

EF生成流程

ef封装操作 –》 ADO.NET操作–》sql–》执行

查询 linq –》 生成expression–》执行ADO.NET操作–》sql–》执行

与ADO.NET性能差距主要在封装,但相对于非常快的代码执行,ef性能差距主要表现在expression生成的sql代码有时候会有很大性能问题

model first

添加新项–》数据–》空ef设计器模型–》编辑设计器

另一个用于数据库设计的工具powerdesign

创建实体Person,PersonInfo,College,Subject

明确表之间对应关系:一对一,一对多,多对多

设计器里面添加三条关系,将Person与其他表联系起来:一个人一个信息,多个学院对应多个人,多个学科对应多个人

针对一对一,一对多关系很简单就是在表里面增加一条外键即可,但是多对多如何实现:

虽然设计器里面只是简单的用—-线连接了起来但是,生成的数据库中会增加一个两张表主键的对应关系的表,使用根据模型生成数据库,执行生成的sql(注意保存数据,表会被drop掉),可以看到存有两张表主键的对应关系的表

数据库变化了,可以在设计器中右键从数据库更新模型

包含外键的数据插入

通过设置一个人对应多个学科可以看到Person.cs里面多了

1
public virtual ICollection<Subject> Subject { get; set; }

Subject集合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var dbEntities = new luox78_成绩管理系统Entities();
Subject subject1=new Subject()
{
SubjectName = "math"
};
Subject subject2 = new Subject()
{
SubjectName = "english"
};
Person person=new Person()
{
Name = "luox78"
};
person.Subject.Add(subject1);
person.Subject.Add(subject2);
dbEntities.PersonSet.Add(person);
dbEntities.SaveChanges();

插入有外键的数据共有2种方法:

通过entity里面的集合添加

将关联实体里面的外键属性set成对应的主键

Linq查询

sql:select * (1)from Person(2) where name=“luox78”(3)

真正的执行顺序应该是231,linq里面一方面为了更符合操作,一方面为了能点出属性,先写from最后select

1
2
3
var details = from d in dbContext.TreeDetail
where d.pingtaiziyuanhao == "11111111111"
select d;

基本上所有的类sql语句都可以转换成拓展方法的形式,等同于

1
var details = dbContext.TreeDetail.Where(d => d.pingtaiziyuanhao == "11111111111");

IQueryable接口即linq实现机制

上文说道linq查询,返回值类型,我用的var,实际上是IQueryable,转到定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[__DynamicallyInvokable]
public interface IQueryable : IEnumerable
{
/// <summary>获取与 <see cref="T:System.Linq.IQueryable" /> 的实例关联的表达式目录树。</summary>
/// <returns><see cref="T:System.Linq.IQueryable" /> 的此实例关联的 <see cref="T:System.Linq.Expressions.Expression" /></returns>
[__DynamicallyInvokable]
Expression Expression { [__DynamicallyInvokable] get; }

/// <summary>获取在执行与 <see cref="T:System.Linq.IQueryable" /> 的此实例关联的表达式目录树时返回的元素的类型。</summary>
/// <returns>一个 <see cref="T:System.Type" />,表示在执行与之关联的表达式目录树时返回的元素的类型。</returns>
[__DynamicallyInvokable]
Type ElementType { [__DynamicallyInvokable] get; }

/// <summary>获取与此数据源关联的查询提供程序。</summary>
/// <returns>与此数据源关联的 <see cref="T:System.Linq.IQueryProvider" /></returns>
[__DynamicallyInvokable]
IQueryProvider Provider { [__DynamicallyInvokable] get; }
}

Expression:为linq编译时生成的表达式

ElementType:Expression返回类型

Provider:获取与此数据源关联的查询提供程序(对应数据源的驱动方法),linq可以提供对不同的集合类型查询,就是Provider实现的

延迟加载

对于像

var details = from d in dbContext.TreeDetail
                     where d.pingtaiziyuanhao == "11111111111"
                     select d;

的linq语句,只是在编译时将detail中的属性填充满,真正存在数据库中的数据此时并没有取出

只有在使用到details时,才会执行sql查询

情况一:

1
2
3
4
5
6
7
8
var dbContext = new GraduationProjectEntities();
var detail = dbContext.TreeDetail.Take(100).OrderBy(t => t.pingtaiziyuanhao);
//List<TreeDetail> list = detail.ToList();
foreach (var treeDetail in list)
{
Console.WriteLine($"{treeDetail.ziyuanbianhao}--{treeDetail.zhongzhimingcheng}");
}
dbContext.SaveChanges();

只有在foreach时才会查询

问题

1
2
3
4
5
6
7
8
foreach (var treeDetail in list)
{
Console.WriteLine($"{treeDetail.ziyuanbianhao}--{treeDetail.zhongzhimingcheng}");
}
foreach (var treeDetail in list)
{
Console.WriteLine($"{treeDetail.ziyuanbianhao}--{treeDetail.zhongzhimingcheng}");
}

两次foreach会执行两遍查询:将IQueryable集合转换成List集合List<TreeDetail> list = detail.ToList();

集合类型:本地型集合(数据存储在内存中),离线型集合(IQueryable,数据并没有分配至内存)

所以讲IQueryable转换成list实际就已经取出数据至内存中了

分页

1
var detail = dbContext.TreeDetail.Take(100).OrderBy(t => t.pingtaiziyuanhao).Skip(100*(2-1));

ef帮我们实现了相应的sql编写,查看sql语句可以看到就是通过row_number()实现的分页sql

方法链的where,select顺序没有限制,因为最后会编译成相应表达式