C# 数据库 EF框架 (Entity Framework)


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添加

  1. c.AddressInfo.Add(model):添加

  2. 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. 生成表模型

相关特性:

  1. [Table(“UserInfo”)]:指定类将映射到数据库(表名)

  2. [Key] :表示模型是主键

    • [DatabaseGenerated( )]:数据库生成属性的方式

      [DatabaseGenerated(DatabaseGeneratedOption.Identity)]  //自动生成主键值
      
  3. [Required]:数据字段为必填项

  4. [DataType(“varchar(50)”)] :表示列的类型(不常用)

  5. [StringLength(50)]:表示列最大长度,只能控制字符串(不常用)

  6. [MaxLength(50)] :表示最大长度

  7. [MinLength(6)] :表示最小长度

  8. [Range(0, 50)] :指定数据范围约束

  9. [Column( )] :列

    [Column("UserName",Order =1,TypeName ="varchar(50)")] //表示数据库字段名称,排序,类型 "顺序不生效"
    
  10. [ForeignKey()]:外键

  11. [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. 把上下文和真实数据库建立连接

在程序包控制台添加迁移命令:

  1. Enable-Migrations:启用迁移(一次性)
  2. Add-Migration InitialCreate:添加迁移(生成创建数据库及数据库表的代码),其中:InitialCreate是版本名称
  3. 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.configWeb.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

或者使用包管理器控制台命令(推荐)

  1. 打开“程序包管理器控制台”(Tools → NuGet → Package Manager Console)
  2. 输入命令:
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查询和操作

  1. 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();
    
  2. 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();
    }
    
  3. 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”)}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值