草庐IT

Net6 EFcore框架介绍

wskxy 2023-03-28 原文

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<Staff> Staffs { get; set; }
        public DbSet<Tenant> 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<Staff> Staffs { get; set; }
        public DbSet<Tenant> 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<Tenant>().Property(x=>x.Description).IsRequired(false); /*指定Description非必填*/
        }
    }
}

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

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

using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace UserModel
{
    public class TenantConfig : IEntityTypeConfiguration<Tenant>
    {
        public void Configure(EntityTypeBuilder<Tenant> 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<Staff> Staffs { get; set; }
        public DbSet<Tenant> 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<Staff> Staffs { get; set; }
        public DbSet<Tenant> 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<Staff>
    {
        public void Configure(EntityTypeBuilder<Staff> builder)
        {
            builder.ToTable("Staff");
            builder.HasKey(x => x.Id);
            builder.Property(x => x.Name).IsRequired().HasColumnType("nvarchar(100)");
            builder.HasOne<Mall>(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<Staff> Staffs { get; set; } //加一个导航属性
    }
}

    StaffConfig 

using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace UserModel
{
    public class StaffConfig : IEntityTypeConfiguration<Staff>
    {
        public void Configure(EntityTypeBuilder<Staff> builder)
        {
            builder.ToTable("Staff");
            builder.HasKey(x => x.Id);
            builder.Property(x => x.Name).IsRequired().HasColumnType("nvarchar(100)");
            builder.HasOne<Mall>(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<Staff>();
    IEnumerable<Staff> staffs1 = list.Where(x => x.Id == 1);
    //EF语法的LINQ返回IQueryable
    var ctx = new MyDBContext();
    IQueryable<Staff> 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<Staff> 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的时候执行的

  这就是延迟访问。。

 

 

 

 

 

 

  致辞

  感谢关注!!

 

 

 

 

 

 

  

 

有关Net6 EFcore框架介绍的更多相关文章

  1. ruby-on-rails - Ruby net/ldap 模块中的内存泄漏 - 2

    作为我的Rails应用程序的一部分,我编写了一个小导入程序,它从我们的LDAP系统中吸取数据并将其塞入一个用户表中。不幸的是,与LDAP相关的代码在遍历我们的32K用户时泄漏了大量内存,我一直无法弄清楚如何解决这个问题。这个问题似乎在某种程度上与LDAP库有关,因为当我删除对LDAP内容的调用时,内存使用情况会很好地稳定下来。此外,不断增加的对象是Net::BER::BerIdentifiedString和Net::BER::BerIdentifiedArray,它们都是LDAP库的一部分。当我运行导入时,内存使用量最终达到超过1GB的峰值。如果问题存在,我需要找到一些方法来更正我的代

  2. ruby - 如何模拟 Net::HTTP::Post? - 2

    是的,我知道最好使用webmock,但我想知道如何在RSpec中模拟此方法:defmethod_to_testurl=URI.parseurireq=Net::HTTP::Post.newurl.pathres=Net::HTTP.start(url.host,url.port)do|http|http.requestreq,foo:1endresend这是RSpec:let(:uri){'http://example.com'}specify'HTTPcall'dohttp=mock:httpNet::HTTP.stub!(:start).and_yieldhttphttp.shou

  3. ruby - Net::HTTP 获取源代码和状态 - 2

    我目前正在使用以下方法获取页面的源代码:Net::HTTP.get(URI.parse(page.url))我还想获取HTTP状态,而无需发出第二个请求。有没有办法用另一种方法做到这一点?我一直在查看文档,但似乎找不到我要找的东西。 最佳答案 在我看来,除非您需要一些真正的低级访问或控制,否则最好使用Ruby的内置Open::URI模块:require'open-uri'io=open('http://www.example.org/')#=>#body=io.read[0,50]#=>"["200","OK"]io.base_ur

  4. Unity 热更新技术 | (三) Lua语言基本介绍及下载安装 - 2

    ?博客主页:https://xiaoy.blog.csdn.net?本文由呆呆敲代码的小Y原创,首发于CSDN??学习专栏推荐:Unity系统学习专栏?游戏制作专栏推荐:游戏制作?Unity实战100例专栏推荐:Unity实战100例教程?欢迎点赞?收藏⭐留言?如有错误敬请指正!?未来很长,值得我们全力奔赴更美好的生活✨------------------❤️分割线❤️-------------------------

  5. Get https://registry-1.docker.io/v2/: net/http: request canceled while waiting - 2

    1.错误信息:Errorresponsefromdaemon:Gethttps://registry-1.docker.io/v2/:net/http:requestcanceledwhilewaitingforconnection(Client.Timeoutexceededwhileawaitingheaders)或者:Errorresponsefromdaemon:Gethttps://registry-1.docker.io/v2/:net/http:TLShandshaketimeout2.报错原因:docker使用的镜像网址默认为国外,下载容易超时,需要修改成国内镜像地址(首先阿里

  6. TimeSformer:抛弃CNN的Transformer视频理解框架 - 2

    Transformers开始在视频识别领域的“猪突猛进”,各种改进和魔改层出不穷。由此作者将开启VideoTransformer系列的讲解,本篇主要介绍了FBAI团队的TimeSformer,这也是第一篇使用纯Transformer结构在视频识别上的文章。如果觉得有用,就请点赞、收藏、关注!paper:https://arxiv.org/abs/2102.05095code(offical):https://github.com/facebookresearch/TimeSformeraccept:ICML2021author:FacebookAI一、前言Transformers(VIT)在图

  7. .net - .NET 将如何影响 Python 和 Ruby 应用程序? - 2

    我很好奇.NET将如何影响Python和Ruby应用程序。用IronPython/IronRuby编写的应用程序是否会非常特定于.NET环境,以至于它们实际上将变得特定于平台?如果他们不使用任何.NET功能,那么IronPython/IronRuby相对于非.NET同类产品的优势是什么? 最佳答案 我不能说任何关于IronRuby的东西,但是大多数Python实现(如IronPython、Jython和PyPy)都试图尽可能忠实于CPython实现。不过,IronPython正在迅速成为这方面的佼佼者之一,并且在PlanetPyth

  8. ruby - 如何使用 Ruby HTTP::Net 处理 404 错误? - 2

    我正在尝试解析网页,但有时会收到404错误。这是我用来获取网页的代码:result=Net::HTTP::getURI.parse(URI.escape(url))如何测试result是否为404错误代码? 最佳答案 像这样重写你的代码:uri=URI.parse(url)result=Net::HTTP.start(uri.host,uri.port){|http|http.get(uri.path)}putsresult.codeputsresult.body这将打印状态码和正文。

  9. ruby - sinatra 框架的 MVC 模式 - 2

    我想开始使用“Sinatra”框架进行编码,但我找不到该框架的“MVC”模式。是“MVC-Sinatra”模式或框架吗? 最佳答案 您可能想查看Padrino这是一个围绕Sinatra构建的框架,可为您的项目提供更“类似Rails”的感觉,但没有那么多隐藏的魔法。这是使用Sinatra可以做什么的一个很好的例子。虽然如果您需要开始使用这很好,但我个人建议您将它用作学习工具,以对您来说最有意义的方式使用Sinatra构建您自己的应用程序。写一些测试/期望,写一些代码,通过测试-重复:)至于ORM,你还应该结帐Sequel其中(imho

  10. .net - 是否有 Ruby .NET 编译器? - 2

    是否有适用于Ruby语言的.NETFramework编译器?我听说过DLR(动态语言运行时),这是否将使Ruby能够用于.NET开发? 最佳答案 IronRuby是Microsoft支持的项目,建立在动态语言运行时之上。 关于.net-是否有Ruby.NET编译器?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/199638/

随机推荐