EF 数据库优先(DBFirst)
一、项目概述
本案例基于EF DBFirst(数据库优先)+ WinForm 实现: 1. 数据表 UserInfo 基础增、删、改、查 2. EF 实体状态控制 3. 数据库外键 ——导航属性联表查询4. 界面 DataGridView 数据绑定与刷新
DBFirst 核心流程:先有数据库、数据表、外键关系 → VS 根据数据库自动生成实体类 + EF上下文 → 代码操作实体即可操作数据库。
二、全局核心对象
1. EF 数据库上下文
// EF核心上下文:所有数据库操作的唯一入口 BankSystemEntities context = new BankSystemEntities();作用: 1. 映射数据库所有表实体 2. 托管实体对象状态(新增/修改/删除/未变更) 3. 统一提交保存到数据库SaveChanges()
三、封装方法:数据绑定刷新
// 封装:刷新表格数据 public void BindView() { // ToList():执行EF查询,把数据加载到内存并绑定控件 dataGridView1.DataSource = context.UserInfo.ToList(); }知识点: EF 查询默认延迟加载,只有调用ToList() / Find() / Single()才会真正执行 SQL 查询数据库。
四、窗体加载事件
private void Form1_Load(object sender, EventArgs e) { // 窗体打开自动加载所有用户数据 BindView(); // 隐藏导航属性自动生成的关联列(界面不需要展示) if (dataGridView1.Columns["AddressInfo"] != null) { dataGridView1.Columns["AddressInfo"].Visible = false; } }五、功能模块拆分(核心考点)
模块1:新增数据(Button1)
private void button1_Click(object sender, EventArgs e) { // 1. 新建实体对象(对应数据库一行数据) UserInfo u1 = new UserInfo() { Account = "123", Password = "123", CreateUserId = 1, LastUpdateTime = DateTime.Now, CreateTime = DateTime.Now, Status = 1 }; // 2. 添加到EF上下文【仅存入内存,未写入数据库】 context.UserInfo.Add(u1); // 3. 提交事务:同步内存所有更改到数据库 int row = context.SaveChanges(); // 4. 判断结果 if (row > 0) { MessageBox.Show("添加成功"); BindView(); // 刷新界面 } else { MessageBox.Show("添加失败"); } }新增核心原理(必背): 1.Add():仅标记为新增状态,保存到 EF 内存上下文 2.SaveChanges():真正生成 Insert 语句、提交数据库 3. 返回值:受影响的数据行数
模块2:删除数据(Button2)——两种删除方式
private void button2_Click(object sender, EventArgs e) { // 判断是否选中表格行 if (dataGridView1.SelectedRows.Count > 0) { // 1. 获取选中行主键ID int id = Convert.ToInt32(dataGridView1.SelectedRows[0].Cells["UserId"].Value); // 2. 根据主键查询被EF托管的实体对象 UserInfo u1 = context.UserInfo.Find(id); // ===== EF两种删除方式(考试重点)===== // 方式一:直接移除(简洁常用) // context.UserInfo.Remove(u1); // 方式二:手动设置实体状态为删除(底层原理) context.Entry(u1).State = System.Data.Entity.EntityState.Deleted; // 3. 提交删除到数据库 int row = context.SaveChanges(); if (row > 0) { MessageBox.Show("删除成功"); BindView(); } else { MessageBox.Show("删除失败"); } } }删除考点: 1. 必须是被EF上下文托管的对象才能删除(Find查询出来的对象) 2. 两种删除效果完全一致 3. 不写 SaveChanges 不会真正删除数据库数据
模块3:修改数据(Button3)
private void button3_Click(object sender, EventArgs e) { if (dataGridView1.SelectedRows.Count > 0) { // 1. 获取主键 int id = Convert.ToInt32(dataGridView1.SelectedRows[0].Cells["UserId"].Value); // 2. 查询出托管实体 UserInfo u1 = context.UserInfo.Single(m => m.UserId == id); // 3. 修改字段值 u1.Password = "888888"; u1.Account = "787777"; // 4. 手动标记实体为【修改状态】 context.Entry(u1).State = System.Data.Entity.EntityState.Modified; // 5. 提交更新 if (context.SaveChanges() > 0) { BindView(); } } }修改核心原理: 1. 先查后改(必须操作EF托管对象) 2.Modified:标记实体发生变更 3. SaveChanges 生成 Update 语句更新数据库
模块4:导航属性(超级重点)
private void button4_Click(object sender, EventArgs e) { // 1. 查询主表用户数据 UserInfo u1 = context.UserInfo.Find(1); // 2. 导航属性直接获取外键关联的从表数据(无需手写联表SQL) foreach (var a in u1.AddressInfo.ToList()) { MessageBox.Show(a.City + ""); } }六、导航属性【简答题必背】
1. 定义
导航属性是实体类中用于描述两张数据表外键关联关系的属性。 数据库通过外键关联,EF 通过导航属性关联,无需手写 JOIN 联表 SQL,直接点语法获取关联数据。
2. 本案例关系
UserInfo(用户表) 一对多 AddressInfo(地址表) 一个用户可以拥有多个地址
3. 核心优势
无需手写复杂联表SQL
面向对象方式获取关联数据
DBFirst 根据数据库外键自动生成
七、EF 五大实体状态(考试必背)
Unchanged:未变更(查询出来的默认状态)
Added:新增状态(Add之后)
Modified:修改状态(字段更新)
Deleted:删除状态
Detached:脱离上下文,不被EF托管
八、全套易错点总结
Add / Remove / 修改属性 只会修改内存上下文,必须
SaveChanges()才会更新数据库删除、修改必须操作EF托管对象(Find/Single 查询出来的)
导航属性依赖数据库外键关系,没有外键无法自动生成
DataGridView 操作前必须判断
SelectedRows.Count>0,防止空指针报错DBFirst 所有实体和上下文都是数据库反向生成,禁止手动修改实体关联
九、整体执行流程总结
打开窗体 → 加载数据绑定表格 → 新增/删除/修改操作改变实体状态 → SaveChanges 提交数据库 → 重新绑定刷新界面 → 导航属性联表查询关联数据
EF数据库优先 DBFirst 执行原生SQL(增删改查)
一、知识点前置(核心考点)
1. EF两种开发模式对比
常规EF写法:使用实体对象、Add/Remove、实体状态修改,面向对象操作,不写SQL。
本案例写法:EF中执行原生SQL,适合复杂查询、存储过程、特殊SQL语句场景。
2. EF执行SQL的两个核心方法(必考)
SqlQuery<T>(sql,参数):执行查询SQL(select),返回实体集合/单个实体
ExecuteSqlCommand(sql,参数):执行增删改SQL(insert/update/delete),返回受影响行数 int
3. 防SQL注入核心
全程使用SqlParameter 参数化查询,拼接字符串极易注入漏洞,考试、项目均禁止拼接SQL。
二、全局定义与初始化
// 全局EF数据库上下文(DBFirst自动生成) BankSystemEntities context = new BankSystemEntities();所有原生SQL执行,都依赖EF上下文对象调用 Database 静态方法。
三、封装方法:原生SQL查询绑定表格
功能:查询全部用户数据,绑定DataGridView
public void BindView() { // 1. 手写原生查询SQL string sql = "select * from UserInfo"; // 2. SqlQuery<实体>:执行查询,自动映射为UserInfo实体集合 List<UserInfo> list = context.Database.SqlQuery<UserInfo>(sql).ToList(); // 3. 绑定控件 dataGridView1.DataSource = list; }方法核心解析
context.Database.SqlQuery<T>:EF执行查询SQL专属方法自动将数据库查询结果,映射为对应实体类对象
.ToList():延迟加载执行,真正访问数据库
四、窗体加载事件
private void Form1_Load(object sender, EventArgs e) { // 窗体加载自动刷新数据 BindView(); // 隐藏导航属性多余列 if (dataGridView1.Columns["AddressInfo"] != null) { dataGridView1.Columns["AddressInfo"].Visible = false; } }五、新增功能(原生SQL Insert)
按钮1:参数化新增用户数据
private void button1_Click(object sender, EventArgs e) { // 1. 编写Insert语句,使用占位符 @参数名 string sql = "insert into UserInfo(Account,Password,CreateUserId,CreateTime,LastUpdateTime,Status) " + "VALUES(@Account,@Password,@CreateUserId,@CreateTime,@LastUpdateTime,@Status)"; // 2. 定义参数数组(防SQL注入) SqlParameter[] paras = new SqlParameter[] { new SqlParameter("@Account","123456"), new SqlParameter("@Password","123456"), new SqlParameter("@CreateUserId",1), new SqlParameter("@CreateTime",DateTime.Now), new SqlParameter("@LastUpdateTime",DateTime.Now), new SqlParameter("@Status",SqlDbType.Int){ Value = 0} }; // 3. 执行增删改SQL,返回受影响行数 int row = context.Database.ExecuteSqlCommand(sql, paras); // 4. 判断执行结果 if (row>0) { MessageBox.Show("插入成功"); BindView(); // 刷新表格 } else { MessageBox.Show("插入失败"); } }新增核心考点
增删改必须使用ExecuteSqlCommand
禁止字符串拼接SQL,必须SqlParameter 参数化
返回值 int:代表数据库受影响行数,大于0即成功
六、修改功能(原生SQL Update)
按钮2:根据ID更新指定用户数据
private void button2_Click(object sender, EventArgs e) { // 1. 更新SQL语句,参数占位 string sql = "update UserInfo set Account = @Account,Password =@Password where UserId =@UserId"; // 2. 封装参数 SqlParameter[] paras = new SqlParameter[] { new SqlParameter("@Account","123456"), new SqlParameter("@Password","123456888"), new SqlParameter("@UserId",5006), }; // 3. 执行更新 int row = context.Database.ExecuteSqlCommand(sql, paras); if (row > 0) { MessageBox.Show("更新成功"); BindView(); } else { MessageBox.Show("更新失败"); } }七、删除功能(原生SQL Delete)
按钮3:根据ID删除单条数据
private void button3_Click(object sender, EventArgs e) { // 1. 删除SQL string sql = "delete from UserInfo where UserId = @UserId"; // 2. 参数封装 SqlParameter[] paras = new SqlParameter[] { new SqlParameter("@UserId",5005), }; // 3. 执行删除 int row = context.Database.ExecuteSqlCommand(sql, paras); if (row > 0) { MessageBox.Show("删除成功"); BindView(); } else { MessageBox.Show("删除失败"); } }八、联表查询功能(两次原生SQL查询替代导航属性)
按钮4:手动SQL联查,不依赖EF导航属性,纯SQL实现多表关联
private void button4_Click(object sender, EventArgs e) { // 第一步:根据ID查询主表 UserInfo 用户数据 string sql = "select * from UserInfo where UserId = @UserId"; SqlParameter paras = new SqlParameter("@UserId",SqlDbType.Int); paras.Value = 1; // 查询单个实体,FirstOrDefault防止空报错 UserInfo info = context.Database.SqlQuery<UserInfo>(sql, paras).FirstOrDefault(); // 第二步:根据用户ID,查询从表 AddressInfo 关联地址数据 string sql1 = "select * from AddressInfo where CreateUserId = @a"; SqlParameter paras1 = new SqlParameter("@a", SqlDbType.Int); paras1.Value = info.UserId; List<AddressInfo> list = context.Database.SqlQuery<AddressInfo>(sql1, paras1).ToList(); // 遍历展示关联数据 foreach (var item in list) { MessageBox.Show(item.City); } }核心解析
传统EF:靠导航属性联表
本案例:手写两次SQL + 参数查询实现多表关联
适用于:导航属性失效、复杂联表、自定义查询场景
九、终极考点总结(考试必背)
1. 两个SQL方法区别(选择题必考)
SqlQuery<T>:执行查询语句(select),返回实体集合
ExecuteSqlCommand:执行增删改语句(insert/update/delete),返回受影响行数
2. 参数化查询作用
防止SQL注入漏洞
自动匹配数据类型,避免类型报错
提升SQL执行效率,支持语句缓存
3. 两种EF开发方式区别
对象方式:Add、Remove、状态修改,无需写SQL,简单CRUD首选
原生SQL方式:手写SQL语句,适合复杂查询、批量操作、特殊业务
4. 易错点
查询用 ExecuteSqlCommand 会报错,增删改用 SqlQuery 无返回值
拼接SQL字符串会导致SQL注入漏洞,考试直接扣分
SqlQuery 查询结果必须 ToList() / FirstOrDefault() 才会执行访问数据库
EF CodeFirst 代码优先
一、CodeFirst 核心概念(简答题必背)
代码优先(CodeFirst): 1. 先写 C# 实体类、通过特性标签定义字段约束 2. 通过 EF 迁移命令,自动根据实体类反向生成数据库、数据表、字段类型、约束3. 完全不用手动建表,全部代码控制数据库结构
对比 DBFirst: DBFirst:先有数据库 → 反向生成实体 CodeFirst:先写实体代码 → 自动生成数据库
二、CodeFirst 搭建完整五步流程(必考大题)
1. 安装 EF 框架 NuGet 包 2. 编写实体类 + 数据特性标签(约束、类型、主键、长度) 3. 编写 EF 数据库上下文类(继承 DbContext) 4. 配置连接字符串 5. 执行 EF 迁移命令,自动生成/更新数据库
三、实体类特性标签大全(考试超级重点)
存放于命名空间:using System.ComponentModel.DataAnnotations;using System.ComponentModel.DataAnnotations.Schema;
UserInfo 实体类完整拆解
// Table 特性:指定数据库生成的表名 [Table("UserInfo")] internal class UserInfo { // 主键 + 自增 [Key] // 设置该字段为主键 [DatabaseGenerated(DatabaseGeneratedOption.Identity)] // 数据库自增 public int Id { get; set; } // 账号字段完整约束 [Required] // 非空约束(不能为空) [MaxLength(20)] // 最大长度20 [MinLength(6)] // 最小长度6 [Column(TypeName = "varchar")] // 指定数据库字段类型 public string Account { get; set; } // 密码字段完整约束 [Required] [MaxLength(20)] [MinLength(6)] [Column(TypeName = "varchar")] public string Password { get; set; } // 状态字段:只能存0或1 [Range(0, 1)] // 值范围约束 [Column(TypeName = "int")] public int Status { get; set; } [Range(0, 1)] [Column(TypeName = "int")] public int Status1 { get; set; } // 重点:不映射到数据库 [NotMapped] public int UserName { get; set; } }所有特性标签作用总结(必背)
[Table("表名")]:指定实体对应数据库表名
[Key]:指定主键字段
[DatabaseGenerated(Identity)]:设置自增主键
[Required]:非空约束(NOT NULL)
[MaxLength/MinLength]:字符串长度限制
[Column(TypeName="类型")]:指定数据库字段类型(varchar/int)
[Range(min,max)]:数值范围约束
[NotMapped]:不映射到数据库,只做程序临时字段,库中无此列
四、EF 上下文类完整解析(CodeFirst核心)
上下文作用:数据库连接、表映射、所有CRUD操作入口
internal class UserInfoModel:DbContext { // 构造函数:绑定配置文件中的连接字符串 name=conn public UserInfoModel():base("name=conn") { } // 数据库表映射集合 // virtual + DbSet<实体> 代表数据库的一张表 public virtual DbSet<UserInfo> UserInfos { get; set; } }上下文三大必须满足条件
继承自:DbContext
必须重写构造函数,绑定连接字符串
必须定义DbSet<实体>属性,表示数据库表
五、EF 迁移命令全套(考试实操必考)
打开:NuGet 程序包管理器控制台
1. 首次创建数据库(项目初始化)
Enable-Migrations启用迁移(整个项目只执行一次)Add-Migration init创建迁移记录(init=初始化数据库)Update-Database执行迁移,自动创建数据库+数据表+字段约束
2. 后续新增/修改字段更新数据库
Add-Migration 自定义名称生成新的迁移文件Update-Database更新数据库结构
核心原理:CodeFirst 不手动改数据库,改实体类 → 迁移更新库
六、WinForm 界面 CRUD 代码拆解
1. 全局上下文对象
// 全局唯一数据库上下文 UserInfoModel u1 = new UserInfoModel();2. 数据绑定封装方法
public void BindView() { // 查询所有数据绑定表格 dataGridView1.DataSource = u1.UserInfos.ToList(); }3. 窗体加载事件
private void Form1_Load(object sender, EventArgs e) { BindView(); // NotMapped 字段不在数据库,隐藏界面列 if (dataGridView1.Columns["UserName"]!=null) { dataGridView1.Columns["UserName"].Visible = false; } }4. 新增功能(Button1)
private void button1_Click(object sender, EventArgs e) { // 1. 实例化实体 UserInfo u2 = new UserInfo() { Account = "123456", Password = "Password", Status = 1, Status1= 0 }; // 2. 添加到EF上下文内存 u1.UserInfos.Add(u2); // 3. 提交到数据库 int row = u1.SaveChanges(); // 4. 判断结果 if (row>0) { MessageBox.Show("添加成功"); BindView(); } else { MessageBox.Show("添加失败"); } }七、CodeFirst 专属超级考点
1. [NotMapped] 必考
标记的属性不会生成数据库列,仅用于程序内部临时使用,数据库完全不存在该字段。
2. 自增主键配置
[Key] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)]CodeFirst 默认非int主键不自增,必须手动加特性。
3. 迁移命令使用场景
每次修改实体类(增字段、删字段、改约束),必须执行迁移更新数据库,否则数据库结构不会同步。
4. CodeFirst 与 DBFirst 最大区别
DBFirst:库优先,实体由数据库生成
CodeFirst:代码优先,数据库由实体生成
八、易错点总结(考试扣分点)
忘记写DbSet 属性,导致无法生成表
缺少base("连接字符串名")导致连接失败
新增字段不执行迁移,数据库不更新
未加 [Required] 允许空值,与需求不符
忘记 [NotMapped] 临时字段会多余生成数据库列
九、完整项目结构梳理
Domain 层:实体类 + 所有数据特性标签(表结构定义)
Context 层:EF上下文类(数据库连接+表映射)
Form 层:界面CRUD + 数据绑定刷新
配套 App.config 完整配置(CodeFirst 直接复制使用)
<?xml version="1.0" encoding="utf-8" ?> <configuration> <connectionStrings> <!-- name="conn" 必须和上下文构造函数 base("name=conn") 名称保持一致 --> <add name="conn" connectionString="Data Source=.,1344;Initial Catalog=BankSystem;User ID=sa;Password=123456" providerName="System.Data.SqlClient"/> </connectionStrings> <startup> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8" /> </startup> </configuration>