惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

H
Hackread – Cybersecurity News, Data Breaches, AI and More
宝玉的分享
宝玉的分享
月光博客
月光博客
爱范儿
爱范儿
阮一峰的网络日志
阮一峰的网络日志
酷 壳 – CoolShell
酷 壳 – CoolShell
Recent Announcements
Recent Announcements
A
About on SuperTechFans
T
The Blog of Author Tim Ferriss
博客园 - 叶小钗
U
Unit 42
aimingoo的专栏
aimingoo的专栏
Y
Y Combinator Blog
Martin Fowler
Martin Fowler
N
Netflix TechBlog - Medium
博客园 - 司徒正美
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
云风的 BLOG
云风的 BLOG
M
MIT News - Artificial intelligence
大猫的无限游戏
大猫的无限游戏
J
Java Code Geeks
V
Visual Studio Blog
腾讯CDC
IT之家
IT之家

博客园 - 东邪独孤

【ASP.NET Core】Cookie 验证中回调用 URL 自动跳转的问题 【ASP.NET Core】在 Windows Forms 中运行 Web 服务器 【EF Core】级联删除行为 【EF Core】继承策略——TPC 【EF Core】继承策略——TPT 【EF Core】继承策略——TPH 【EF Core】使用自定义的值比较器 【EF Core】值转换器 【EF Core】实体追踪——Entry中记录的数据 【EF Core】实体状态与变更追踪 【EF Core】将一个实体映射到多个表的正确方法 【EF Core】“Code First”方案下以编程方式生成迁移 【EF Core】“DB First”方案下用编程方式生成数据库模型代码 【EF Core】三种方法记录生成的 SQL 语句 【EF Core】未定义实体类的数据库模型 【EF Core】“多对多”关系与跳跃导航 【EF Core】FromExpression 方法有什么用? 【EF Core】通过 DbContext 选项扩展框架 【EF Core】框架底层的数据库连接管理 【EF Core】再谈普通实体关系与 Owned 关系的区别 【EF Core】实体类的依赖注入 【EF Core】优化后的模型 【EF Core】使用外部 Model 【EF Core】聊聊“复合”属性 【EF Core】为 DatabaseFacade 扩展“创建”与“删除”数据表功能 【EF Core】带主键实体与无主键实体 【EF Core】框架是如何识别实体类的属性和主键的
【EF Core】直接更新数据
东邪独孤 · 2026-04-03 · via 博客园 - 东邪独孤

老周叕回来了,很久没写点什么了。声明一下,老周并不是没啥可写,真要写的,老周能写的东西可多了。毕竟老周聊发少年狂,左玩C,右++。内外兼修,戎码一生,天上地下海里都干过。潜水许久,先容老周解释一下。春节期间基本只做两件大事:远的不想去,近的玩腻了,正巧地铁修到家门口。所以白天带着父母坐地铁到处溜;晚上吃完饭就给家里的灯啊、风扇等低功耗电路做大面积改造。率先实现家居环境工控化。以下是灌水时间,不看可以跳过哟。

过年前买好了配电箱、直流电源、分线器、继电器、MOS 管、工控板、三块 esp32 小板;红外传感器,主要是改楼梯口的灯,拆了开关,换成红外人体感应。

至于风扇,有两台改换成 12 V 的电机,效果不太好,所以其他的保持不改,交流 220V 买的是固态继电器,3V就能驱动,所以直接连单片机 IO 口没问题。麻烦的是要用 5V 的电源单独供电。

空调没有改,它们本身就自带 Wifi 控制。

----------------------------------------------------------------------------------------------------------------------------------------------

过完年了,又是去年那个某集团。去年没调试出个结果,开年他们把机器修好了,然后又要过去继续调试。但这次破案了——当初老周怀疑是有人故意拔网线,最后发现不是人为捣乱,是交换机搞鬼,换了一台就一切顺利,迎来开年红了。

然后这个项目就要交接了,就是说,以后老周不给他们维护了,让他们自己的开发人员继续搞(他们自己养了 70 多人的开发团队,不用浪费)。于是,车间的事情了结后,要到他们集团工业区那边开了几个会,主要是给接手的人讲一下源代码,以及一些 Modbus 控制设备的原理。

----------------------------------------------------------------------------------------------------------------------------------------------

接着,经中学同学介绍,接了一个蓝牙 BLE 的项目,采集木材数据的。花了 2.5 个星期弄好,现在已正式使用,目前他们厂那里没有反馈问题,应该是可以结案了。

最近,又接了一个 ASP.NET Core 的,项目还没开始(也有可能会黄了),不知道干吗的。好像说是生产袜子的。还有一个是做游戏的,老周不会做,就推掉了。

-----------------------------------------------------------------------------------------------------------------------------------------------

好了,F 话讲完。下面是主题。说明一下,老周写的这系列 EF Core 可不算是 0 基础入门的,主要是分享实际使用技巧,不能涉及每个知识点的。

地球人都知道,EF Core 的实体追踪是通过与从数据库查询出来的值进行比较来生成更新数据库的 SQL 的,但是,在实际使用时,咱们经常不需要先查询再更新的。比如,删除一条记录;登录时写入用户登录时间等。这些操作没有必要把数据都查出来再去比较变更,再去更新。

虽然说 EF Core 在 DbContext 类的 Database 属性所引用的 DatabaseFacade 对象上提供了如 ExecuteSqlRaw 这样的方法,允许直接发 SQL 语句,不过呢,这样玩的风格还是太不像 EF 了。

于是,EF Core 又提供了另一套 API。可以绕过 ChangeTracker 来生成更新的 SQL 语句。用起来很是简单,咱们直接用实例讲解。

1、打开 VS(或者长得像 VS 的 IDE),新建一个控制台应用项目。

2、执行菜单【工具】-【Nuget 包管理器】-【程序包管理器控制台】,打开 Nuget 包控制台窗口。

3、在控制台中输入以下命令,活加 SQLite 的 EF Core 库。

install-package microsoft.entityframeworkcore.sqlite

4、打开或新建代码文件。先定义实体类。

public class Photo
{
    public int Pid { get; set; }
    public required string Title { get; set; }
    public int DPI { get; set; }
    public int Width { get; set; }
    public int Height { get; set; }
    public string? Tag { get; set; }
}

5、实现数据库上下文。

public class MyContext : DbContext
{
    public MyContext(DbContextOptions<MyContext> ops)
        : base(ops)
    { }

    /// <summary>
    /// 数据集合
    /// </summary>
    public DbSet<Photo> Photos { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Photo>(et =>
        {
            // 主键要配置,它不符合公共约定的格式
            et.HasKey(k => k.Pid).HasName("PK_Pid");
            // 其他属性可以自动配置,没特别要求可以跳过
        });
    }
}

作为主键的属性叫 Pid,没法通过公共约定类自动配置为主键,所以要手动写一下。

6、可以为数据库上下文配置日志来记录SQL语句(记得老周前面写过)。

// 先生成数据库上下文选项
DbContextOptionsBuilder<MyContext> optBuilder = new();
// 连接字符串
optBuilder.UseSqlite("data source=hehe.db");
// 过滤日志,只记录生成的SQL
optBuilder.LogTo((eventId, logLevel) =>
{
    // CommandExecuting 表示SQL执行前
    // CommandExecuted 表示SQL执行后
    // 两个事件任选其一都可以拿到SQL语句
    if (eventId.Id == RelationalEventId.CommandExecuted)
    {
        return true;    // true 表示记录该日志
    }
    return false;       // false 表示不记录
},
eventData =>
{
    if(eventData is CommandEventData cmdEvtData)
    {
        // 获取控制台当前的文本颜色
        ConsoleColor backup = Console.ForegroundColor;
        // 把文本改成地球颜色
        Console.ForegroundColor = ConsoleColor.Yellow;
        // 打印SQL
        Console.WriteLine($"已执行:{cmdEvtData.LogCommandText}");
        // 恢复文本颜色
        Console.ForegroundColor = backup;
    }
});

7、实例化数据库上下文,创建数据库,并放一点初始数据进去。

// 实例化数据库上下文
using(var c = new MyContext(optBuilder.Options))
{
    // 如果存在,删库跑路
    // 如果不存在,建库留守
    c.Database.EnsureDeleted();
    if(c.Database.EnsureCreated())
    {
        // 新库无数据,加点料
        c.Photos.Add(new Photo
        {
            Title = "山的对面有恐龙",
            Width = 50,
            Height = 35,
            DPI = 300,
            Tag = "休闲"
        });
        c.Photos.Add(new()
        {
            DPI = 300,
            Title = "清明上坟图",
            Tag = "祭祖,观光",
            Width = 1700,
            Height = 650
        });
        // 保存数据
        c.SaveChanges();
    }
}

其实,为新数据库备点新数据,可以在数据库模型配置阶段完成。

public class MyContext : DbContext
{
    ……

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Photo>(et =>
        {
            // 主键要配置,它不符合公共约定的格式
            et.HasKey(k => k.Pid).HasName("PK_Pid");
            // 初始数据
            et.HasData(new Photo
            {
                Pid = 1,
                Title = "山的对面有恐龙",
                Width = 50,
                Height = 35,
                DPI = 300,
                Tag = "休闲"
            },
            new()
            {
                Pid = 2,
                DPI = 300,
                Title = "清明上坟图",
                Tag = "祭祖,观光",
                Width = 1700,
                Height = 650
            });
        });
    }
}

注意如果用这种方法设置种子数据,Pid 要明确赋值,否则会报错。

不过,老周更推荐以下方法:

// 先生成数据库上下文选项
DbContextOptionsBuilder<MyContext> optBuilder = new();
// 连接字符串
optBuilder.UseSqlite("data source=hehe.db");
……
// 初始化数据
optBuilder.UseSeeding((context, _) =>
{
    context.Photos.Add(
     new Photo
     {
         Title = "山的对面有恐龙",
         Width = 50,
         Height = 35,
         DPI = 300,
         Tag = "休闲"
     });
    context.Photos.Add(new()
    {
        DPI = 300,
        Title = "清明上坟图",
        Tag = "祭祖,观光",
        Width = 1700,
        Height = 650
    });
    context.Photos.Add(new Photo
    {
        Title = "燕子",
        Width = 500,
        Height = 400,
        DPI = 300,
        Tag = "动物"
    });
    // 保存到数据库
    context.SaveChanges();
});

UseSeeding 方法在 EnsureCreated 方法创建数据库后调用,它的用法和常规 DbContext 用法一样,因此 Pid 属性不需要显式赋值。

8、重点来了。下面咱们直接更新数据,不需要先执行查询。

 using (MyContext context = new(optBuilder.Options))
 {
    int n = context.Photos
         .Where(x=> x.Pid == 3)
         .ExecuteUpdate(updsetBuilder =>
              {
                  updsetBuilder.SetProperty(a => a.Tag, "下次拍更好的");
              });
     Console.WriteLine("更新了{0}条记录", n);
 }

记得要先 Where 然后再调用 ExecuteUpdate 方法。调用 Where 方法是为了生成 WHERE 子句,不调用的话,那么 UPDATE 语句会更新所有数据记录。生成的 SQL 如下:

UPDATE "Photos" AS "p"
SET "Tag" = @p
WHERE "p"."Pid" = 3

ExecuteUpdate 方法需要用到名为 UpdateSettersBuilder 的类,它的作用就是你可以通过它来设置实体属性(通过表达式对象传入),EF Core 再通过传入的表达式来生成 SET 子句。该类的核心就是 SetProperty 方法。

// 重载1
SetProperty<TProperty>(Expression<Func<TSource,TProperty>> propertyExpression, TProperty valueExpression);
// 重载2
SetProperty<TProperty>(Expression<Func<TSource,TProperty>> propertyExpression, Expression<Func<TSource,TProperty>> valueExpression);

咱们的示例用的是重载1,这两个重载的区别在第二个参数。第一个参数就是告 EF 你要设置实体的哪个属性,如 p => p.Tag,就是说我要设置 Tag 属性。

第二个参数咱们分开讲:

1、在重载1中,你直接提供要设置的值,示例中咱们用的就是这样;

2、在重载2中,第二个参数是 Expression<Func<TSource,TProperty>>,隐式转换后就是 Func<TSource,TProperty> 委托类型。它带有输入输出,为什么呢?这个主要是用于你修改时需要旧值的参与的情况。

例如,我要改DPI属性的值,这个值是在旧值基础上减掉 100。

int n = context.Photos
     .Where(x=> x.Pid == 3)
     .ExecuteUpdate(updsetBuilder =>
          {
              updsetBuilder.SetProperty(a => a.Tag, "下次拍更好的");
              updsetBuilder.SetProperty(a => a.DPI, a => (a.DPI - 100));
          });

产生的 SQL 语句如下:

UPDATE "Photos" AS "p"
SET "Tag" = @p,
    "DPI" = "p"."DPI" - 100
WHERE "p"."Pid" = 3

另一种直接更新的操作就是【删除】,对应的是 ExecuteDelete 方法。该方法用起来更简单。

 int n = context.Photos
     .Where(p => p.Pid == 2)
     .ExecuteDelete();
 Console.WriteLine($"已删除{n}条记录");

多数情况下,你都不能忘了调用 Where 方法,没有 WHERE 语句会删除所有数据记录!!!这一点一定要注意。

上述代码删除 Pid 为 2 的数据记录,产生的 SQL 语句如下:

DELETE FROM "Photos" AS "p"
WHERE "p"."Pid" = 2

好了,今天的内容就到此了。老周都是一文聊一主题,内容简单的就多扯些别的,内容复杂的就多说些“正经”话。