C#中的EFCore数据库连接

核心组件:微软推出的一个orm框架, 主要作用简化对数据库的操作:1:Sql生成器,2:实体关系映射,sql解析器( 范型+反射 ),3: 代码生成器。之后针对实例化数据库实体模型对象建立的ef框架
EFcore实体框架,基于ORM框架(Object Relational Maqqing)对象--关系--映射,意思是一个对象(实体类)表示一个表。一个上下文类表示一个数据库。
ORM框架:不用考虑SQL语句怎么写,以类为单位去操作数据库,以面向对象的思想对数据库进行操作。可以搭配【linq查询条件】一起使用。
两种模式:DB First 数据库优先;code First 代码优先。
EFcore其实是一个提供程序包(dll)文件,连接数据库用的。需要NuGet包管理器下载到项目中才能用。搜索entity就可以找到。
下载名称如下:
EF框架核心包(必装):Microsoft.EntityFrameworkCore
SQL server中型数据库:Microsoft.EntityFrameworkCore.SqlServer 
MySQL小型数据库:MySQL.Data.EntityFrameworkCore
Oracle大型数据库:Oracle.EntityFrameworkCore
SQLite移动端数据库: Microsoft.EntityFrameworkCore.SQLite
InMemory做性能测试的:Microsoft.EntityFrameworkCore.InMemory
API 数据接口:Microsoft.EntityFrameworkCore.Cosmos
EF框架命令工具包:Microsoft.EntityFrameworkCore.Tools 必须安装后面你才能使用一些命令,比如数据库迁移;命令控制台:工具--->NuGet程序包管理器--->程序包管理控制台。即可输入命令。

拼接数据库连接字符串:
服务器名称3种方法:1.Data Source = (localdb)\ProjectModels; 2.server=.;3.addr=.;
数据库名称两种:1、database=数据库名;2、initial catalog=数据库名;
安全信息凭证两种:1、trusted_connection=true;2、Integrated Security = false; 【true表示不需要账号密码登录数据库,false表示必须要账号密码登录,默认值所以需要配合:uid=sa;pwd=123456;一起用】。
属性:MultipleActiveResultSets=true;表示提高访问数据库的效率

string connString = @"server=.;database=OA;uid=sa;pwd=123456;TrustServerCertificate=True;";//数据库连接字符串
string connString = @"server=.;database=OA;trusted_connection=true;MultipleActiveResultSets=true;";//数据库连接字符串

1.code First:用代码来生成数据库(比较灵活)

首先创建个实体类,实体类最好建立Models文件夹专门存放。通过实体类转换成数据库。

//Models实体类:做数据传输用,一个类表示一个表。
public class userinfo
{
    public int id { get; set; }//编号,默认约束指定:第一个属性名包含id大小都可以,默认为主键,如:uID
    public string name { get; set; }//姓名
    public string sex { get; set; }//性别
    public int age { get; set; }//年龄
}

数据库上下文类:操作数据库用的

数据库上下文类(管道:操作ef框架的一个类)可以把上下文类看成一个数据库,表看成一个属性
必须继承DbContext是ef框架提提供给我们的,封装了对数据库的增删改查等功能,功能不够要添加一下自己的东西比如:新表等等

using ConsoleApp1.Models;//实体类文件夹自己创建的
using Microsoft.EntityFrameworkCore;//使用EFCore框架必须要引入。

Console.WriteLine("这个代码没用,只是顶级程序入口没有代码,随便输入一句话指定入口Main方法而已");

//创建一个操作OA数据库的上下文类
public class NetContext : DbContext//必须继承ef框架提供的DbContext类,我们创建的上下文类是用来添加新功能的
{
    //构造方法主要ef框架在用,有时用户也可能用到
    public NetContext()
    {         
    }
    //其实这两个构造也是父类的构造,其实参数是给父类传递的。参数的意思是自动获取到连接数据库字符串
    public NetContext(DbContextOptions<NetContext> options):base(options)
    {
    }

    public DbSet<userinfo> userinfo { get; set; }//把表看成一个属性:用一个字段属性表示一个表。连接model用与生成表,默认属性名为表名

    //重写实现父类虚方法;配置方法:作用是配置数据库(参数:选项创建者)
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        string connString = @"server=.;database=OA;trusted_connection=true;MultipleActiveResultSets=true;";//数据库连接字符串
        optionsBuilder.UseSqlServer(connString);
    }
}

创建好后就可以执行数据库迁移两个命令:工具--NuGet包管理器--控制台
add-migration init-Mig (生成迁移代码  init-Mig是迁移的名称) 得到一个迁移文件夹
update-database(执行到数据库)

注意:provider: SSL Provider, error: 0 - 证书链是由不受信任的颁发机构颁发的

如果勾选了,连接字符串需要设置:Encrypt=True;TrustServerCertificate=True;

另外还可以用代码来删除和创建数据库,这种会整个数据库重新创建一遍(数据会丢失)
using (var db = new userContext())
{
    db.Database.EnsureDeleted();//删除数据库
    db.Database.EnsureCreated();//创建数据库
}

2、DB First 用数据库来生成代码(了解)

首先要安装一个反向过程设计包:Microsoft.EntityFrameworkCore.Design 就是把数据库转换成实体类,不需要手动写上下文类,用命令来自动生成。最终还是用上下文类操作数据库。
创建一个数据库做演示

create database OA
go
use OA
go
create table userinfo
(
    id int primary key identity(1,1),
    name nvarchar(20),
    sex char(5),
    age int,
)
insert into userinfo values('张三','',19)select * from userinfo
--修改: update userinfo set name='大王',sex='男',age=16 where id=2
--删除: delete from userinfo where id=2

生成上下文类命令3个部分:1.创建命令 2.连接数据库字符串 3.数据库程序包
Scaffold-DbContext "server=.;database=OA;trusted_connection=true;MultipleActiveResultSets=true;" Microsoft.EntityFrameworkCore.SqlServer
规范命令:继续往后加
-Context NameContext  上下文名称规范
-outputdir Models  实体类生成在Models文件夹下
-contextdir Data 上下文类在Data目录下
-Tables userinfo 我在想生成userinfo其他表不需要,需要就,往后面加,默认生成所有表。
-DataAnnotations  生成数据注解。也就是【特性】
-Force 强制覆盖

常用的特性约束

配置约束的三种方式:
1.默认约束。如:实体类表的第一个字段只要包含id大小写都可以:StudentID,默认就是主键。
2.数据注解(通过【特性】进行约束)执行优先级:FluentAPI高于----特性高于-----默认约束
3.FluentAPI(通过接口来配置,最强大)FluentAPI会覆盖前面两种约束

using System.ComponentModel.DataAnnotations;//系统特性
using System.ComponentModel.DataAnnotations.Schema;//数据库特性
using Microsoft.EntityFrameworkCore;//efcore框架

namespace ConsoleApp1.Models
{
    [NotMapped, Table("userinfo")]//不想同步当前表到数据库。更改表名。多特性可以写一起用逗号隔开。
    public partial class Userinfo
    {
        [Key]//主键,不过一般都用默认值,这个特性用于不包含id的属性名。
        
        public int Id { get; set; }
        [Column("name")]//Column是列的意思,这里表示更改属性名(列名),Column列名多参数可以用逗号隔开构造方法重载的。
        [StringLength(20)]//字符串长度,参数最大长度。
        [Display(Name = "姓名")]//UI界面会显示别名
        public string Name { get; set; } 
        [StringLength(5,MinimumLength = 1,ErrorMessage ="错误信息:长度超出范围")]
        [Unicode(false)]//非unicode,表示可以包含汉字+特殊字符。如果数据库支持Unicode 类型时,UnicodeAttribute 会被忽略。
        [Required]//不为空
        [Column(Order = 3)]//改为第3列,默认id为0开始排序
        public string Sex { get; set; } //默认不为空
        [NotMapped]//不想映射这个字段到数据库,
        [Column(TypeName = "money")] //在数据库显示的是money类型表示金钱类型,对应decimal类型,这里随便写的int
        [ForeignKey("Stu")]//外键约束,把当前属性,当成表Student的外键,如果当前属性名为StudentID之类的默认就是外键,就不需要特性了。
        public int? Age { get; set; } //问号表示可以为空null

        public virtual Student Stu { get; set; }//导航属性:导航属性要添加为virtual虚属性,c#特性新功能,重写数据延迟加载。
    }

    public partial class Student//建立关系,1对多,多的那一边要有导航属性和外键关联。如{1,2{1,2,3,4...}},只要是关联都定义为虚属性。
    { 
        public int Id { get; set; }
        public string Name { get; set; }
        public virtual ICollection<Userinfo> Userinfo { get; set; } //建立关系用集合ICollection,集合里必须有导航属性和外键属性。
        public virtual Userinfo Userinfo1 { get; set; } //这样就是1对1的关系不是集合,而是两把都是对应的导航属性。
    }
}

给表添加默认数据OnModelCreating

创建两个实体类做表

using System.ComponentModel.DataAnnotations;//系统特性
namespace ConsoleApp1.Models
{
    //Models实体类:做数据传输用,一个类表示一个表。
    public class userinfo
    {
        [Key]
        public int usersid { get; set; }//编号,默认约束指定:第一个属性名包含id大小都可以,默认为主键,如:uID
        [StringLength(10)]
        [Display(Name = "姓名")]
        public string? name { get; set; }//姓名
        public DateTime time { get; set; }//时间
        //public virtual Student Student { get; set; }//假如我在这边也使用导航属性,那么就变成1对1关系了,使用一个usersid,另外Studentid就不需要。
        public virtual ICollection<Student> Student { get; set; }//集合导航属性1对多接收多的那一边,如;Student有很多usersid为2
    }
    public class Student
    {
        public int Studentid { get; set; }
        public int usersid { get; set; }//1对多,多的一边要加对应关联id。     
        public GetSex sex { get; set; }//性别
        public int age { get; set; }//年龄
        //导航属性都要定义为虚属性virtual,框架会重写,添加新功能。
        public virtual userinfo users{ get; set; } //表Student为多个数据,对应一个userinfo中的一个数据必须指定对应的一个字段如usersid
        //public virtual ICollection<userinfo> userss{ get; set; }//两边都有叫多对多,不需要引导id会自动生成一个中间表。
    }
    public enum GetSex { 男,女 } //枚举默认,男=0 
}

创建数据库上下文类

using ConsoleApp1.Models;
using Microsoft.EntityFrameworkCore;//使用EFCore框架必须要引入。

namespace ConsoleApp1.Data
{
    //创建一个操作OA数据库的上下文类
    public class OAContext : DbContext
    {
        public DbSet<userinfo> userinfo { get; set; }
        public DbSet<Student> Student { get; set; }
        //重写实现父类虚方法;作用是配置数据库
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            string connString = @"server=.;database=OA;trusted_connection=true;MultipleActiveResultSets=true;";//数据库连接字符串
            optionsBuilder.UseSqlServer(connString);
        }
        //数据播种,默认添加默认数据
        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<userinfo>().HasData(new List<userinfo> {
                new userinfo{ usersid = 1,name="张三",time=DateTime.UtcNow },
                new userinfo{ usersid = 2,name="李四",time=DateTime.UtcNow },
                new userinfo{ usersid = 3,name="王五",time=DateTime.UtcNow }
            });
            modelBuilder.Entity<Student>().HasData(new List<Student> {
                new Student{ Studentid=1, usersid = 1,sex=GetSex.男,age = 18 },
                new Student{ Studentid=2, usersid = 1,sex=GetSex.女,age = 19 },
                new Student{ Studentid=3, usersid = 3,sex=GetSex.男,age = 20 }
            });
        }
    }
}

数据加载(查询)

查询单表的所有数据(通常搭配linq查询)

using ConsoleApp1.Data;//上下文的文件夹
using Microsoft.EntityFrameworkCore;//ef框架,这里AsNoTracking方法取消跟踪使用到

//using括号内对象执行玩自动释放空间,因为ef数据库操作类DbContext实现了IDisposable接口,只要实现这个接口的对象都需要手动close关闭。
using (var db = new OAContext()) //上下文类继承的是DbContext,所以需要手动关闭,而using关键字,内部实现了自动close关闭功能。
{
    //查询不需要追踪,增删改才需要,默认ef是打开追踪模式。可以用AsNoTracking方法关闭追踪,提高查询效率。
    var users = db.userinfo.AsNoTracking().ToList();

    foreach (var user in users) Console.WriteLine($"{user.usersid},{user.name},{user.time}");
}

查询关联表,一级用Include二级以后用ThenInclude。

using ConsoleApp1.Data;//上下文的文件夹
using Microsoft.EntityFrameworkCore;//ef框架

using (var db = new OAContext()) 
{
    var stu = db.Student.AsNoTracking().Include(s=>s.users).ToList();//二级三级以上往后加ThenInclude(c=>c.关联名)即可

    foreach (var s in stu) Console.WriteLine($"{s.age},{s.sex},{s.users.name},{s.users.time}");

    var ss = stu.FirstOrDefault();//显示查询到的第一条数据
    Console.WriteLine($"{ss.age},{ss.sex},{ss.users.name},{ss.users.time}");//如果users也是一个集合也可以用ss.users.FirstOrDefault().name这样查。
}

匹配加载:大家叫显示加载,用于查询关联表,提高效率,匹配查找减少不需要的数据。

using ConsoleApp1.Data;//上下文的文件夹
using Microsoft.EntityFrameworkCore;//ef框架

using (var db = new OAContext()) 
{
    var user = db.userinfo.First(); //拿第一条数据,或者筛选出一条数据,通过Entry方法匹配查找
    db.Entry(user).Collection(c=>c.Student).Load();//针对集合用Collection,加载用load
    Console.WriteLine($"{user.name}:{user.time}:{user.Student.FirstOrDefault().sex}");

    var stu = db.Student.First();//查第一个的区别,First为空会报错,FirstOrDefault返回默认值0
    db.Entry(stu).Reference(c => c.users).Load();//针对单个实体用Reference
    Console.WriteLine($"{stu.sex}:{stu.age}:{stu.users.name}:{stu.users.time}");
}

条件查询,选择性加载用到linq里的select方法

using ConsoleApp1.Data;//上下文的文件夹
using Microsoft.EntityFrameworkCore;//ef框架

using (var db = new OAContext())
{
    var user = db.userinfo.AsTracking() //开启追踪模式,还可以userinfo.Where(a=>a.name=="条件")
        .Select(x => new modle //转换最好定义一个实体类,匿名对象到时候还需要在转换一次。
        {
            name = x.name,//查到的数据,在存入实体类
            time = x.time //不定义modle指定匿名对象是泛型数组
        }).ToList();

    foreach (var item in user) Console.WriteLine($"{item.name}:{item.time}");
}

class modle
{
    public string name { get; set; }
    public DateTime time { get; set; }
}

延迟加载:1.必须安装:Microsoft.EntityFrameworkCore.Proxies 包,2.配置上下文类中的OnConfiguring方法,3.导航属性必须是虚属性virtual

//重写实现父类虚方法;作用是配置数据库,这里配置第二步的延迟加载。
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    optionsBuilder.UseLazyLoadingProxies();//配置延迟加载
    string connString = @"server=.;database=OA;trusted_connection=true;MultipleActiveResultSets=true;";//数据库连接字符串
    optionsBuilder.UseSqlServer(connString);
}

演示查询延迟加载

using ConsoleApp1.Data;//上下文的文件夹
using Microsoft.EntityFrameworkCore;//ef框架

using (var db = new OAContext())
{
    var user = db.userinfo.FirstOrDefault();
    var stu = user.Student.FirstOrDefault();

    Console.WriteLine($"{user.name}:{stu.sex}");
}

增删改

添加

using ConsoleApp1.Data;//上下文的文件夹
using ConsoleApp1.Models;//实体类
using Microsoft.EntityFrameworkCore;//ef框架

using (var db = new OAContext())
{
    userinfo u = new userinfo();
    u.name = "赵六";
    u.time = DateTime.Now;

    var id = db.Add(u);//在内存进行添加,返回对象id值,可以直接实体类中取值。

    var count = db.SaveChanges();//保存到数据库,返回受影响行数。增删改都需要保存数据库。

    Console.WriteLine($"添加{count}条数据id值:{u.usersid}");
}

修改

using (var db = new OAContext())
{
    var u = db.userinfo.Find(5);//通过id查询到数据
    u.name = "王大妈";
    u.time = DateTime.Now;
    db.userinfo.Update(u);//内存里执行修改

    u = db.userinfo.Find(6);//通过id查询到数据
    u.name = "王大妈2";
    u.time = DateTime.Now;
    db.userinfo.Update(u);//内存里执行修改

    var count = db.SaveChanges();//保存到数据库,其实他是一个事物处理,可以保存所有操作,写一个就可以,一个出错,全部撤销。

    Console.WriteLine($"修改{count}条数据");
}

删除(默认是级联删除)

using (var db = new OAContext())
{
    var u = db.userinfo.SingleOrDefault(a => a.usersid == 1);//通过id查询先到数据,SingleOrDefault如果大于1异常,为空返回null
    db.userinfo.Remove(u);//在内存里删除

    var count = db.SaveChanges();//保存到数据库。

    Console.WriteLine($"删除{count}条数据");
}

软删除:通过删除,实现数据隐藏功能,并非真的删除,比如管理员权限操作等等,也可以用此方法。
首先在上下文中配置过滤器

//数据播种,默认添加默认数据,操作实体类的配置方法。
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    //配置软删除过滤器:表示可以查询到年龄为20的数据,其他的就查不到。建议改为:(false和true),懒得写了。
    modelBuilder.Entity<Student>().HasQueryFilter(s => s.age == 20);//可以重新定义一个实现布尔类型的判断。
                                                                          //初始化数据
    modelBuilder.Entity<userinfo>().HasData(new List<userinfo> {
                new userinfo{ usersid = 1,name="张三",time=DateTime.UtcNow },
                new userinfo{ usersid = 2,name="李四",time=DateTime.UtcNow },
                new userinfo{ usersid = 3,name="王五",time=DateTime.UtcNow }
            });
    modelBuilder.Entity<Student>().HasData(new List<Student> {
                new Student{ Studentid=1, usersid = 1,sex=GetSex.男,age = 18 },
                new Student{ Studentid=2, usersid = 2,sex=GetSex.女,age = 19 },
                new Student{ Studentid=3, usersid = 3,sex=GetSex.男,age = 20 }
            });
}

 执行权限修改操作,完成软删除功能

using ConsoleApp1.Data;//上下文的文件夹
using ConsoleApp1.Models;//实体类
using Microsoft.EntityFrameworkCore;//ef框架

using (var db = new OAContext())
{
    var s = db.Student.SingleOrDefault(a => a.Studentid == 2);//通过id查询先到数据,SingleOrDefault如果大于1异常,为空返回null
    s.age = 20;//可以改成布尔值true和false 懒得写代码才用年龄判断。
    db.Student.Update(s);//这里是修改数据权限,不是真的删除数据。

    var count = db.SaveChanges();//保存到数据库。

    Console.WriteLine($"删除{count}条数据");
}

FromSql和ExecuteSql的使用

用来执行常规的SQL语句

using (userContext db = new userContext())
{
    db.Database.ExecuteSql($"insert into uinfo values('张三22')");
    db.Database.ExecuteSql($"delete from uinfo where id=2");
    db.Database.ExecuteSql($"update uinfo set name='王五' where id=1");//用sql语句执行增删改

    var users = db.uinfo.FromSql($"select *from uinfo").ToList();//用SQL语句查询
    foreach(var user in users) Console.WriteLine($"{user.id}, {user.name}");
}

为了避免SQL注入建议使用SqlParameter()对象来传递参数,使用这个对象时框架的方法也修改改为:ExecuteSqlRaw(增删改)和 FromSqlRaw(查)

using (userContext db = new userContext())
{
    IDbContextTransaction trans = null;//1.创建一个事务:作用是当第一个执行完成,第二个出现错误,都会回滚到起点重新来过。
    try
    {
        db.Database.ExecuteSqlRaw($"insert into uinfo values(@name)",new SqlParameter("@name", "张三33"));
        db.Database.ExecuteSqlRaw($"update uinfo set name=@name where id=@id", new[] { new SqlParameter("@name", "张三22"), new SqlParameter("@id", 3) });

        var users = db.uinfo.FromSqlRaw($"select *from uinfo where name like @name", new SqlParameter("@name", "张%")).ToList();//原生SQL语句的模糊查询
        foreach (var user in users) Console.WriteLine($"{user.id}, {user.name}");

        trans?.Commit();//2.提交事务
    }
    catch(Exception ex)
    {
        if(trans !=null)trans?.Rollback();//3.事务回滚,如果用Using包裹的话,可以不需要手动Rollback,走完Using会自动回滚。
    }
    finally
    { 
        trans?.Dispose();//4.销毁事务
    }
}

另外还有有个是多线程异步处理ExecuteSqlRawAsync

posted @ 2022-11-02 02:05  Akai_啊凯  阅读(2566)  评论(0编辑  收藏  举报