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:新增数据(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; }
}

上下文三大必须满足条件

  1. 继承自:DbContext

  2. 必须重写构造函数,绑定连接字符串

  3. 必须定义 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>
相关推荐
BullSmall9 小时前
Another Redis Desktop Manager(ARDM)下载指南
数据库·redis·缓存
SelectDB10 小时前
Apache Doris 倒排索引工作原理:全文检索提速 59 倍,点查提速 14 倍
数据库
码农颜10 小时前
5.1.1 原⼦性
数据库·mysql
机建狂魔10 小时前
Codex 接入第三方模型 API 实战:以 Mimo 为例
java·服务器·数据库·ai·ai编程·codex
山峰哥10 小时前
数据库工程与SQL调优:从慢查询到秒级响应的实战之路
java·开发语言·数据库·sql·深度优先·启发式算法
moMo11 小时前
# 向量数据库入门:从"关键词匹配"到"语义理解"
数据库
ACP广源盛1392462567311 小时前
DeepSeek‑V4‑Flash 公测@ACP#昇腾 950 国产算力组合落地,国产 PCIe 交换芯片 IX9104 有哪些硬件机会
大数据·数据库·人工智能·分布式·单片机·嵌入式硬件·microsoft
进击的女IT11 小时前
通过mysql中的Data目录恢复数据库数据
数据库·mysql
yuezhilangniao11 小时前
AI工具全家桶:从开发到运维,从数据库到产品经理-含魔塔社区简介
运维·数据库·人工智能
gb421528712 小时前
ChromaDB向量数据库的特点
数据库