EF 框架
EntityFramework (第三方)
EF概念:
实体框架(Entity Framework)是一种对象关系映射器(O/RM),它使.NET开发人员能够通过.NET对象来操作数据库。它消除了开发人员通常需要编写的大多数数据访问代码的需求。ORM框架有个优势:解放开发人员编写数据库操作逻辑,把关注点转移到业务逻辑。提高开发效率。(开发快,节省成本。)
ORM:对象关系映射(英语:Object Relational Mapping,简称ORM,或O/RM,或O/R mapping),是一种程序技术,用于实现面向对象编程语言里不同类型系统的数据之间的转换。
即:把数据库中的表结构映射成语言中的对象。
EF两版本:.net Core版本跨平台、.net Framework只支持Window平台
三种使用方式:
数据库优先 database first(数据库已经设计好,基本不改动的情况下使用)
模型优先 model first(先设计模型,再根据模型生成数据库实体对象和相关访问数据库的代码)
代码优先 code first(推荐,先写代码,再生成数据库) (重要)
包管理器中重要的命令:
Install-Package 模块包名称 //安装程序集
Uninstall-Package 模块包名称 //卸载程序集
数据库优先
//获取实例,封装绑定方法
private void BindDataGridView()
{
var c = new BankSystemEntities();
dataGridView1.DataSource = c.AddressInfo.ToList();
}
增删查改
添加
方法一:API添加
-
c.AddressInfo.Add(model):添加
-
c.SaveChanges():同步数据库,返回受影响的行数
//方法一:
private void button1_Click(object sender, EventArgs e)
{
var c = new BankSystemEntities();
var model = new AddressInfo()
{
ProvinceName = "a",
City = "a",
Area = "a",
DetailAddress = "a",
CreateUserId = 1,
CreateTime = DateTime.Now,
Status = 0
};
c.AddressInfo.Add(model); // 提醒:还没有真正存储到数据库,存储到内存中。 // Update/Edit, Delete/Remove
int row = c.SaveChanges(); // 把上下文中的更改(增,删,修)同步到数据库中。
if (row > 0)
{
MessageBox.Show("添加成功!");
BindDataGridView();
}
}
方法二:状态添加
private void button4_Click(object sender, EventArgs e)
{
var c = new BankSystemEntities();
var model = new AddressInfo()
{
ProvinceName = "b",
City = "b",
Area = "b",
DetailAddress = "b",
CreateUserId = 1,
CreateTime = DateTime.Now,
Status = 0
};
// a.设置实体的状态,可以使用相应的API替换,如:Add, AddRange, Remove, RemoveRange
c.Entry(model).State = EntityState.Added;
// b.同步到数据库
int row = c.SaveChanges();
if (row > 0)
{
MessageBox.Show("添加成功!");
BindDataGridView();
}
}
CodeFirst 代码优先
1. 生成表模型
相关特性:
-
[Table(“UserInfo”)]:指定类将映射到数据库(表名)
-
[Key] :表示模型是主键
-
[DatabaseGenerated( )]:数据库生成属性的方式
[DatabaseGenerated(DatabaseGeneratedOption.Identity)] //自动生成主键值
-
-
[Required]:数据字段为必填项
-
[DataType(“varchar(50)”)] :表示列的类型(不常用)
-
[StringLength(50)]:表示列最大长度,只能控制字符串(不常用)
-
[MaxLength(50)] :表示最大长度
-
[MinLength(6)] :表示最小长度
-
[Range(0, 50)] :指定数据范围约束
-
[Column( )] :列
[Column("UserName",Order =1,TypeName ="varchar(50)")] //表示数据库字段名称,排序,类型 "顺序不生效"
-
[ForeignKey()]:外键
-
[NotMapped]:表示该数据库字段不映射到数据库
namespace _2.CodeFirst优先.Domains
{
// DataAnnotations数据注解,在C#称为特性 Schema架构
// Table特性:指定数据库中表名。,常用
[Table("UserInfo")]
public class UserInfo
{
// Key特性:设置主键,常用
//[Key]
//[DatabaseGenerated(DatabaseGeneratedOption.Identity)] // 设置自动编号
// 多个特性,写一行,用英文逗号分割。
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Required] // 设置必填写,相当于not null,常用
[MaxLength(20)] // 设置列的长度最大为20,
[Column(TypeName = "varchar")] // 设置列类型
public string Account { get; set; }
//[MaxLength(12)]
//[MinLength(6)]
[Required]
[MaxLength(20)] // 设置列的长度为20,
[Column(TypeName = "varchar")] // 设置列名称,顺序,类型
[Editable(true, AllowInitialValue = true)]
public string Password { get; set; } ;
public string Email { get; set; } ;
[Required]
public string Phone { get; set; }
[Required]
[Range(0, 1)] // 指定数据范围
[Column(TypeName = "int")] // 设置列名称,顺序,类型
[Editable(true, AllowInitialValue = true)]
public int Status { get; set; } = 1;
[NotMapped] // 不映射,在表中不生成对应的列
public int TotalPrice { get; set; }
public decimal Price { get; set; }
public int Count { get; set; }
}
2. 创建数据库上下文
必须继承 DbContext 类
// 数据库的上下文怎么定义?
// 1。必须继承 DbContext
// 2。在构造函数中设置连接字符串
// 3。定义数据集(表),建议是虚拟的
internal class EFUseModel : DbContext
{
public EFUseModel() : base("name=ConnSting") //连接字符串
{
}
// 定义数据集
public virtual DbSet<UserInfo> UserInfos { get; set; }
}
3. 把上下文和真实数据库建立连接
在程序包控制台添加迁移命令:
- Enable-Migrations:启用迁移(一次性)
- Add-Migration InitialCreate:添加迁移(生成创建数据库及数据库表的代码),其中:InitialCreate是版本名称
- Update-Database:更新数据库(把Add-Migration生成的代码执行一下,把领域模型的结构同步到数据库中)
增删查改
添加数据
using (var context = new SchoolContext())
{
var student = new Student { Name = "Tom", Age = 20 };
context.Students.Add(student);
context.SaveChanges();
}
查询数据
using (var context = new SchoolContext())
{
var students = context.Students.ToList();
foreach (var student in students)
{
Console.WriteLine($"ID: {student.Id}, Name: {student.Name}, Age: {student.Age}");
}
}
更新数据
using (var context = new SchoolContext())
{
var student = context.Students.FirstOrDefault(s => s.Id == 1);
if (student != null)
{
student.Age = 25;
context.SaveChanges();
}
}
删除数据
using (var context = new SchoolContext())
{
var student = context.Students.FirstOrDefault(s => s.Id == 1);
if (student != null)
{
context.Students.Remove(student);
context.SaveChanges();
}
}
📚代码优先流程示例
graph TD
A[开始:创建实体类] --> B[创建 DbContext 类]
B --> C[配置连接字符串]
C --> D{使用 EF 版本?}
D -->|EF6| E[启用迁移:Enable-Migrations]
E --> F[添加迁移:Add-Migration InitialCreate]
F --> G[更新数据库:Update-Database]
D -->|EF Core| H[添加包:Microsoft.EntityFrameworkCore]
H --> I[添加迁移:dotnet ef migrations add InitialCreate]
I --> J[更新数据库:dotnet ef database update]
G --> K[使用 DbContext 进行 CRUD 操作]
J --> K
K --> L[可选:配置 Fluent API 或 DataAnnotation]
L --> M[结束]
在 C# 中使用 Entity Framework(EF)的 Code First(代码优先) 模式,是一种通过编写类来定义数据库结构的方式。EF 会根据你的代码自动生成数据库表结构。
下面是 Entity Framework Code First 的基本使用流程,适用于 EF6(非 Core)或 EF Core。两者流程类似,但部分细节不同,我会在流程中说明差异。
1️⃣ 第一步:创建实体类(Entity)
public class Student
{
public int Id { get; set; } // 主键,EF 默认识别“Id”或“[类名]Id”为主键
public string Name { get; set; }
public DateTime EnrollmentDate { get; set; }
}
你可以创建多个实体类,EF 会将它们映射为数据表。
2️⃣ 第二步:创建数据库上下文类(DbContext)
using System.Data.Entity; // EF6
// using Microsoft.EntityFrameworkCore; // EF Core
public class SchoolContext : DbContext //要继承 DbContext
{
public DbSet<Student> Students { get; set; }
// 构造函数指定连接字符串名称(EF6)
public SchoolContext() : base("name=SchoolDB") { }
// EF Core 一般使用 OnConfiguring
// protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
// {
// optionsBuilder.UseSqlServer("your_connection_string");
// }
}
3️⃣ 第三步:配置数据库连接字符串
在 EF6 中,修改 App.config
或 Web.config
:
<connectionStrings>
<!-- 语法:name 是名称,connectionString 是实际连接字符串 -->
<add name="LianJie"
connectionString="server=.;database=BankSystem;uid=sa;pwd=123456"
providerName="System.Data.SqlClient" />
</connectionStrings>
EF Core 中可以通过 OnConfiguring
或依赖注入配置连接字符串。
4️⃣ 第四步:创建数据库(迁移)
● EF6 使用 Database.SetInitializer
Database.SetInitializer(new CreateDatabaseIfNotExists<SchoolContext>());
// 其他选项:DropCreateDatabaseIfModelChanges,DropCreateDatabaseAlways
或者使用包管理器控制台命令(推荐):
- 打开“程序包管理器控制台”(Tools → NuGet → Package Manager Console)
- 输入命令:
Enable-Migrations // 仅限 EF6
Add-Migration InitialCreate
Update-Database
● EF Core 使用命令行工具
dotnet ef migrations add InitialCreate
dotnet ef database update
5️⃣ 第五步:使用数据库(CRUD 操作)
using (var context = new SchoolContext())
{
// 增加
var student = new Student { Name = "张三", EnrollmentDate = DateTime.Now };
context.Students.Add(student);
context.SaveChanges();
// 查询
var students = context.Students.ToList();
// 更新
var s = context.Students.First();
s.Name = "李四";
context.SaveChanges();
// 删除
context.Students.Remove(s);
context.SaveChanges();
}
6️⃣ 可选:使用 Fluent API 或 Data Annotations 进行配置
● Data Annotation 示例
public class Student
{
public int Id { get; set; }
[Required]
[StringLength(50)]
public string Name { get; set; }
[Column(TypeName = "date")]
public DateTime EnrollmentDate { get; set; }
}
● Fluent API 示例(在 DbContext 中)
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Student>()
.Property(s => s.Name)
.IsRequired()
.HasMaxLength(50);
}
关系映射
导航属性应定义为public virtual xxx,如果属性未定义为virtual,则Context不会进行延迟加载。
一对一:
1:1关系中,主表创建了导航属性后(数据库中已经创建两个表之间的关系),次表不能再创建导航属性
注意:一对一的关系中只能单向设置导航属性
public partial class Student
{
public Student()
{
this.Courses = new HashSet<Course>();
}
public int StudentID { get; set; }
public string StudentName { get; set; }
public Nullable<int> StandardId { get; set; }
public byte[] RowVersion { get; set; }
//实体导航属性
public virtual StudentAddress StudentAddress { get; set; }
}
public partial class StudentAddress
{
//同时是主键和外键
public int StudentID { get; set; }
public string Address1 { get; set; }
public string Address2 { get; set; }
public string City { get; set; }
public string State { get; set; }
//实体导航属性
public virtual Student Student { get; set; }
}
一对多:
//Standard与Teacher实体具一对多的关系。这表示多个教师评同一个级别(成绩级别A,B,C,D),
//而每个教师只能评一个级别,如给学生1评A级的同时不能再评一个B级。
public partial class Standard
{
public Standard()
{
this.Teachers = new HashSet<Teacher>();
}
public int StandardId { get; set; }
public string StandardName { get; set; }
public string Description { get; set; }
//集合导航属性
public virtual ICollection<Teacher> Teachers { get; set; }
}
public partial class Teacher
{
public Teacher()
{
this.Courses = new HashSet<Course>();
}
public int TeacherId { get; set; }
public string TeacherName { get; set; }
public Nullable<int> TeacherType { get; set; }
//外键
public Nullable<int> StandardId { get; set; }
//实体导航属性
public virtual Standard Standard { get; set; }
}
多对多:
N:N关系中,主表中可以创建导航属性,次表也可以创建导航属性。并且导航属性是集合ICollection类型。
特别提醒:N:N关系中,会创建一个【中间表】,中间表是联接主表和次表的【桥梁】
//Student和Course具有多到多关系。这表示一个学生可以参加许多课程,而一个课程也可以向许多学生讲授。
//数据库包括StudentCourse中间表,表中只包含Student和Course的主键。
//Student实体包含集合导航属性 Courses,Course实体包含集合导航属性 Students以表示它们之间的多对多关系。
//这两个表都没有外键关系,它们通过StudentCourse中间表进行关联。
public partial class Student
{
public Student()
{
this.Courses = new HashSet<Course>();
}
public int StudentID { get; set; }
public string StudentName { get; set; }
public Nullable<int> StandardId { get; set; }
public byte[] RowVersion { get; set; }
//集合导航属性
public virtual ICollection<Course> Courses { get; set; }
}
public partial class Course
{
public Course()
{
this.Students = new HashSet<Student>();
}
public int CourseId { get; set; }
public string CourseName { get; set; }
//集合导航属性
public virtual ICollection<Student> Students { get; set; }
}
预先加载
了解
预先加载:在对一种类型的实体进行查询时,将相关的实体作为查询的一部分一起加载。预先加载可以使用Include()方法实现。
EF默认使用是延迟加载,提高第一次加载的效率。数据在什么时候用,再去加载,后来还会再加载。
延迟加载又称懒加载,如果禁用后,需要使用预先加载。
预先加载,由于第一次加载数据比较多,会把关联的数据一并加载,造成加载速度慢,但后面再使用数据时,就不用再加载了。
加载一个相关实体类型
使用Include()方法从数据库中获取所有学生及成绩级别。
//使用导航属性实现
using (var ctx = new SchoolDBEntities())
{
var stud1 = ctx.Students
.Include("Standard")
.Where(s => s.StudentName == "Bill")
.FirstOrDefault<Student>();
}
//方法2:
using (var ctx = new SchoolDBEntities())
{
var stud1 = ctx.Students.Include(s => s.Standard.Teachers)
.Where(s => s.StudentName == "Bill")
.FirstOrDefault<Student>();
}
加载多个相关实体类型
//使用导航属性实现
using (var ctx = new SchoolDBEntities())
{
var stud1 = ctx.Students.Include("Standard.Teachers")
.Where(s => s.StudentName == "Bill")
.FirstOrDefault<Student>();
}
//方法2
using (var ctx = new SchoolDBEntities())
{
var stud1 = ctx.Students.Include(s => s.Standard)
.Where(s => s.StudentName == "Bill")
.FirstOrDefault<Student>();
Configuration专门用来对上下文对象配置。
//SchoolDBEntities的所有实体都禁用延迟加载(懒加载)
this.Configuration.LazyLoadingEnabled = true;
显式加载
Load方法
using (var context = new SchoolContext())
{
var student = context.Students
.Where(s => s.FirstName == "Bill")
.FirstOrDefault<Student>();
context.Entry(student).Reference(s => s.StudentAddress).Load(); // loads StudentAddress
context.Entry(student).Collection(s => s.StudentCourses).Load(); // loads Courses collection
}
原生Sql查询和操作
-
DbSet.SqlQuery():返回的结果是DbSqlQuery类型,该类型实现了IEnumberable接口,所以结果集也可以用linq操作
//查询名字为ls,密码是123321的用户集 //使用占位符,执行的时候自动生成sql参数,不用担心sql注入 var user1 = context.UserInfo.SqlQuery("select * from userinfo where username=@p0 and userpass=@p1", "ls", "123"); //方法2. //自己设sql参数 SqlParameter [] pars= { new SqlParameter("@name", SqlDbType.NVarChar, 20) { Value = "ls" }, new SqlParameter("@pass", SqlDbType.NVarChar, 20) { Value="123"} }; var user2 = context.Database.SqlQuery<UserInfo>( "select * from userinfo where username=@name and userpass=@pass", pars); //查询用户人数 int count = context.Database.SqlQuery<int>("select count(*) from userinfo").SingleOrDefault();
-
Database.SqlQuery():context.Database对应着底层数据库,Database.SqlQuery用于查询,可以返回任意类型。
using (var ctx = new SchoolDBEntities()) { //获取ID为1的student的名字 string studentName = ctx.Database.SqlQuery<string>("Select studentname from Student where studentid=@id", new SqlParameter("@id", 1)) .FirstOrDefault(); }
-
Database.ExecuteSqlCommand():Database.ExecuteSqlCommand()用于通过Sql进行CUD操作,返回int类型(受影响的行数)
using (var ctx = new SchoolDBEntities()) { //修改 int noOfRowUpdated = ctx.Database.ExecuteSqlCommand("Update student set studentname ='changed student by command' where studentid=@id", new SqlParameter("@id", 1)); //添加 int noOfRowInserted = ctx.Database.ExecuteSqlCommand("insert into student(studentname) values('New Student')"); //删除 int noOfRowDeleted = ctx.Database.ExecuteSqlCommand("delete from student where studentid=@id",new SqlParameter("@id", 1)); }
存储过程
在上下文中:
// 修改OnModelCreating,需要迁移
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// 添加FunctionImport配置(把存储过程或函数导入进来。)
// 导入添加存储过程
modelBuilder.Entity<User>().MapToStoredProcedures(s =>
s.Insert(sp => sp.HasName("P_User_Insert")));
// 导入删除存储过程
modelBuilder.Entity<User>().MapToStoredProcedures(s =>
s.Delete(sp => sp.HasName("P_User_Delete")));
// 导入修改存储过程
modelBuilder.Entity<User>().MapToStoredProcedures(s =>
s.Update(sp => sp.HasName("P_User_Update")));
}
public virtual int UserInsert()
{
//ExecuteFunction(”数据库中的存储过程名称”)
return ((I0bjectContextAdapter)this). 0bjectContext.ExecuteFunction("P User Insert");
}
public virtual int UserDelete()
{
return ((I0bjectContextAdapter)this).0bjectContext.ExecuteFunction("P User Delete”);
}
public virtual int UserUpdate()
{
return ((I0bjectContextAdapter) this). 0bjectContext.ExecuteFunction("p User Update”);
}