• Net6 EFcore框架介绍


    1、简介

      EFcore,可用使得开发人员不需要再去关注数据库的实现,全都由代码进行生成

      这样有利于减少工作量、数据库快速迁移...

    2、上手搭建架构

      

      (这个图是做完本章内容的完整图,我们一步步深入即可)

      在写EF之前,先安装好数据库,我选择在本地安装Sqlserver

     

      我们先执行最核心的两步,将EF和数据库跑通

      1)类&表的定义:基本上会保持class和数据库的table字段保持一致,如上UserModel,我定义了Staff、Tenant两个类,会自动生成两个表

        UserModel需要安装

    Microsoft.EntityFrameworkCore.SqlServer

        Staff

    复制代码
    using System.ComponentModel.DataAnnotations;
    using System.ComponentModel.DataAnnotations.Schema;
    namespace UserModel
    {
        public class Staff
        {
            public int Id { get; set; }public string Name { get; set; }
            public string Description { get; set; }
            public string? PhoneNumber { get; set; }
            public string? Email { get; set; }
        }
    }
    复制代码

        Tenant

    复制代码
    namespace UserModel
    {
        public class Tenant
        {
            public int Id { get; set; }
            public string Name { get; set; }
            public string Description { get; set; }
        }
    }
    复制代码

      2)上下文定义:负责关联实体类、访问数据库配置,提供后续生成数据库支持,如上MyDBContextLibrary

      MyDBContextLibrary需要安装

    Microsoft.EntityFrameworkCore.Tools

      MyDBContext

    复制代码
    using Microsoft.EntityFrameworkCore;
    namespace UserModel
    {
        public class MyDBContext : DbContext
        {
            public DbSet Staffs { get; set; }
            public DbSet Tenants { get; set; }
            protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
            {
                base.OnConfiguring(optionsBuilder);
                optionsBuilder.UseSqlServer("Data Source=.;Initial Catalog=master;Integrated Security=True;TrustServerCertificate=yes");
            }
        }
    }
    复制代码

      准备完毕!!

      打开【程序包管理器控制台】

      项目指定到MyDBContext

      

    复制代码
    Add-Migration Ini   #添加一个迁移  Ini是为这个迁移起的备注名
    Update-database  #更新到数据库,执行了才会同步迁移到数据库
    =======================================================
    #扩展
    Update-datebase A #指定更新到A脚本
    Remove-migration #删除最近一次迁移脚本
    Script-migration #生成脚本对应的SQL,可手动到数据库执行,Update-datebase命令就是执行这串代码更新数据库的
               #疑问来了:为啥需要这个?答:正常Update-database适合更新开发环境,生产环境需要生产SQL脚本来更新
    Script-migration A B #生成版本A更新到版本B的SQL脚本
    Script-migration A #生成版本A更新到最新的SQL脚本
    复制代码

      到此,简单的EF框架已经跑起来了

    3、扩展

      EF是一个十分强大的框架,我们逐渐扩展知识点。

      1)属性定义

      有两种方式

      其一:Data Annotations(数据注解),利用特性进行定义,如对Staff属性进行定义

    复制代码
    using System.ComponentModel.DataAnnotations;
    using System.ComponentModel.DataAnnotations.Schema;
    //Data Annotations例子
    namespace UserModel
    {
        [Table("Staff")]//可用加特性指定表名
        public class Staff
        {
            public int Id { get; set; }
            [Required]//必填
            [MaxLength(10)]//最大长度为10
            public string Name { get; set; }
            [Required]
            public string Description { get; set; }
            public string? PhoneNumber { get; set; } //可空
            public string? Email { get; set; }
        }
    }
    复制代码

      PS:提醒一点,Id / 类名+Id  在迁移到数据库表的时候,会默认为递增序列

      其二:Fluent API,微软官方提供的API,如对Tenant属性进行定义

      在MyDBContext,重写OnModelCreating方法

    复制代码
    using Microsoft.EntityFrameworkCore;
    namespace UserModel
    {
        public class MyDBContext : DbContext
        {
            public DbSet Staffs { get; set; }
            public DbSet Tenants { get; set; }
    
            protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
            {
                base.OnConfiguring(optionsBuilder);
                optionsBuilder.UseSqlServer("Data Source=.;Initial Catalog=master;Integrated Security=True;TrustServerCertificate=yes");
            }
            protected override void OnModelCreating(ModelBuilder modelBuilder)
            {
                base.OnModelCreating(modelBuilder);modelBuilder.Entity().Property(x=>x.Description).IsRequired(false); /*指定Description非必填*/
            }
        }
    }
    复制代码

      当然,我们容易看到,如果实体很多,属性直接写在这里代码太冗长了

      改变一下方法,添加一个TenantConfig类

    复制代码
    using Microsoft.EntityFrameworkCore;
    using Microsoft.EntityFrameworkCore.Metadata.Builders;
    namespace UserModel
    {
        public class TenantConfig : IEntityTypeConfiguration
        {
            public void Configure(EntityTypeBuilder builder)
            {
                builder.ToTable("Tenant");//可重新指定表名
                builder.HasKey(x => x.Id);
                builder.Property(x=>x.Name).IsRequired().HasColumnType("nvarchar(100)");
                builder.Property(x=>x.Description).IsRequired(false);
            }
        }
    }
    复制代码

      然后  DbContext:

    复制代码
    using Microsoft.EntityFrameworkCore;
    namespace UserModel
    {
        public class MyDBContext : DbContext
        {
            public DbSet Staffs { get; set; }
            public DbSet Tenants { get; set; }
    
            protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
            {
                base.OnConfiguring(optionsBuilder);
                optionsBuilder.UseSqlServer("Data Source=.;Initial Catalog=master;Integrated Security=True;TrustServerCertificate=yes");
            }
            protected override void OnModelCreating(ModelBuilder modelBuilder)
            {
                base.OnModelCreating(modelBuilder);modelBuilder.ApplyConfigurationsFromAssembly(typeof(Tenant).Assembly); //利用反射,加载Tenant程序集下的IEntityTypeConfiguration
            }
        }
    }
    复制代码

      完成,再次生成一个迁移到数据库看看!!!

      代码不会一步到位的,大家逐步测试严重,这边我就不贴数据库的截图了

    4、然后说明一下ConsoleApp

      Program

    复制代码
    using UserModel;
    using(var ctx = new MyDBContext())
    {
        var s = new Staff()
        {
            Name = "kxy2",
            Description = "三好员工",
            PhoneNumber = "1234567890"
        };
        ctx.Staffs.Add(s);
    
        var t = new Tenant()
        {
            Name = "ccc",
        };
        ctx.Tenants.Add(t);
        ctx.SaveChanges();
    }
    Console.ReadLine();
    复制代码

      测试数据而已,怎么方便怎么来

      PS:有个点,如果设置ConsoleApp为启动项,迁移的时候会验证启动项的依赖,从而产生错误

      ConsoleApp需要安装

    Microsoft.EntityFrameworkCore.Design

      至此,完成!!

    5、日志输出

      EF会翻译成SQL,然后到数据库执行,尝试一下在控制台输出EF的SQL代码。

      安装nuget包

    Microsoft.Extensions.Logging.Console

      MyDBContext,添加

    复制代码
    using Microsoft.EntityFrameworkCore;
    using Microsoft.Extensions.Logging;
    
    namespace UserModel
    {
        public class MyDBContext : DbContext
        {
            private static ILoggerFactory _loggerFactory = LoggerFactory.Create(build =>
            {
                build.AddConsole();
            });
            public DbSet Staffs { get; set; }
            public DbSet Tenants { get; set; }
    
            protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
            {
                base.OnConfiguring(optionsBuilder);
                optionsBuilder.UseSqlServer("Data Source=.;Initial Catalog=master;Integrated Security=True;TrustServerCertificate=yes");
                optionsBuilder.UseLoggerFactory(_loggerFactory);
            }
            protected override void OnModelCreating(ModelBuilder modelBuilder)
            {
                base.OnModelCreating(modelBuilder);
                modelBuilder.ApplyConfigurationsFromAssembly(typeof(Tenant).Assembly); //指定其他类库下的程序集
            }
        }
    }
    复制代码

      完成,执行一下代码,查看输出

      

     6.导航属性

      EF有单向导航属性和双向导航属性两种,主要是方便EF框架本身的操作,对数据库表结构并没有影响

      究其本质,就是希望通过一个主体查询/新增另一个主体的信息

      1)单向导航属性:我们要求查出一个员工,并查看该员工所属商场

        定义 Mall 

    复制代码
    namespace UserModel
    {
        public class Mall
        {
            public int Id { get; set; }
            public string Name { get; set; }
        }
    }
    复制代码

        定义 Staff

    复制代码
    namespace UserModel
    {
        public class Staff
        {
            public int Id { get; set; }
            public string Name { get; set; }
            public string? Description { get; set; }
            public string? PhoneNumber { get; set; }
            public string? Email { get; set; }
            public Mall TheMall { get; set; }
        }
    }
    复制代码

        StaffConfig 定义单向导航

    复制代码
    using Microsoft.EntityFrameworkCore;
    using Microsoft.EntityFrameworkCore.Metadata.Builders;
    namespace UserModel
    {
        public class StaffConfig : IEntityTypeConfiguration
        {
            public void Configure(EntityTypeBuilder builder)
            {
                builder.ToTable("Staff");
                builder.HasKey(x => x.Id);
                builder.Property(x => x.Name).IsRequired().HasColumnType("nvarchar(100)");
                builder.HasOne(x => x.TheMall).WithMany().IsRequired();
            }
        }
    }
    复制代码

        这样做的好处是什么?

        新增的时候,可用指定该员工所属商场,所以这是必要的:

    复制代码
        var staff = new Staff()
        {
            Name = "员工3",
            //TheMall = new Mall() { Id = 1 },//new的话会新增一个mall
            TheMall = ctx.Mall.FirstOrDefault(x => x.Id == 1)
        };
        ctx.Staffs.Add(staff);
        ctx.SaveChanges();
    复制代码

        查询:可用通过一条SQL查询出员工1信息及所属商场

        var staff = ctx.Staffs.Include(x=>x.TheMall).FirstOrDefault(x => x.Id == 1);
        Console.WriteLine($"员工:{staff.Id}-{staff.Name},在商场{staff.TheMall.Name}");

        结果如图:(其实就是告诉SQL进行Join操作)

      2)双向导航属性:那如果,我们要通过商场查员工呢

        Mall

    复制代码
    namespace UserModel
    {
        public class Mall
        {
            public int Id { get; set; }
            public string Name { get; set; }
            public List Staffs { get; set; } //加一个导航属性
        }
    }
    复制代码

        StaffConfig 

    复制代码
    using Microsoft.EntityFrameworkCore;
    using Microsoft.EntityFrameworkCore.Metadata.Builders;
    namespace UserModel
    {
        public class StaffConfig : IEntityTypeConfiguration
        {
            public void Configure(EntityTypeBuilder builder)
            {
                builder.ToTable("Staff");
                builder.HasKey(x => x.Id);
                builder.Property(x => x.Name).IsRequired().HasColumnType("nvarchar(100)");
                builder.HasOne(x => x.TheMall).WithMany(x => x.Staffs).IsRequired();
            }
        }
    }
    复制代码

        查询商场ID=1的所有员工

    复制代码
        var mall = ctx.Mall.Include(x => x.Staffs).Single(x => x.Id == 1);
        Console.WriteLine("查询出商场及其所有员工 \n");
        foreach (var staff in mall.Staffs)
        {
            Console.WriteLine($"商场:{mall.Id}-{mall.Name},有员工{staff.Id}-{staff.Name} \n");
        }
    复制代码

        结果如图:告诉SQL,进行子查询

         也可以这样写

    复制代码
        var staffs = ctx.Staffs.Include(x=>x.TheMall).Where(x => x.TheMall.Id== 1);
        foreach (var staff in staffs)
        {
            Console.WriteLine($"商场:{staff.TheMall.Id}-{staff.TheMall.Name},有员工{staff.Id}-{staff.Name} \n");
        }
    复制代码

        结果:(这样写语法效率更好,避免了子查询)

         总结:不管是单向还是双向导航,都是希望通过一个主体带出/新增另一个主体的信息。

    7、IEnumerable和IQueryable

    复制代码
        //普通List的LINQ返回IEnumerable
        var list = new List();
        IEnumerable staffs1 = list.Where(x => x.Id == 1);
        //EF语法的LINQ返回IQueryable
        var ctx = new MyDBContext();
        IQueryable staffs2 = ctx.Staffs.Where(x => x.Id== 1);
    复制代码

      Enumerable:可枚举的

      Queryable:可查询的

      IQueryable对比IEnumerable,没有功能上的扩充,EF为什么大费周章的引进IQueryable呢!

      答:为了加入了延迟访问数据库机制,IQueryable相当于一个可访问数据库对象,而不是立马去访问数据库,当IQueryable遇到终结方法的时候,才会执行数据库操作

    非终结方法:Where()、GroupBy()、OrderBy()、Inclede()、Skip()、Take()等数据库操作
    终结方法:ToList()、ToArray()、Min()、Max()、Count()等非数据操作或统计操作
    总结:返回结果非IQueryable的时候,就会到数据库执行

      PS:执行For循环读取数据的时候,也是终结方法,示例:

    复制代码
        //EF语法的LINQ返回IQueryable
        IQueryable staffs2 = ctx.Staffs.Include(x=>x.TheMall).Where(x => x.Id > 0);
        Console.WriteLine("还没进入foreach");
        foreach (var staff in staffs2)
        {
            Console.WriteLine("进入foreach");
            Console.WriteLine($"商场:{staff.TheMall.Id}-{staff.TheMall.Name},有员工{staff.Id}-{staff.Name} \n");
        }
    复制代码

      可见,访问数据库是在需要for循环staffs2的时候执行的

      这就是延迟访问。。

     8、事务域

      一个域内,只有执行了Complete,才会提交事务

      如果多个事务中有一个发生错误,所有事务都会一起回滚

    复制代码
    using(TransactionScope tx=new TransactionScope())
    {
        var staff = new Staff()
        {
            Name = "员工",
            TheMall = ctx.Mall.FirstOrDefault(x => x.Id == 1)
        };
        ctx.Staffs.Add(staff);
        ctx.SaveChanges(); //这是一个事务
        var staff3 = new Staff()
        {
            Name = "员工333333333333333333333333333333333",//名字长度超过允许最大长度
            TheMall = ctx.Mall.FirstOrDefault(x => x.Id == 1)
        };
        ctx.Staffs.Add(staff3);
        ctx.SaveChanges(); //这是另一个事务
        tx.Complete();
    }
    复制代码

      PS:异步情况需要指定变量存储位置

      ThreadLocal:当前线程全局变量上下文

      AsyncLocal:当前异步流全局变量上下文

      域内有异步函数,需要加参数,告诉TransactionScope把事务信息保存在AsyncLocal

    using(TransactionScope tx=new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))

     

     

     

     

     

     

     

     

     

     

     

     

     

      

     

  • 相关阅读:
    C# FileSystemWatcher 多文件夹、多文件类型文件监控增加、修改、重命名和删除实例
    手把手带你上手swagger3
    易拓SAP上新季 | SAP 分析云 2022年第四季度更新 TOP5 新功能速递
    Golang 优化之内存对齐
    电脑无线网络不显示网络列表应该如何解决
    生产管理中,如何做好生产进度控制?
    Java面试题:SimpleDateFormat是线程安全的吗?使用时应该注意什么?
    MD5加密算法
    想玩配音的小伙伴,赶快来试试这个款配音神器~
    如何理解Linux文件IO?
  • 原文地址:https://www.cnblogs.com/wskxy/p/17179491.html