EF框架学习总结

一、EF框架概述

1.1 概念定义

实体框架(EntityFramework) 是一种对象关系映射器(ORM),它使.NET开发人员能够通过.NET对象来操作数据库。它消除了开发人员通常需要编写的大多数数据访问代码的需求。

ORM(对象关系映射) 是一种程序技术,用于实现面向对象编程语言里不同类型系统的数据之间的转换。即:把数据库中的表结构映射成语言中的对象。

1.2 EF框架优势

  • 解放开发人员编写数据库操作逻辑,把关注点转移到业务逻辑
  • 提高开发效率,开发快,节省成本
  • 微软官方推广的重要ORM框架

1.3 EF版本说明

版本特点平台支持
.NET Framework版本EF6.x及以下仅支持Windows平台
.NET Core版本EF Core跨平台支持

二、EF框架组成

2.1 EDM(实体数据模型)

EDM包含三个主要部分:

组成部分说明类比
概念模型(Conceptual Model)包含模型类和它们之间的关系,独立于数据库表设计想法/设计思路
存储模型(Storage Model)数据库设计模型,包括表、视图、存储过程等及其关系实际数据库设计
映射(Mapping)概念模型如何映射到存储模型的信息转换规则

2.2 查询语言

  • LINQ To Entity(L2E):查询实体对象的语言,返回概念模型中定义的实体
  • Entity SQL:类似于L2E的查询语言,比L2E更加复杂

2.3 数据访问层

  • Object Services(对象服务):访问数据库数据的主要入口点,负责数据实例化
  • Entity Client Data Provider:将L2E或Entity Sql转换成数据库可识别的SQL语句
  • ADO.Net Data Provider:使用标准ADO.net与数据库通信

三、EF三种使用方式

3.1 数据库优先(Database First)

适用场景:数据库已经设计好,基本不改动的情况下使用

核心特点:

  • 先有数据库,再生成实体模型
  • 通过EDMX文件可视化设计
  • 自动生成实体类和DbContext

3.2 模型优先(Model First)

适用场景:先设计模型,再根据模型生成数据库实体对象

核心特点:

  • 先设计EDMX模型图
  • 根据模型自动生成数据库表结构
  • 自动生成实体类和DbContext

3.3 代码优先(Code First)⭐

适用场景:推荐使用,先写代码,再生成数据库

核心特点:

  • 纯代码方式定义实体模型
  • 支持数据迁移(Migration)
  • 灵活性最高

代码优先使用步骤:

# 1. 安装EF框架
Install-Package EntityFramework

# 2. 启用数据迁移(一次性)
Enable-Migrations

# 3. 添加迁移版本
Add-Migration 版本名称

# 4. 更新数据库
Update-Database

四、EF重要文件

文件类型说明示例
.edmx实体数据模型文件,包含概念模型、存储模型和映射Model1.edmx
.tt模板文件,生成模型、上下文等使用的模板Model1.tt
.Context.cs实体上下文,数据库上下文类Model1.Context.cs
.cs(实体类)模型类文件,领域模型(Domain Model)UserInfo.cs

五、PM包管理器详解

5.1 概念说明

PM(Package Manager)包管理器:可以从NuGet上下载第三方模块包(类库、程序集),也可以上传自己开发的模块包。

NuGet:类似于手机上的"应用市场",提供平台供开发者上传、下载分享的模块包。

5.2 打开方式

VS2022 → 工具 → NuGet包管理器 → 程序包管理器控制台

注意:执行命令前需在包管理器顶部切换到当前项目。

5.3 常用命令

# 安装程序集
Install-Package 模块包名称

# 卸载程序集
Uninstall-Package 模块包名称

# 安装EF框架
Install-Package EntityFramework

六、数据迁移指令详解

6.1 初始化迁移(首次使用必执行)

Enable-Migrations

作用:

  • 在项目自动生成 Migrations 文件夹和配置类 Configuration.cs
  • 只运行一次,后续不用重复执行
  • 生成的配置文件控制自动迁移、种子数据等

6.2 生成迁移文件(记录实体改动)

Add-Migration 自定义迁移名称

# 示例
Add-Migration _20010
Add-Migration Add_Status2_Field

作用:

  • 对比模型快照,生成继承 DbMigration 的变更文件(包含Up/Down方法)
  • 执行后仅新增代码文件,不会修改数据库
  • 修改实体后可重复执行同一条命令覆盖本次迁移

6.3 应用迁移(同步到数据库)

# 执行所有未运行的迁移,升级到最新版本
Update-Database

6.4 常用参数拓展

回滚到指定旧版本:

Update-Database -TargetMigration "_20010"

回滚到最初空库:

Update-Database -TargetMigration:$InitialDatabase

仅生成SQL脚本:

Update-Database -Script

强制执行:

Update-Database -Force

6.5 其他实用指令

获取当前所有迁移列表:

Get-Migrations

清除已执行迁移记录(慎用):

  1. 删除 Migrations 文件夹全部文件
  2. 删除数据库表 __MigrationHistory
  3. 重新执行 Add-Migration Init

6.6 指令完整使用流程

# 1. 首次项目开启迁移(仅1次)
Enable-Migrations

# 2. 修改实体类后,生成迁移记录文件
Add-Migration _20010

# 3. 将迁移同步到数据库
Update-Database

6.7 关键补充说明

  1. 所有命令在 NuGet包管理器控制台(PMC) 执行,不是CMD/PowerShell
  2. 执行前顶部「默认项目」要选中包含DbContext的项目
  3. Database First模式完全不需要以上任何迁移命令
  4. Add-Migration只生成代码文件,必须搭配Update-Database才会改数据库
  5. 回滚本质:执行对应迁移类的 Down() 方法,撤销Up里的操作

七、LINQ概念与使用

7.1 LINQ概念

LINQ(Language Integrated Query) 是一种C#语言中的查询技术,它允许我们在代码中使用类似SQL的查询语句来操作各种数据源。这些数据源可以是集合、数组、数据库、XML文档等等。LINQ提供了一种统一的编程模型,使我们能够使用相同的方式来查询和操作不同类型的数据。

7.2 LINQ组成部分

组成部分说明是否必选
数据源(Data source)集合、数组、数据库、XML文档等必选
查询变量(Query variable)用于存储查询结果的变量必选
查询表达式(Query expression)类似SQL语句的查询表达式,用于描述查询的逻辑和条件可选
查询操作符(Query operator)用于执行各种查询操作,如筛选、排序、分组、投影等可选
查询结果(Query result)查询操作的返回结果必选

注意:数据源、查询变量、查询结果是必选项;查询表达式、查询操作符是可选项。

7.3 LINQ分类

LINQ To Entities

LINQ to Entities是Entity Framework的一部分,取代LINQ to SQL作为在数据库上使用LINQ的标准机制。Entity Framework是行业领先的对象-关系映射(ORM)系统,可以和多种数据库一起使用,并支持各种灵活、复杂的数据模型。

LINQ To Objects

主要负责对象的查询,是指直接对任意IEnumerable集合使用LINQ查询,无需使用中间LINQ程序或API。LINQ To Object提供的是内存中集合数据的实体映射。

LINQ To XML

在System.Xml.Linq命名空间下实现对XML的操作。采用高效、易用、内存中的XML工具在宿主编程语言中提供XPath/XQuery功能等。

7.4 LINQ语法

LINQ有两种主要使用方式:查询表达式和扩展方法。

7.4.1 LINQ查询表达式

查询表达式使用类似SQL的语法,通过关键字组合实现数据查询。

int[] scores = new int[] { 98, 45, 77, 888, 81, 51 };

// from关键字:从哪里查
// s:查询变量,查询变量名和命名相同
// in:在哪个数据源中查询
// scores:数据源(数组、集合、SQL数据库、XML文档)
// where:筛选条件(==、!=、>、<、>=、<=,逻辑运算符&&、||、!)
// orderby:排序(默认ascending升序,descending降序)
// select:结果投影

IEnumerable<string> query = from s in scores
                           where s >= 50 && s <= 100
                           orderby s descending
                           select s + "分";

Console.WriteLine(query.ToList()[0]);
Console.WriteLine(query.ToList().Count);

foreach (string i in query)
{
    Console.WriteLine(i);
}

字符串筛选示例:

string[] names = { "吴亦凡", "罗志祥", "李云迪" };

// 查询以"凡"结尾的字符串
// where n.EndsWith("凡")

// 查询包含"吴"的字符串
// where n.IndexOf("吴") != -1

// 查询以"罗"开头的字符串
IEnumerable<string> query1 = from n in names
                            where n.StartsWith("罗")
                            select n;

Console.WriteLine(query1.Count());

foreach (string i in query1)
{
    Console.WriteLine(i);
}

多数据源查询示例:

Student[] students = {
    new Student(){Id=1,Name="吴亦凡",Favorites=new string[]{ "写代码1","打游戏1"} },
    new Student(){Id=2,Name="蔡徐坤",Favorites=new string[]{ "写代码2","打篮球2","打游戏2"} },
    new Student(){Id=3,Name="罗志祥",Favorites=new string[]{ "写代码3","打篮球3","打游戏3"} },
};

// 如果有多个数据源,一般后面的数据源能使用前面数据源中的数据,反过来不行
// 只能有一个结果集
var query2 = from s in students                    // 第一个数据源,类型Student
             where s.Name == "吴亦凡"              // 对第一个数据源筛选
             from f in s.Favorites                 // 第二个数据源,类型string[]
             where f.Contains("打篮球")            // 对第二个数据源筛选
             select f;                             // 结果集

foreach (var i in query2)
{
    Console.WriteLine(i);
}
7.4.2 LINQ扩展方法

扩展方法通过链式调用实现查询,使用Lambda表达式作为参数。

int[] ints = { 1, 2, 3, 6, 5, 7 };

// Where():筛选,和Find类似,相当于where子句
// IEnumerable<int> query1 = ints.Where((item) => { return item >= 2; });
// IEnumerable<int> query2 = ints.Where(v => v >= 2);

// OrderBy:升序,等同于orderby ascending
// OrderByDescending:降序,等同于orderby descending
IEnumerable<int> query2 = ints.Where(v => v >= 2)
                              .OrderByDescending(v => v)
                              .Select(v => v);

foreach (var i in query2)
{
    Console.WriteLine(i);
}

7.5 LINQ+EF实战

在EF框架中,LINQ是查询数据库的主要方式:

BankSystemEntities context = new BankSystemEntities();

// 查询所有用户
var allUsers = context.UserInfo.ToList();

// 根据ID查询
var user = context.UserInfo.Find(id);

// LINQ查询表达式
var activeUsers = from u in context.UserInfo
                  where u.Status == 1
                  orderby u.CreateTime descending
                  select new 
                  {
                      u.UserId,
                      u.Account,
                      u.CreateTime
                  };

// LINQ扩展方法
var recentUsers = context.UserInfo
                         .Where(u => u.CreateTime > DateTime.Now.AddDays(-7))
                         .OrderByDescending(u => u.CreateTime)
                         .Take(10);

7.6 常用LINQ操作符

操作符说明示例
Where筛选满足条件的元素.Where(u => u.Status == 1)
OrderBy升序排序.OrderBy(u => u.CreateTime)
OrderByDescending降序排序.OrderByDescending(u => u.CreateTime)
Select投影,选择指定属性.Select(u => u.Account)
First返回第一个元素.First(u => u.Status == 1)
FirstOrDefault返回第一个元素或默认值.FirstOrDefault(u => u.UserId == id)
Single返回唯一满足条件的元素.Single(u => u.UserId == id)
SingleOrDefault返回唯一元素或默认值.SingleOrDefault(u => u.UserId == id)
Count返回元素数量.Count()
Any判断是否存在满足条件的元素.Any(u => u.Account == "admin")
All判断是否所有元素都满足条件.All(u => u.Status == 1)
Take取前N个元素.Take(10)
Skip跳过前N个元素.Skip(10)
Contains判断是否包含指定元素.Contains("admin")
GroupBy分组.GroupBy(u => u.Status)
Join连接两个数据源.Join(orders, u => u.UserId, o => o.UserId, ...)

八、代码实战示例

8.1 DbContext上下文类

public partial class BankSystemEntities : DbContext
{
    public BankSystemEntities()
        : base("name=BankSystemEntities")
    {
    }

    // DbSet属性,对应数据库表
    public virtual DbSet<UserInfo> UserInfo { get; set; }
    public virtual DbSet<AddressInfo> AddressInfo { get; set; }
    public virtual DbSet<CustomerInfo> CustomerInfo { get; set; }
    public virtual DbSet<StudentInfo> StudentInfo { get; set; }
}

8.2 实体类定义

public partial class UserInfo
{
    public UserInfo()
    {
        this.AddressInfo = new HashSet<AddressInfo>();
    }

    // 标量属性(对应数据库字段)
    public int UserId { get; set; }
    public string Account { get; set; }
    public string Password { get; set; }
    public int CreateUserId { get; set; }
    public System.DateTime CreateTime { get; set; }
    public Nullable<int> LastUpdateUserId { get; set; }
    public Nullable<System.DateTime> LastUpdateTime { get; set; }
    public int Status { get; set; }

    // 导航属性(对应表关系)
    public virtual ICollection<AddressInfo> AddressInfo { get; set; }
}

8.3 增删改查操作

添加数据
BankSystemEntities context = new BankSystemEntities();

UserInfo u1 = new UserInfo()
{
    Account = "123",
    Password = "123",
    CreateUserId = 1,
    LastUpdateTime = DateTime.Now,
    CreateTime = DateTime.Now,
    Status = 1
};

context.UserInfo.Add(u1);           // 添加到上下文(未同步到数据库)
int row = context.SaveChanges();    // 保存更改(同步到数据库)
删除数据
// 方式一:使用Remove方法
UserInfo u1 = context.UserInfo.Find(id);
context.UserInfo.Remove(u1);

// 方式二:设置实体状态
context.Entry(u1).State = System.Data.Entity.EntityState.Deleted;

context.SaveChanges();
更新数据
UserInfo u1 = context.UserInfo.Single(m => m.UserId == id);

// 修改属性
u1.Password = "888888";
u1.Account = "787777";

// 标记为修改状态
context.Entry(u1).State = System.Data.Entity.EntityState.Modified;

context.SaveChanges();
查询数据
// 查询所有
var list = context.UserInfo.ToList();

// 根据ID查询
var user = context.UserInfo.Find(id);

// LINQ查询
var users = from u in context.UserInfo
            where u.Status == 1
            orderby u.CreateTime descending
            select u;

8.4 导航属性使用

导航属性:实体类中用来表示「表与表之间外键关联关系」的属性。

// 一对多关系:一个用户对应多个地址
UserInfo u1 = context.UserInfo.Find(1);

foreach (var a in u1.AddressInfo.ToList())
{
    MessageBox.Show(a.City);
}

九、配置文件说明

9.1 App.config配置

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <configSections>
    <section name="entityFramework" 
             type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" 
             requirePermission="false" />
  </configSections>
  
  <entityFramework>
    <providers>
      <provider invariantName="System.Data.SqlClient" 
                type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" />
    </providers>
  </entityFramework>
  
  <connectionStrings>
    <add name="BankSystemEntities" 
         connectionString="metadata=res://*/Model1.csdl|res://*/Model1.ssdl|res://*/Model1.msl;
                           provider=System.Data.SqlClient;
                           provider connection string="data source=LAPTOP-804BMAF5\SQLEXPRESS;
                           initial catalog=BankSystem;
                           integrated security=True;
                           trustservercertificate=True;
                           MultipleActiveResultSets=True;
                           App=EntityFramework"" 
         providerName="System.Data.EntityClient" />
  </connectionStrings>
</configuration>

十、学习资源推荐

资源类型链接
EF官方文档https://learn.microsoft.com/zh-cn/ef/ef6/
EF三种使用方式对比https://www.cnblogs.com/wfy680/p/11969505.html
代码优先教程https://download.csdn.net/blog/column/7504784/84750516
DataType属性https://blog.csdn.net/ultramand/article/details/130039001

十一、重点总结

11.1 核心要点

  1. EF是ORM框架:将数据库表映射为.NET对象,简化数据访问
  2. 三种使用方式:Database First、Model First、Code First(推荐)
  3. 数据迁移:Code First模式下必须掌握的技能,实现数据库版本控制
  4. DbContext:数据库上下文,是EF操作数据库的核心入口
  5. 导航属性:实现表之间关联关系的访问,无需手写联表SQL

11.2 关键命令

# 安装EF
Install-Package EntityFramework

# 启用迁移
Enable-Migrations

# 添加迁移
Add-Migration 版本名

# 更新数据库
Update-Database

11.3 开发流程

设计实体类 → 编写DbContext → 启用迁移 → 添加迁移 → 更新数据库 → 业务开发
    ↑                                              |
    └───────────────────────────────────────────────┘
                    模型变更后重复迁移流程

更多推荐