• 【To .NET】C#数据模型,从Entity Framework Core到LINQ


    大家好!我是未来村村长,就是那个“请你跟我这样做,我就跟你这样做!”的村长👨‍🌾!

    👩‍🌾“人生苦短,你用Python”,“Java内卷,我用C#”。

    ​ 从Java到C#,不仅仅是语言使用的改变,更是我从理想到现实,从象牙塔到大熔炉的第一步。.NET是微软的一盘棋,而C#是我的棋子,只希望微软能下好这盘棋,而我能好好利用这个棋子。

    ​ 上一篇文章:[C#基础语法,从数据类型到面向对象],我们介绍了C#相关语法,这篇文章我们学习C#的数据模型。

    一、Entity Framework Core入门使用

    1、准备工作

    (1)数据库表

    ​ 我们通过创建一个数据库来进行模拟操作,其中包含以下表格和字段:

    • Customer:Id、FirstName、LastName、Address、Phone、Email
    • Order:Id、OrderPlaced、OrderFulfilled、CustomerId
    • Product:Id、Name、Price
    • OrderDetail:Id、Quantity、OrderId、ProductId

    (2)Nuget配置Packages

    ​ 需要配置以下项目包

    • Microsoft.EntityFramework Core.Design
    • Microsoft.EntityFramework Core.Tools
    • Microsoft.EntityFramework Core.SqlServer

    (3)建立对应的Model(POCO)

    Customer:

    using System.Collections.Generic;
    
    namespace EFCore.Models
    {
        public class Customer
        {
            public int Id { get; set; }
            public string FirstName { get; set; }
            public string LastName { get; set; }
            public string? Address { get; set; }
            public string? Phone { get; set; }
            public ICollection<Order>  Orders { get; set; }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    Order:

    using System;
    using System.Collections.Generic;
    
    namespace EFCore.Models
    {
        public class Order
        {
            public int Id { get; set; }
            public DateTime? OrderPlaced { get; set; }
            public DateTime? OrderFulfilled { get; set; }
            public int CustomerId { get; set; }
    
            public Customer Customer { get; set; }
    
            public ICollection<OrderDetail> OrderDetials { get; set; }
    
            
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    Product:

    using System.ComponentModel.DataAnnotations.Schema;
    using System.ComponentModel.DataAnnotations;
    
    namespace EFCore.Models
    {
        public class Product
        {
            public int Id { get; set; }
            public string Name { get; set; }
            [Column(TypeName = "decimal(6,2)")]
            public decimal Price { get; set; }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    OrderDetail:

    namespace EFCore.Models
    {
        public class OrderDetail
        {
            public int Id { get; set; }
            public int Quantity { get; set; }
            public int ProductId { get; set; }
            public int OrderId { get; set; }
            public Order Order { get; set; }
            public Product Product { get; set; }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    (4)创建数据库上下文

    ​ 此处十分重要,其中重写的OnConfiguring方法中通过optionBuilder.UserSqlServer用于连接相应的数据库,传入的参数称为Connection String,还要记得加上@符号,表示取消字符串中的转意符。

    • Data Source:指定连接的数据源,一般连接本地的(localdb)\MSSQLLocalDB

    • Initial Catalog:对应连接的的数据库名称,此处定义为ContosoPizza

    using Microsoft.EntityFrameworkCore;
    using EFCore.Models;
    
    namespace EFCore.Data
    {
        public class ContosoPizzaContext : DbContext
        {
            //映射到创建的表
            public DbSet<Customer> Customers { get; set; }
            public DbSet<Order> Orders { get; set; }
            public DbSet<Product> Products { get; set; }
            public DbSet<OrderDetail> OrderDetails { get; set; }
    
            //配置方法
            protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
            {
    	        optionsBuilder.UseSqlServer(@"Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=ContosoPizza;Integrated Security=True;");
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    2、生成数据库和表

    ​ 如果这时候你发现你并没有SQL Server的话可以通过该教程进行下载:https://blog.csdn.net/qq_43884946/article/details/123312148

    ​ 下载以后,我们到可以到Visual Studio中进行连接,此处连接MSSQLLocalDB。

    在这里插入图片描述

    ​ 我们使用NuGet包管理命令来生成数据库和表,在EF Core中使用数据库迁移命令来实现,步骤如下:

    ​ ① 通过路径[工具-NuGet包管理器-程序包管理器控制台] 打开相应控制台

    ​ ② 分别输入以下命令

    Add-Migration InitialCreate
    Update-Database
    
    • 1
    • 2
    • Add-Migration InitialCreate:生成Migration文件,其中InitialCreate是生成文件的别名

    • Update-Database:根据Migration文件生成相应的数据库表。

    ​ 在程序包管理器控制台输入执行后,数据库中会新建ContosoPizza数据库,其中有对应的表,如此我们便完成了通过映射实体类生成相应的数据库。

    在这里插入图片描述

    ​ 我们可以发现除了对应实体类生成的表以外,还有__EFMigrationsHistory表,表示EF Core的迁移历史,MigrationId标识迁移记录,5.0.17表示使用的EntityFramework Core版本。

    在这里插入图片描述

    ​ 如果此时我们发现,需要在原来的Entity类基础上增加一些字段,我们不需要将数据库直接删除,只需要重新生成另一个Migration文件,进行更新即可。

    ​ 例如,我们在Customer类中增加email属性。

    public string? Email { get; set; }
    
    • 1

    ​ 然后输入以下指令。

    Add-Migration AddEmail
    Update-Database
    
    • 1
    • 2

    ​ 此时在Migration中会生成相应的迁移文件,执行Update命令后,我们到数据库查看dbo.Customer表中,发现多了Email属性列。

    在这里插入图片描述

    二、Entity Framework Core逆向工程

    1、执行逆向工程

    ​ 上一节,我们通过两个命令(Add-Migration和Update-Database)来构建迁移文件并更新到数据库中,随后通过数据库上下文和LINQ完成了对数据库表中字段的增删改查操作。但一般情况下,是先建立数据库再进行系统的搭建,这节将通过逆向工程把数据库中的表和字段。

    ​ 这节我们创建一个新的工程,用上一节创建的表,来生成相应的实体类。同样需要导入三个包:

    • Microsoft.EntityFramework Core.Design
    • Microsoft.EntityFramework Core.Tools
    • Microsoft.EntityFramework Core.SqlServer

    ​ 同样在[工具-NuGet包管理器-程序包管理器控制台] 打开相应控制台,我们使用Scaffold-DbContext "Contect String"来执行,并且通过Microsoft.EntityFrameworkCore.Sq1Server的-ContextDir创建数据库上下文Data文件夹和实体类Models文件夹。

    Scaffold-DbContext "Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=ContosoPizza;Integrated Security=True;" Microsoft.EntityFrameworkCore.SqlServer -ContextDir Data -OutputDir Models
    
    • 1

    ​ 执行之后便会产生相应文件。
    在这里插入图片描述

    ​ 我们还可以在命令后添加-DataAnnotation,这样创建的实体类中会添加相应的数据注释,建议通过该方式进行生成,增加实体类代码的可读性。

    Scaffold-DbContext "Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=ContosoPizza;Integrated Security=True;" Microsoft.EntityFrameworkCore.SqlServer -ContextDir Data -OutputDir Models -DataAnnotation
    
    • 1

    2、Scaffold-DbContext命令进阶

    (1)Scaffold-DbContext命令含义

    ​ 在[工具-NuGet包管理器-程序包管理器控制台] 中,以下命令的含义,需要注意的是.NET CLI对应的命令是不同的。

    • Scaffold-DbContext:执行脚手架命令
    • “Contect String”:连接字符串
    • Microsoft.EntityFrameworkCore.SqlServer:数据库提供者
    • -ContextDir Data:创建数据库上下文输出文件夹
    • -OutputDir Models:创建实体类输出文件夹
    • -DataAnnotation:在实体类中添加相应属性注释

    (2)partial实体类解读

    ​ 我们来看逆向工程所生成的Customer类定义,发现其是一个partial[翻译为部分的]类。

    public partial class Customer
    
    • 1

    ​ 关键字partial是一个上下文关键字,其作用是将一个类(或class、struct、interface)分解到不同的类文件中,在最终编译时可将其合并。局部类型的各个部分一般是分开放在几个不同的.cs文件中,但C#编译器允许我们将他们放在同一文件中,一般的使用场景如下:

    • 类型特别大,不宜放在一个文件中实现
    • 一个类型中的一部分代码为自动化工具生成的代码,不宜与我们自己编写的代码混合在一起
    • 需要多人合作编写一个类

    ​ 局部类型上的修饰符:

    • 一个类型的各个部分上的访问修饰符必须维持一致性
    • 如果一个类型有一个部分使用了abstract修饰符,那么整个类都将被视为抽象类
    • 如果一个类型有一个部分使用了 sealed 修饰符,那么整个类都将被视为密封类
    • 一个类的各个部分不能使用相互矛盾的修饰符,比如abstract和sealed

    局部类型的基类和接口:

    • 一个类型的各个部分上指定的基类必须一致。某个部分可以不指定基类,但如果指定,则必须相同
    • 局部类型上的接口具有“累加”效应

    (3)Scaffold-DbContext命令进阶

    ​ 我们在之前的命令中进行再次修改。

    Scaffold-DbContext "Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=ContosoPizza;Integrated Security=True;" Microsoft.EntityFrameworkCore.SqlServer -ContextDir Data -OutputDir Models/Generated -ContextNamespace ContosoPizza.Data -Namespace ContosoPizza.Models
    
    • 1
    • -ContextNamespace xxx:重定义数据库上下文的命名空间
    • -Namespace xxx:重定义实体类命名空间

    ​ 这样做好像只改变了命名空间和文件存放位置,但是更深一层的目的是为了逆向工程生成的代码与我们自己编写的业务代码通过partial分离,使得代码文件可维护性和拓展性更强。

    三、执行数据库操作

    1、增加字段

    ​ 在Program.cs中的Main方法执行下列操作。

    using EFCore.Data;
    using EFCore.Models;
    
    namespace EFCore
    {
        internal class Program
        {
            static void Main(string[] args)
            {
    			//通过using获取使用数据库上下文
                using ContosoPizzaContext context = new ContosoPizzaContext();
                //新建商品
                Product veggieSpecial = new Product() { 
                    Name = "veggie Special pizza",
                    Price = 9.99M
                };
                //通过数据库上下文添加商品
                context.Products.Add(veggieSpecial); 
                
                Product deluxeMeat = new Product(){
                    Name = "Deluxe Meat Pizza",
                    Price = 12.99M
                };
                //通过数据库上下文添加商品
                context.Add(deluxeMeat);
    			//执行操作更新
                context.SaveChanges();
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    在这里插入图片描述

    2、查询字段

    ​ 我们也可以通过LINQ查询语法进行数据库操作,例如查询Price大于10的商品

    using System;
    using System.Linq;
    using EFCore.Data;
    using EFCore.Models;
    
    namespace EFCore
    {
        internal class Program
        {
            static void Main(string[] args)
            {
    
                using ContosoPizzaContext context = new ContosoPizzaContext();
    
                var products = from product in context.Products
                                        where product.Price > 10.00M
                                        orderby product.Name
                                        select product;
    
                foreach(Product p in products)
                {
                    Console.WriteLine(p.Id);
                    Console.WriteLine(p.Name);
                    Console.WriteLine(p.Price);
                    Console.WriteLine("-------");
                }
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    在这里插入图片描述

    3、修改字段

    ​ 我们通过数据库上下文来修改属性

    //获取相应数据库上下文
    using ContosoPizzaContext context = new ContosoPizzaContext();
    
    //获取字段
    var veggieSpecial = context.Products.Where(p => p.Name == "veggie Special Pizza").FirstOrDefault();
    
    //修改字段
    if (veggieSpecial is Product)
    {
         veggieSpecial.Price = 10.99M;
         //提交修改
         context.SaveChanges();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    ​ 我们继续执行查询,可得到以下结果。

    在这里插入图片描述

    4、删除字段

    ​ 我们通过数据库上下文来删除相应的字段

    //获取相应数据库上下文
    using ContosoPizzaContext context = new ContosoPizzaContext();
    
    //获取字段
    var veggieSpecial = context.Products.Where(p => p.Name == "veggie Special Pizza").FirstOrDefault();
    
    //修改字段
    if (veggieSpecial is Product)
    {
         context.Remove(veggieSpecial)
         //提交修改
         context.SaveChanges();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    ​ 此时对应的数据就被删除了

    在这里插入图片描述

    四、LINQ查询语法详解

    ​ 第三节中我们已经使用了LINQ查询语法方法语法进行数据的查询,并且使用了EF Core对数据进行增加、修改以及删除操作,我们在这节重点介绍LINQ的查询语法。

    (1)简单LINQ

    var results = from result in db.数据表名
    		 	  select result;
    
    • 1
    • 2

    (2)Where

    var results = from result in db.数据表名
                 where result.属性名 条件表达式
                 select result;
    
    • 1
    • 2
    • 3

    (3)函数计算(count,min,max,sum)

    var results = (from result in db.数据表名
                  select result).Sum(p => p.属性名);
    
    • 1
    • 2

    (4)order

    var results = from result in db.数据表名
                 where result.属性名 > 10
                 orderby result.属性名 descending  //倒序
                 //  orderby  result.属性名 ascending   //正序
                 select result;
    
    • 1
    • 2
    • 3
    • 4
    • 5

    (5)top

    var ss = (from result in db.数据表名                 		select result).FirstOrDefault();
    
    • 1

    (6)分页查询

    var ss = (from result in db.数据表名
              where result.属性名 > 10
              orderby result.属性名 descending
              select r).Skip(10).Take(10); //取第11条到第20条数据 
    
    • 1
    • 2
    • 3
    • 4

    (7)分组查询

     var ss = from result in db.数据表名
              group result by result.类型属性 into n
              select new
              {
                 n.Key,  //这个Key是recType
                 rpId = n.Sum(r => r.rpId), //组内rpId之和
                 MaxRpId = n.Max(r => r.rpId),//组内最大rpId
                 MinRpId = n.Min(r => r.rpId), //组内最小rpId
              };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    (8)连接查询

    var ss = from r in db.数据表1
             join w in db.数据表2 on r.属性 equals w.属性
             select r;
    
    • 1
    • 2
    • 3
  • 相关阅读:
    嵌入式开发:选择嵌入式GUI生成器时要注意什么
    Ubuntu 20.04 下 APT 安装 mysql-8.0 并配置 root 远程访问
    linux下tomcat怎么部署war包
    PHP Zip File 函数
    【2023】Jenkins入门与安装
    嵌入式分享合集99
    uni-app 报错 navigateTo:fail page “/pages/.../...“ is not found
    基于jsp+mysql+ssm大学本科考研服务系统-计算机毕业设计
    y46.第三章 Kubernetes从入门到精通 -- ceph 在k8s中的使用案例(十九)
    Septentrio接收机二进制的BDS b2b改正数解码
  • 原文地址:https://blog.csdn.net/apple_51976307/article/details/125480751