ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

EF 数据库优先(DBFirst)+EF数据库优先 DBFirst 执行原生SQL(增删改查)

EF 数据库优先(DBFirst)+EF数据库优先 DBFirst 执行原生SQL(增删改查) 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新增数据Button1private 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修改数据Button3private 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.Count0防止空指针报错DBFirst 所有实体和上下文都是数据库反向生成禁止手动修改实体关联九、整体执行流程总结打开窗体 → 加载数据绑定表格 → 新增/删除/修改操作改变实体状态 → SaveChanges 提交数据库 → 重新绑定刷新界面 → 导航属性联表查询关联数据EF数据库优先 DBFirst 执行原生SQL增删改查一、知识点前置核心考点1. EF两种开发模式对比常规EF写法使用实体对象、Add/Remove、实体状态修改面向对象操作不写SQL。本案例写法EF中执行原生SQL适合复杂查询、存储过程、特殊SQL语句场景。2. EF执行SQL的两个核心方法必考SqlQueryT(sql,参数)执行查询SQL(select)返回实体集合/单个实体ExecuteSqlCommand(sql,参数)执行增删改SQL(insert/update/delete)返回受影响行数 int3. 防SQL注入核心全程使用SqlParameter 参数化查询拼接字符串极易注入漏洞考试、项目均禁止拼接SQL。二、全局定义与初始化// 全局EF数据库上下文DBFirst自动生成 BankSystemEntities context new BankSystemEntities();所有原生SQL执行都依赖EF上下文对象调用 Database 静态方法。三、封装方法原生SQL查询绑定表格功能查询全部用户数据绑定DataGridViewpublic void BindView() { // 1. 手写原生查询SQL string sql select * from UserInfo; // 2. SqlQuery实体执行查询自动映射为UserInfo实体集合 ListUserInfo list context.Database.SqlQueryUserInfo(sql).ToList(); // 3. 绑定控件 dataGridView1.DataSource list; }方法核心解析context.Database.SqlQueryTEF执行查询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 (row0) { 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.SqlQueryUserInfo(sql, paras).FirstOrDefault(); // 第二步根据用户ID查询从表 AddressInfo 关联地址数据 string sql1 select * from AddressInfo where CreateUserId a; SqlParameter paras1 new SqlParameter(a, SqlDbType.Int); paras1.Value info.UserId; ListAddressInfo list context.Database.SqlQueryAddressInfo(sql1, paras1).ToList(); // 遍历展示关联数据 foreach (var item in list) { MessageBox.Show(item.City); } }核心解析传统EF靠导航属性联表本案例手写两次SQL 参数查询实现多表关联适用于导航属性失效、复杂联表、自定义查询场景九、终极考点总结考试必背1. 两个SQL方法区别选择题必考SqlQueryT执行查询语句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 { // 构造函数绑定配置文件中的连接字符串 nameconn public UserInfoModel():base(nameconn) { } // 数据库表映射集合 // virtual DbSet实体 代表数据库的一张表 public virtual DbSetUserInfo 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. 新增功能Button1private 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 (row0) { 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 version1.0 encodingutf-8 ? configuration connectionStrings !-- nameconn 必须和上下文构造函数 base(nameconn) 名称保持一致 -- add nameconn connectionStringData Source.,1344;Initial CatalogBankSystem;User IDsa;Password123456 providerNameSystem.Data.SqlClient/ /connectionStrings startup supportedRuntime versionv4.0 sku.NETFramework,Versionv4.8 / /startup /configuration
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进