C#学习笔记-Entity Framework Core基础操作学习

文章目录

  • 一、概述
  • 二、使用NuGet安装
    • [2.1 新建一个WPF项目](#2.1 新建一个WPF项目)
    • [2.2 安装Entity Framework Core包](#2.2 安装Entity Framework Core包)
  • 三、基本使用
    • [3.1 创建数据库](#3.1 创建数据库)
      • [3.1.1 创建两个模型类(对应数据库的两个表)](#3.1.1 创建两个模型类(对应数据库的两个表))
      • [3.1.2 创建上下文对象](#3.1.2 创建上下文对象)
      • [3.1.3 工程中主页面设计](#3.1.3 工程中主页面设计)
      • [3.1.4 创建数据库](#3.1.4 创建数据库)
      • [3.1.4 运行程序,创建数据库](#3.1.4 运行程序,创建数据库)
    • [3.2 增删改查](#3.2 增删改查)
      • [3.2.1 调整界面](#3.2.1 调整界面)
      • [3.2.2 增加绑定数据源并绑定到界面中](#3.2.2 增加绑定数据源并绑定到界面中)
      • [3.2.3 查询表数据操作](#3.2.3 查询表数据操作)
      • [3.2.4 查询单条数据](#3.2.4 查询单条数据)
      • [3.2.5 增加数据](#3.2.5 增加数据)
      • [3.2.6 修改数据](#3.2.6 修改数据)
      • [3.2.7 删除数据](#3.2.7 删除数据)
    • [3.3 数据库迁移](#3.3 数据库迁移)

悟剑声

诗号: 天外情风吹云立,红尘飞雨悟剑声。

悟剑声,霹雳布袋戏中的虚拟角色,伴随着雷雨交加而现的神秘少年剑客,身法奇快、剑法殊异,背着一口具有意识之剑,招式夹带紫电云光。其诗号为"天外情风吹云立,红尘飞雨悟剑声"。

其谜样的身份和武功属性,格外引人联想。泣龙怨内中藏有文诏,是东瀛军的象征。

一、概述

Entity Framework (EF) Core 是轻量化、可扩展、开源和跨平台版的常用 Entity Framework 数据访问技术。

EF Core 可用作对象关系映射程序 (O/RM),其功能包括以下几点:

  • 使 .NET 开发人员能够使用 .NET 对象处理数据库。
  • 无需再像通常那样编写大部分数据访问代码。
  • EF Core 支持多个数据库引擎。

二、使用NuGet安装

2.1 新建一个WPF项目

直接用visual studio创建wpf项目。

框架:.net 10.0

2.2 安装Entity Framework Core包

需要安装的包如下:

  • Entity FrameworkCore.xxx(具体数据的包)
  • Microsoft.EntityFrameworkCore.Tools(工具包)
  • Microsoft.EntityFrameworkCore.Proxies(延迟加载相关)

注: 各个包安装的版本需要保持一致,不然会出现冲突。

打开NuGet包管理器,搜索"Entity FrameworkCore.Mysql",这个按照自己使用的数据库,加尾缀直接搜,也可以搜索"Entity FrameworkCore"然后再找自己需要的数据库对应的包,mysql的包主要有两个:

对比如下:

特性 Pomelo.EntityFrameworkCore.MySql MySql.EntityFrameworkCore
开发方/维护方 Pomelo 基金会 主导的社区开源项目 Oracle 公司官方推出的连接器包
核心优势 社区活跃,更新快 ,紧跟 EF Core 最新版本,采用高性能的 MySqlConnector 库,被广泛验证,稳定可靠 官方背景 ,在部分场景下可能是唯一合规选择(例如部分大型企业的 Oracle 生态内部项目)
版本更新状态 非常积极,已发布支持 .NET 9 / EF Core 9 的正式版 虽然也在更新,但历史上更新节奏相对较慢,曾落后于 EF Core 版本,目前有支持 EF Core 9 的预览/测试版
一个典型的细节 版本号规则清晰,如 8.0.3 明确对应 EF Core 8 版本号可能包含 MySQL 版本信息,如 6.0.30+MySQL9.0.0,需要仔细核对对应关系

建议

  • 大多数情况,选择 Pomelo :无论是你正在开发的 WPF 自动化检测项目,还是其他 Web 应用、微服务,Pomelo.EntityFrameworkCore.MySql 都是社区公认的首选。它更快的迭代速度和更活跃的社区支持,能让你更方便地使用 EF Core 的新特性,遇到问题也更容易找到解决方案。微软官方文档也将它列为 MySQL 的主要提供程序之一。

  • 少数情况,选择官方包 :只有在项目有硬性要求必须使用 Oracle 官方组件 时(如特定的合规需求),才需要考虑 MySql.EntityFrameworkCore。同时需要注意,选择它意味着可能需要忍受较慢的更新节奏和潜在的兼容性问题。

我这边安装Pomelo的包,直接装最新版9.0.0。

安装时会自动安装相关的依赖库。

三、基本使用

3.1 创建数据库

3.1.1 创建两个模型类(对应数据库的两个表)

bash 复制代码
    /// <summary>
    /// 类别
    /// </summary>
    public class Category
    {
        /// <summary>
        /// 类别Id
        /// </summary>
        public int CategoryId { get; set; }
        /// <summary>
        /// 类别名称
        /// </summary>
        public string Name { get; set; }
        /// <summary>
        /// 类别下的产品集合
        /// </summary>

        public virtual ICollection<Product>
            Products
        { get; private set; } =
            new ObservableCollection<Product>();
    }
bash 复制代码
    public class Product
    {
        /// <summary>
        /// 产品Id
        /// </summary>
        public int ProductId { get; set; }
        /// <summary>
        /// 产品名称
        /// </summary>
        public string Name { get; set; }
        /// <summary>
        /// 类别Id
        /// </summary>

        public int CategoryId { get; set; }
        /// <summary>
        /// 类别
        /// </summary>
        public virtual Category Category { get; set; }
    }

3.1.2 创建上下文对象

上下文对象模型映射是通过DBSet来映射的,相当于一个数据集。

理解上,可以理解成ADO的DataSet,比较类似。

通过重写OnConfiguring函数,配置数据库连接字符串,这边连接本地测试的mysql数据库。

bash 复制代码
    /// <summary>
    /// 产品数据库上下文对象
    /// </summary>
    public class ProductContext : DbContext
    {
        /// <summary>
        /// 产品集合
        /// </summary>
        public DbSet<Product> Products { get; set; }
        /// <summary>
        /// 类别集合
        /// </summary>
        public DbSet<Category> Categories { get; set; }
        /// <summary>
        /// 配置数据库上下文对象
        /// </summary>
        /// <param name="optionsBuilder"></param>
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            var connectionString = "server=localhost;user=root;password=root;database=test";

            var serverVersion = new MySqlServerVersion(new Version(8, 0, 29));

            optionsBuilder.UseMySql(connectionString, serverVersion);
            optionsBuilder.LogTo(Console.WriteLine, LogLevel.Information);
            optionsBuilder.EnableSensitiveDataLogging();
            optionsBuilder.EnableDetailedErrors();
            optionsBuilder.UseLazyLoadingProxies();
        }
    }

3.1.3 工程中主页面设计

我这边添加了2个按钮,一个创建数据,一个查询的按钮。

下方是一个RichTextBox,用来展示日志

后台代码:

封装了一个简单的日志打印函数,正确的黑色,失败红色。

bash 复制代码
        private void LogWrite(string msg)
        {
            Paragraph paragraph = (Paragraph)rtxt_Log.Document.Blocks.FirstBlock;
            Run run = new Run(string.Format("{0} : {1}", DateTime.Now.ToString("HH:mm:ss"), msg))
            {
                Foreground = msg.Contains("失败") ? new SolidColorBrush(Colors.Red) : new SolidColorBrush(Colors.Black)
            };
            paragraph.Inlines.Add(run);
            paragraph.Inlines.Add(new LineBreak());
        }

创建数据库按钮事件,直接通过命令创建数据库:

3.1.4 创建数据库

直接使用EnsureCreated方法创建数据库

注: 测试时可用,官方推荐生产环境需要时候在程序外进行迁移升级,比较安全。

使用此方法会与迁移功能冲突,应为直接创建时,不会在数据库生成迁移记录表,等后续使用迁移功能时会报错,需要手动处理迁移数据记录才能正常迁移。

bash 复制代码
        /// <summary>
        /// 创建数据库按钮点击事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Button_Click(object sender, RoutedEventArgs e)
        {
            // 创建数据库
            if (ProductDB.Database.EnsureCreated())
            {
                LogWrite("创建数据库成功!");
            }
            else
            {
                LogWrite("创建数据库失败!");
            }

        }
使用MigrateAsync来创建数据库

这是使用迁移功能来创建数据库,迁移时如果不存在数据库会自动创建,也会在数据库创建迁移记录表用作标识,后面可以正常完成数据库迁移功能,不用手动处理数据。

bash 复制代码
        /// <summary>
        /// 创建数据库
        /// </summary>
        private async void CreateDatabase()
        {
            // 迁移数据库
            await ProductDB.Database.MigrateAsync();
            // 读取待执行的迁移记录,没有要继续迁移的记录标识迁移成功。
            if (!(await ProductDB.Database.GetPendingMigrationsAsync()).Any())
            {
                LogWrite("创建数据库成功!");
            }
            else
            {
                LogWrite("创建数据库失败!");
            }
        }

用迁移方法创建,会自动创建迁移记录表,并添加一条初始化的迁移记录。

3.1.4 运行程序,创建数据库

启动程序,点击创建数据库按钮:

显示创建成功,查看下后台数据库。

数据库后台也正常创建。

注: 数据库已存在的情况,再次新增就会返回失败,不会修改到数据的信息。

3.2 增删改查

3.2.1 调整界面

增加两个DataGrid显示数据,增加增删改查的基本和保存按钮。

3.2.2 增加绑定数据源并绑定到界面中

创建资源

xml 复制代码
    <Window.Resources>
        <CollectionViewSource x:Key="categoryViewSource"/>
        <CollectionViewSource x:Key="categoryProductsViewSource" 
                              Source="{Binding Products, Source={StaticResource categoryViewSource}}"/>
    </Window.Resources>

设置分类表格式并绑定数据

xml 复制代码
            <DataGrid x:Name="grid_Categories" Grid.Column="0" AutoGenerateColumns="False"
                      ItemsSource="{Binding Source={StaticResource categoryViewSource}}"
                      RowDetailsVisibilityMode="VisibleWhenSelected">
                <DataGrid.Columns>
                    <DataGridTextColumn Binding="{Binding CategoryId}"
                            Header="Category Id" MinWidth="80" Width="SizeToHeader"
                            IsReadOnly="True" Visibility="Visible"/>
                    <DataGridTextColumn Binding="{Binding Name}" Header="Name" 
                            Width="*" MinWidth="80"/>
                </DataGrid.Columns>
            </DataGrid>

设置产品表格式并绑定数据

xml 复制代码
            <DataGrid x:Name="grid_Products" Grid.Column="1" AutoGenerateColumns="False" 
                       EnableRowVirtualization="True" 
                       ItemsSource="{Binding Source={StaticResource categoryProductsViewSource}}"
                       RowDetailsVisibilityMode="VisibleWhenSelected">
                <DataGrid.Columns>
                    <DataGridTextColumn Binding="{Binding CategoryId}" 
                            Header="Category Id" Width="SizeToHeader"
                            IsReadOnly="True"/>
                    <DataGridTextColumn Binding="{Binding ProductId}" Header="Product Id" 
                            Width="SizeToHeader" IsReadOnly="True"/>
                    <DataGridTextColumn Binding="{Binding Name}" Header="Name" Width="*"/>
                </DataGrid.Columns>
            </DataGrid>

3.2.3 查询表数据操作

查询数据按钮增加查询事件处理函数。

csharp 复制代码
            if (categoryViewSource.Source == null)
            {
                grid_Categories.Items.Clear();
                grid_Products.Items.Clear();
            }
            // 加载数据
            ProductDB.Categories.Load(); 
            LogWrite("分类表数据加载完毕!");

            // 绑定集合数据
            categoryViewSource.Source = null;
            categoryViewSource.Source = ProductDB.Categories.Local.ToObservableCollection();
            RefreshItem();
            if (categoryViewSource.Source == null)
            {
                LogWrite("分类表集合为空!");
            }
            else
            {
                LogWrite("分类表集合加载成功!");
            }

3.2.4 查询单条数据

在3.1的基础上,添加一些数据

bash 复制代码
            GetInfo();
            if (rbtn_Category.IsChecked == true)
            {
                if (ID != null)
                {
                    // 异步查询方法
                    category = ProductDB.Categories.Find(ID);
                    if (category == null)
                    {
                        LogWrite("未找到该分类!");
                    }
                    else
                    {
                        txt_Name.Text = category.Name;
                        LogWrite($"查询到分类:ID={category.CategoryId}, Name={category.Name}");
                    }
                }
                else
                {
                    // 异步查询方法
                    category = ProductDB.Categories.FirstOrDefault(x => x.Name.Equals(Name));
                    if (category == null)
                    {
                        LogWrite("未找到该分类!");
                    }
                    else
                    {
                        txt_ID.Text = category.CategoryId.ToString();
                        LogWrite($"查询到分类:ID={category.CategoryId}, Name={category.Name}");
                    }
                }

            }
            else
            {
                if (ID != null)
                {
                    // 异步查询方法
                    product = ProductDB.Products.Find(ID);
                    if (product == null)
                    {
                        LogWrite("未找到该产品!");
                    }
                    else
                    {
                        txt_Name.Text = product.Name;
                        LogWrite($"查询到产品:ID={product.ProductId}, Name={product.Name}");
                    }
                }
                else
                {
                    // 异步查询方法
                    product = ProductDB.Products.FirstOrDefault(x => x.Name.Equals(Name));
                    if (product == null)
                    {
                        LogWrite("未找到该产品!");
                    }
                    else
                    {
                        txt_ID.Text = product.ProductId.ToString();
                        LogWrite($"查询到产品:ID={product.ProductId}, Name={product.Name}");
                    }
                }
            }
            RefreshItem();

3.2.5 增加数据

bash 复制代码
        private async void Add()
        {
            GetInfo();
            ID = null;  // 添加数据时,ID应为空
            if (rbtn_Category.IsChecked == true)
            {
                category = new Category()
                {
                    Name = Name,
                };

                // 异步查询方法
                ProductDB.Add(category);
                // 这边使用异步方法,需要搭配await关键字,添加数据后,会自动更新ID。
                await ProductDB.SaveChangesAsync();
                if (category.CategoryId == 0)
                {
                    LogWrite("添加分类失败!");
                }
                else
                {
                    LogWrite($"添加分类成功:ID={category.CategoryId}, Name={category.Name}");
                }
            }
            else
            {
                product = new Product()
                {
                    Name = Name,
                    CategoryId = category?.CategoryId ?? 0,
                };

                // 异步查询方法
                ProductDB.Add(product);
                await ProductDB.SaveChangesAsync();
                if (product.ProductId == 0)
                {
                    LogWrite("添加产品失败!");
                }
                else
                {
                    LogWrite($"添加产品成功:CategoryId={product.CategoryId},ID={product.ProductId}, Name={product.Name}");
                }
            }
            RefreshItem();
        }

3.2.6 修改数据

bash 复制代码
           GetInfo();
           if (rbtn_Category.IsChecked == true)
           {
               category.Name = Name;

               Task<int> tasks = ProductDB.SaveChangesAsync();
               if (tasks == null)
               {
                   LogWrite("修改分类失败!");
               }
               else
               {
                   LogWrite($"修改分类成功:ID={category.CategoryId}, Name={category.Name}");
               }
           }
           else
           {
               product.Name = Name;

               Task<int> tasks = ProductDB.SaveChangesAsync();
               if (tasks == null)
               {
                   LogWrite("修改产品失败!");
               }
               else
               {
                   LogWrite($"修改产品成功:CategoryId={product.CategoryId},ID={product.ProductId}, Name={product.Name}");
               }
           }
           RefreshItem();

3.2.7 删除数据

bash 复制代码
            GetInfo();
            Button_Query_Click(sender, e);
            if (rbtn_Category.IsChecked == true)
            {
                // 异步查询方法
                ProductDB.Categories.Remove(category);
                Task<int> tasks = ProductDB.SaveChangesAsync();
                if (tasks == null)
                {
                    LogWrite("删除分类失败!");
                }
                else
                {
                    LogWrite($"删除分类成功:ID={ID}, Name={Name}");
                }
            }
            else
            {
                // 异步查询方法
                ProductDB.Products.Remove(product);
                Task<int> tasks = ProductDB.SaveChangesAsync();
                if (tasks == null)
                {
                    LogWrite("修改产品失败!");
                }
                else
                {
                    LogWrite($"删除产品成功:CategoryId={product.CategoryId},ID={product.ProductId}, Name={product.Name}");
                }
            }
            RefreshItem();

3.3 数据库迁移

3.3.1 修改具体的模型实体类

我这边以Product为例,添加一个新的字段Remark。

csharp 复制代码
    public class Product
    {
        /// <summary>
        /// 产品Id
        /// </summary>
        [Key]
        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        public int ProductId { get; set; }
        /// <summary>
        /// 产品名称
        /// </summary>
        public string Name { get; set; }
        /// <summary>
        /// 备注        <- 这是新增的字段
        /// </summary>
        public string Remark { get; set; } = string.Empty;
        /// <summary>
        /// 类别Id
        /// </summary>

        public int CategoryId { get; set; }
        /// <summary>
        /// 类别
        /// </summary>
        public virtual Category Category { get; set; }
    }

3.3.2 创建迁移 (Add-Migration)

VS 的程序包管理器控制台(PMC) 或使用 .NET CLI(命令行)

PMC下创建

打开PMC。

输入创建指令:

bash 复制代码
# AddProductRemark是这次迁移的备注信息,会在项目文件中生成一个对应的迁移文件
PM> Add-Migration AddProductRemark
Build started...
Build succeeded.
2026/7/16 10:50:30.855 CoreEventId.SensitiveDataLoggingEnabledWarning[10400] (Microsoft.EntityFrameworkCore.Infrastructure) 
      Sensitive data logging is enabled. Log entries and exception messages may include sensitive application data; this mode should only be enabled during development.
To undo this action, use Remove-Migration.
PM> 

这是生成的迁移文件:

CLI下迁移

打开终端

安装donet-ef工具包

注: 安装的版本需要与Entity Framework Core的版本保持一致。

bash 复制代码
PS D:\Users\LJM\Desktop\TestWpfEfCoreMySql\TestWpfEfCoreMySql> dotnet tool install --global dotnet-ef --version 9.0.0
可使用以下命令调用工具: dotnet-ef
已成功安装工具"dotnet-ef"(版本"9.0.0")。

创建迁移

注意: df工具需要在项目入口文件所在的文件夹进行操作,这个要注意下自己工程数据库项目文件入口所在的文件夹,用cd指令切到对应文件夹。

bash 复制代码
PS D:\Users\LJM\Desktop\TestWpfEfCoreMySql\TestWpfEfCoreMySql> cd .\TestWpfEfCoreMySql\
PS D:\Users\LJM\Desktop\TestWpfEfCoreMySql\TestWpfEfCoreMySql\TestWpfEfCoreMySql> dotnet-ef migrations add AddProductRemark
Build started...
Build succeeded.
warn: 2026/7/16 11:01:50.968 CoreEventId.SensitiveDataLoggingEnabledWarning[10400] (Microsoft.EntityFrameworkCore.Infrastructure)
      Sensitive data logging is enabled. Log entries and exception messages may include sensitive application data; this mode should only be enabled during development.
Done. To undo this action, use 'ef migrations remove'
PS D:\Users\LJM\Desktop\TestWpfEfCoreMySql\TestWpfEfCoreMySql\TestWpfEfCoreMySql>

3.3.3 迁移数据库

应用内调用函数进行迁移

这种方式建议设计开发的时候用,生产环境,建议审核后用命令行升级。

bash 复制代码
        private async void Migrate()
        {
            // 获取待执行的迁移列表
            var pendingMigrations = await ProductDB.Database.GetPendingMigrationsAsync();
            LogWrite(string.Format("发现 {0} 个待执行的数据库更新", pendingMigrations.Count()));

            if (pendingMigrations.Any())
            {
                await ProductDB.Database.MigrateAsync();
                LogWrite("数据库更新完成!");
            }
        }
CLI迁移

在项目目录下执行update指令进行迁移

bash 复制代码
PS D:\Users\LJM\Desktop\TestWpfEfCoreMySql\TestWpfEfCoreMySql\TestWpfEfCoreMySql> dotnet-ef database update

3.3.4 删除迁移

在项目文件下执行remove,可以删除当前未执行的迁移,然后可以修改后重新创建一个迁移文件。

bash 复制代码
dotnet-ef migrations remove
相关推荐
Z5998178412 小时前
c#软件开发学习笔记--分组、游标与临时表、分页
笔记·学习·c#
rick9772 小时前
用 C# 从零搭建本地知识库:RAG 系统完整实战
c#
Z5998178413 小时前
c#软件开发学习笔记--事务、索引
笔记·学习·c#
geovindu3 小时前
CSharp: Composite Pattern
开发语言·后端·c#·组合模式·结构型模式
LeoTao3 小时前
C#专题-C# 异常处理
c#
影寂ldy3 小时前
C# 多线程进阶知识点(线程优先级、多委托传参、线程锁、死锁)
开发语言·数据库·c#
wrq14720 小时前
MyAccess 完整使用手册
c#·orm框架
z落落1 天前
C# WinForm 自定义控件
开发语言·c#
lzhdim1 天前
20260714 - 个人小作品更新
c#·notenet