返回 EF Core

基础篇(一)

以下为完整正文内容。

正文

EF Core 是一个 ORM 框架,让你用 C# 代码操作数据库,不用手写 SQL。 EF Core 翻译的基础是“映射”,把 C# 类和数据库表对应起来。 映射就是告诉 EF Core:这个 C# 类对应数据库里那张表,这个属性对应那个列。 一个 C# 类: public class User { public int Id { get; set; } public string Name { get; set; } public int Age { get; set; } } EF Core 把它映射到数据库里的一张同名表: Id | Name | Age 1 | 张三 | 25 2 |李四 | 30 类 → 表,属性 → 列。这个对应关系就是映射。 public class User // → 映射到数据库表 Users { public int Id { get; set; } // → 列 Id,且自动当主键(后面会说) public string Name { get; set; } // → 列 Name public int Age { get; set; } // → 列 Age } EF Core 自动生成对应关系: User 类 → Users 表 Id → Id 列,且是主键 Name → Name 列 Age → Age 列 只有名字对不上的时候才需要手动指定。 比如类叫 UserInfo,但你想映射到数据库里已有的 Users 表,就在类上面加 [Table("Users")]。 主键是每张表里唯一标识一条记录的字段,用来精确定位某一行。 主键值在整个表里绝不重复,靠它能找到唯一的一条数据。 public class User { public int Id { get; set; } // 主键,每个用户有唯一的 Id } 数据库 Users 表: Id | Name 1 | 张三 2 | 李四 Id=1 就是张三,Id=2 就是李四。不可能有两个 Id=1。 查单个、修改、删除都靠主键定位。 EF Core 默认约定:属性名叫 Id 或 <类名>Id 的自动当主键。 public class User { public int Id { get; set; } //主键 } public class Product { public int ProductId { get; set; } //主键 } 类型不限,int、Guid、string 都可以。一般用 int,数据库自动递增。 外键是用来关联两张表的字段,存的是另一张表的主键值。 外键表示“这一行数据属于谁”。 public class Article { public int Id { get; set; } // 主键 public string Title { get; set; } // 标题 public int UserId { get; set; } // 外键,存的是 User 表的主键值 public User User { get; set; } // 导航属性,关联到 User 对象 } 数据库里两张表: User 表 Id | Name 1 | 张三 2 | 李四 Article 表 Id |Title |UserId 101 |文章A |1 102 |文章B |2 103 |文章C |1 UserId 是外键,存的就是 User 表的主键值。UserId=1 → 张三写的。UserId=2 → 李四写的。 外键属性也有约定,叫 <导航属性名>Id 的就自动当外键。 public class Article { public int UserId { get; set; } // 外键:User + Id public User User { get; set; } // 导航属性 } UserId → 拆成 User + Id → User 指向 User 类 → Id 对应 User 类的主键 → 配对成功 如果属性名不按约定写,就用 [ForeignKey] 手动指定。 路由映射,找到对应的控制器和接口。 DbContext 是你和数据库之间的大门,所有操作都通过它。 定义一个类继承 DbContext,里面放 DbSet<T> 属性,每个属性代表一张表: public class AppDbContext : DbContext { public DbSet<User> Users { get; set; } // 对应 Users 表 public DbSet<Article> Articles { get; set; } // 对应 Articles 表 } 以后所有增删改查,都通过 AppDbContext 来操作。DbSet 就是你对一张表进行操作的全部入口。 使用 EF Core 需要安装数据库对应的 NuGet 包。 EF Core 是框架,要连哪种数据库就装对应的“驱动”。 数据库 NuGet 包 SQL Server: Microsoft.EntityFrameworkCore.SqlServer MySQL: Pomelo.EntityFrameworkCore.MySql PostgreSQL: Npgsql.EntityFrameworkCore.PostgreSQL SQLite: Microsoft.EntityFrameworkCore.Sqlite 数据库连接字符串告诉 EF Core数据库在哪、怎么连。 连接字符串是数据库的地址、用户名、密码拼成的一串文本。 写在 appsettings.json 里: { "ConnectionStrings": { "Default": "Server=localhost;Database=MyApp;User Id=sa;Password=123456;" } } Server — 数据库的地址 Database — 数据库名 User Id / Password — 登录账号密码 在 Program.cs 里把 DbContext 和连接字符串注册到依赖注入容器。 builder.Services.AddDbContext<AppDbContext>(options =>options.UseSqlServer(builder.Configuration.GetConnectionString("Default"))); 有点长,一步一步看: AddDbContext<AppDbContext> — 注册 AppDbContext 到容器 UseSqlServer — 指定用 SQL Server 数据库 GetConnectionString("Default") — 从配置里读刚才写的连接字符串 注册完后,任何地方都能通过构造函数拿到 AppDbContext,开始操作数据库。 这行是固定模板,直接套用。 查数据用 ToList(),把整张表的数据全部读进内存。 dbContext.Users.ToList() 翻译成 SELECT * FROM Users,返回一个 List<User>。 var allUsers = dbContext.Users.ToList(); ToList 是触发查询的最后一步,不调它就不执行 SQL 加筛选条件用 Where,和 LINQ 一样。 var adults = dbContext.Users.Where(u => u.Age > 18).ToList(); 翻译成 SQL:SELECT * FROM Users WHERE Age > 18。 Where 后面还能链式接 OrderBy 排序、Take(10) 取前 10 条,最后 .ToList() 才执行查询。 查单条数据用 FirstOrDefault 或 Find。 FirstOrDefault 按条件查第一条,没找到返回 null: var user = dbContext.Users.FirstOrDefault(u => u.Id == 5); Find 直接按主键查,不需要写 Where: var user = dbContext.Users.Find(5); Find 先查内存缓存,缓存没有才查数据库。FirstOrDefault 每次都查库。 新增数据用 Add,然后 SaveChanges。 var newUser = new User { Name = "张三", Age = 25 }; dbContext.Users.Add(newUser); dbContext.SaveChanges(); // 真正写入数据库,生成 INSERT SQL Add 只是标记“这个对象要新增”,SaveChanges 才生成 SQL 并执行。所有增删改,最后都要调 SaveChanges。 修改数据先查出来,改属性,然后 SaveChanges。 var user = dbContext.Users.Find(5); // 先查出来 user.Name = "李四"; // 改属性 dbContext.SaveChanges(); // 生成 UPDATE SQL 执行 框架自动追踪了哪些属性被改了,SaveChanges 时生成对应的 UPDATE 语句。 删除数据也是先查出来,然后 Remove,再 SaveChanges。 var user = dbContext.Users.Find(5); dbContext.Users.Remove(user); dbContext.SaveChanges(); // 生成 DELETE SQL 执行 一对多关系——一个用户有多篇文章,用 List 表示“多”的那一方。 public class User { public int Id { get; set; } public string Name { get; set; } public List<Article> Articles { get; set; } // 一个用户有多篇文章 } public class Article { public int Id { get; set; } public string Title { get; set; } public int UserId { get; set; } // 外键 public User User { get; set; } // 导航属性 } 查用户时想顺便拿出他的所有文章,用 .Include: var user = dbContext.Users.Include(u => u.Articles).FirstOrDefault(u => u.Id == 1); 导航属性让你通过对象直接跳到关联的对象,不用手写 SQL 去查。 没有导航属性时: 查出文章A: Id Title UserId 101 文章A 1 只知道 UserId=1。想知道作者是谁,得再查一次 User 表: var article = dbContext.Articles.Find(101); // 第一步:查出文章 var author = dbContext.Users.Find(article.UserId); // 第二步:拿UserId再查User表 Console.WriteLine(author.Name); // 拿到作者名 有导航属性时: Article 类里加了这个: public User User { get; set; } // 导航属性 EF Core 自动帮你把 UserId=1 翻译成“去 User 表找 Id=1 的那个人”。你直接用: var article = dbContext.Articles.Find(101); // 查出文章 Console.WriteLine(article.User.Name); // 直接拿到作者名 导航属性就是帮你省掉“拿着外键再查一次”这一步。 外键是编号,导航属性让编号变成活的对象。 下面看完整例子 using Microsoft.EntityFrameworkCore; public class Class { public int Id { get; set; } public string ClassName { get; set; } public List<Student> Students { get; set; } // 导航属性:这个班的所有学生 } public class Student { public int Id { get; set; } public string Name { get; set; } public int ClassId { get; set; } // 外键:存班级编号 public Class Class { get; set; } // 导航属性:这个学生所在的班级 } public class AppDbContext : DbContext { public DbSet<Class> Classes { get; set; } // 对应数据库 Classes 表 public DbSet<Student> Students { get; set; } // 对应数据库 Students 表 protected override void OnConfiguring(DbContextOptionsBuilder options) { options.UseSqlServer("Server=localhost;Database=School;User Id=sa;Password=你的密码;TrustServerCertificate=True"); } } class Program { static void Main() { using var db = new AppDbContext(); // 确保数据库和表已创建(首次运行用) db.Database.EnsureCreated(); //新增班级 var class1 = new Class { ClassName = "一班" }; var class2 = new Class { ClassName = "二班" }; db.Classes.Add(class1); db.Classes.Add(class2); db.SaveChanges(); // --- 新增学生(关联班级) --- var stu1 = new Student { Name = "小明", ClassId = class1.Id }; var stu2 = new Student { Name = "小红", ClassId = class1.Id }; var stu3 = new Student { Name = "小刚", ClassId = class2.Id }; db.Students.Add(stu1); db.Students.Add(stu2); db.Students.Add(stu3); db.SaveChanges(); // 从学生往上查,拿班级名 var s = db.Students.Find(stu1.Id); // 查出小明 Console.WriteLine(s.Name); // "小明" Console.WriteLine(s.ClassId); // 1(数字) Console.WriteLine(s.Class.ClassName); // "一班"(自动找到班级) // 从班级往下查,拿所有学生 var c = db.Classes.Include(c => c.Students) // 把学生一起查出来 .FirstOrDefault(c => c.Id == class1.Id); Console.WriteLine(c.ClassName); // "一班" foreach (var stu in c.Students) { Console.WriteLine(stu.Name); // 小明、小红 } } } 看懂这个例子,就说明上面的内容都已经理解了 迁移(Migration)让你根据 C# 实体类自动生成数据库表结构,不用手动建表。 以前你得先打开数据库工具,手写 SQL 建表: CREATE TABLE Users ( Id INT PRIMARY KEY, Name NVARCHAR(50), Age INT ); 实体类改了还得手动改表,很麻烦。 有了迁移,改完 C# 实体类,跑两条命令,数据库表自动同步。 迁移就三步,装工具、生成迁移、更新数据库。 全局安装 EF Core 命令行工具(只装一次) dotnet tool install --global dotnet-ef 然后生成迁移脚本 在项目目录下打开终端,运行: dotnet ef migrations add 这次改了什么 比如: dotnet ef migrations add 新建User表和Article表 这条命令会对比 C# 实体类和当前数据库的差异,生成一个迁移文件,里面是自动写好的 SQL。 add 后面是写给自己看的注释,方便以后知道这次改了什么 更新数据库 dotnet ef migrations update 执行迁移文件里的 SQL,自动建表、改表结构。 以后实体类有改动,重复第二步和第三步即可。 加字段、删字段、改字段名,改完 C# 类后跑一遍 migrations add 和 update,数据库跟着变。 从此不需要手动管理数据库表结构,C# 实体类是唯一的数据结构来源。