.NET EF Core(Entity Framework Core)

news2024/9/21 16:43:35

文章目录

    • EF Core与其他ORM比较
    • EF Core与EF比较
    • Migration数据库迁移
      • 反向工程
      • Migrations其他命令
      • 修改表结构
    • EF Core操作数据库
      • 插入数据
      • 查询数据
      • 修改、删除
      • 其他数据库
    • EF Core实体的配置
      • Data Annotation配置方式
      • Fluent API配置方式
      • Fluent API
    • 通过代码查看EF Core的sql语句
      • 方法1:标准日志
      • 方法2:简单日志
      • 方法3:ToQueryString
    • 悲观并发控制
    • 乐观并发控制:并发令牌
    • 乐观并发控制:RowVersion

EF Core与其他ORM比较

1、Entity Framework Core(EF Core)是微软官方的ORM框架。优点:功能强大、官方支持、生产效率高、力求屏蔽底层数据库差异;缺点:复杂、上手门槛高、不熟悉EFCore的话可能会进坑。
2、Dapper。优点:简单,N分钟即可上手,行为可预期性强;缺点:生产效率低,需要处理底层数据库差异。
3、EF Core是 模型驱动 (Model-Driven)的开发思想,Dapper是 数据库驱动(DataBase-Driven)的开发思想的。没有优劣,只有比较。
4、性能: Dapper等≠性能高;EF Core≠性能差。
5、EF Core是官方推荐、推进的框架,尽量屏蔽底层数据库差异,.NET开发者必须熟悉,根据的项目情况再决定用哪个。

EF Core与EF比较

1、EF有DB First、Model First、Code First。EF Core不支持模型优先,推荐使用代码优先,遗留系统可以使用Scaffold-DbContext来生成代码实现类似DBFirst的效果,但是推荐用Code First
2、EF会对实体上的标注做校验,EF Core追求轻量化,不校验。
3、熟悉EF的话,掌握EFCore会很容易,很多用法都移植过来了。EF Core又增加了很多新东西。
4、EF中的一些类的命名空间以及一些方法的名字在EF Core中稍有不同。
5、EF不再做新特性增加。

Migration数据库迁移

面向对象的ORM开发中,数据库不是程序员手动创建的,而是由Migration工具生成的。关系数据库只是盛放模型数据的一个媒介而已,理想状态下,程序员不用关心数据库的操作。
根据对象的定义变化,自动更新数据库中的表以及表结构的操作,叫做Migration(迁移)。
迁移可以分为多步(项目进化),也可以回滚。

1、Nuget安装Install-Package Microsoft.EntityFrameworkCore.SqlServer,Microsoft.EntityFrameworkCore.Tools
2、搭建工程:
创建实体类Book.cs

public class Book
{
    public long Id { get; set; }//主键
    public string Title { get; set; }//标题
    public DateTime PubTime { get; set; }//发布日期
    public double Price { get; set; }//单价

    public override string ToString()
    {
        return "Id:" + this.Id + ",Title:" + this.Title + ",PubTime" + this.PubTime +",Price" + this.Price + ",AuthorName" + AuthorName;
    }
}

实体配置类:创建实现了IEntityTypeConfiguration接口的实体配置类,配置实体类和数据库表的对应关系

class BookEntityConfig : IEntityTypeConfiguration<Book>
{
	public void Configure(EntityTypeBuilder<Book> builder)
	{
		builder.ToTable("T_Books");
	}
}

创建继承自DbContext的类

internal class MyDbContext: DbContext
{
    public DbSet<Book> Books { get; set; }
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        string connStr = "Server=192.168.1.193;Database=demo1; User=sa;Password=@q123; Trusted_Connection=False;MultipleActiveResultSets=true;TrustServerCertificate=true";
        optionsBuilder.UseSqlServer(connStr);
    }
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        modelBuilder.ApplyConfigurationsFromAssembly(this.GetType().Assembly);
    }

}

2、在“程序包管理器控制台”中执行命令:Add-Migration InitialCreate,会自动在项目的Migrations文件夹中中生成操作数据库的C#代码。
3、代码需要执行后才会应用对数据库的操作。“程序包管理器控制台”中执行Update-database
4、查看一下数据库,表建好了

反向工程

1、根据数据库表来反向生成实体类
2、Scaffold-DbContext 'Server=.;Database=demo1;Trusted_Connection=True;MultipleActiveResultSets=true' Microsoft.EntityFrameworkCore.SqlServer

Tips:1、生成的实体类可能不能满足项目的要求,可能需要手工修改或者增加配置。
2、再次运行反向工程工具,对文件所做的任何更改都将丢失。
3、不建议把反向工具当成了日常开发工具使用,不建议DBFirst。

Migrations其他命令

1、Update-Database XXX :把数据库回滚到XXX的状态,迁移脚本不动。
2、Remove-migration:删除最后一次的迁移脚本
3、Script-Migration:生成迁移SQL代码。
可以生成版本D到版本F的SQL脚本:Script-Migration D F
生成版本D到最新版本的SQL脚本:Script-Migration D
4、通过给Add-Migration命令添加“-OutputDir”参数的形式来在同一个项目中为不同的数据库生成不同的迁移脚本

小结
1、使用迁移脚本,可以对当前连接的数据库执行编号更高的迁移,这个操作叫做“向上迁移”(Up),也可以执行把数据库回退到旧的迁移,这个操作叫“向下迁移”(Down)。
2、除非有特殊需要,否则不要删除Migrations文件夹下的代码。
3、进一步分析Migrations下的代码。分析Up、Down等方法。查看Migration编号。
4、查看数据库的__EFMigrationsHistory表:记录当前数据库曾经应用过的迁移脚本,按顺序排列。

修改表结构

想要限制Title的最大长度为50,Title字段设置为“不可为空”,并且想增加一个不可为空且最大长度为20的AuthorName(作者名字)属性。
1、首先在Book实体类中增加一个AuthorName属性。

2、修改BookEntityConfig

builder.ToTable("T_Books");
builder.Property(e => e.Title).HasMaxLength(50).IsRequired();
builder.Property(e => e.AuthorName).HasMaxLength(20).IsRequired();

3、执行Add-Migration AddAuthorName_ModifyTitle。AddAuthorName_ModifyTitle为本次迁移操作的名称
4、执行:Update-Database

EF Core操作数据库

在这里插入图片描述

插入数据

只要操作Books属性,就可以向数据库中增加数据,但是通过C#代码修改Books中的数据只是修改了内存中的数据。对Books做修改后,需要调用DbContext的异步方法SaveChangesAsync()把修改保存到数据库。也有同步的保存方法SaveChanges(),但是用EF Core都推荐用异步方法。

 static async Task Main(string[] args)
 {
     using (MyDbContext dbContext = new MyDbContext())
     {
         Book book = new Book() { Title = "西游记", Price = 15.5, AuthorName = "吴承恩" };
         dbContext.Books.Add(book);
         await dbContext.SaveChangesAsync();
     }
 }

查询数据

DbSet实现了IEnumerable<T>接口,因此可以对DbSet实施Linq操作来进行数据查询。EF Core会把Linq操作转换为SQL语句。面向对象,而不是面向数据库(SQL)。

IQueryable<Book> books = dbContext.Books.Where(b => b.Price > 20);
foreach (Book book in books)
{
    Console.WriteLine(book);
}
Book b1 = dbContext.Books.Single(b => b.Title == "西游记");
Book b2 = dbContext.Books.FirstOrDefault(b => b.Id == 2);
Console.WriteLine(b1);
Console.WriteLine(b2);
IEnumerable<Book> books2 = dbContext.Books.OrderByDescending(b => b.Price);

var groups = dbContext.Books.GroupBy(b => b.AuthorName)
	.Select(g => new { AuthorName = g.Key, BooksCount = g.Count(), MaxPrice = g.Max(b => b.Price) });
foreach(var g in groups)
{
	Console.WriteLine($"作者名:{g.AuthorName},著作数量:{g.BooksCount},最贵的价格:{g.MaxPrice}");
}

修改、删除

1、要对数据进行修改,首先需要把要修改的数据查询出来,然后再对查询出来的对象进行修改,然后再执行SaveChangesAsync()保存修改。

修改:

 var b = dbContext.Books.Single(b => b.Title == "西游记");
 b.AuthorName = "WuChengEn";
 await dbContext.SaveChangesAsync();

2、删除也是先把要修改的数据查询出来,然后再调用DbSet或者DbContext的Remove方法把对象删除,然后再执行SaveChangesAsync()保存修改。

var b = dbContext.Books.Single(b => b.Id == 1);
dbContext.Remove(b);//也可以写成ctx.Books.Remove(b);
await dbContext.SaveChangesAsync();

开源批量修改删除插件

其他数据库

mysql:
Install-Package Pomelo.EntityFrameworkCore.MySql

optionsBuilder.UseMySql("server=localhost;user=root;password=root;database=ef",
	new MySqlServerVersion(new Version(5, 6, 0)));

PostgreSQL:
Install-Package Npgsql.EntityFrameworkCore.PostgreSQL

optionsBuilder.UseNpgsql("Host=127.0.0.1;Database=ef;Username=postgres;Password=123456");

EF Core实体的配置

主要规则:
1:表名采用DbContext中的对应的DbSet的 属性名
2:数据表列的名字采用实体类属性的名字,列的数据类型采用和实体类属性类型最兼容的类型。
3:数据表列的可空性 取决于对应实体类属性的可空性
4:名字为Id的属性为主键,如果主键为short, int 或者 long类型,则默认采用自增字段,如果主键为Guid类型,则默认采用默认的Guid生成机制生成主键值。

Data Annotation配置方式

把配置以特性(Annotation)的形式标注在实体类中

 [Table("t_books")]
 public class Book
 {
     public long Id { get; set; }//主键
     [Required]
     [MaxLength(50)]
     public string Title { get; set; }//标题
     public DateTime PubTime { get; set; }//发布日期
     public double Price { get; set; }//单价

     public string AuthorName { get; set; }

     public override string ToString()
     {
         return "Id:" + this.Id + ",Title:" + this.Title + ",PubTime" + this.PubTime +",Price" + this.Price + ",AuthorName" + AuthorName;
     }
 }

Fluent API配置方式

把配置写到单独的配置类中

 internal class BookEntityConfig : IEntityTypeConfiguration<Book>
 {
     public void Configure(EntityTypeBuilder<Book> builder)
     {
         builder.ToTable("t_books");
      
         builder.Property(e => e.Title).HasMaxLength(50).IsRequired();
         builder.Property(e => e.AuthorName).HasMaxLength(20).IsRequired();
     }
 }

Fluent API

1、视图与实体类映射:

modelBuilder.Entity<Blog>().ToView("blogsView");

2、排除属性映射:

modelBuilder.Entity<Blog>().Ignore(b => b. Name2);

3、配置列名:

modelBuilder.Entity<Blog>().Property(b =>b.BlogId).HasColumnName("blog_id");

4、配置列数据类型:

builder.Property(e => e.Title) .HasColumnType("varchar(200)");

5、配置主键
默认把名字为Id或者“实体类型+Id“的属性作为主键,可以用HasKey()来配置其他属性作为主键。

modelBuilder.Entity<Student>().HasKey(c => c.Number);

6、可以用HasDefaultValue()为属性设定默认值

modelBuilder.Entity<Student>().Property(b => b.Age).HasDefaultValue(6);

7、索引

modelBuilder.Entity<Blog>().HasIndex(b => b.Url);

复合索引

modelBuilder.Entity<Person>().HasIndex(p => new { p.FirstName, p.LastName });

唯一索引:IsUnique();聚集索引:IsClustered()
8、…

通过代码查看EF Core的sql语句

方法1:标准日志

nuget安装:Install-Package Microsoft.Extensions.Logging.Console

public static readonly ILoggerFactory MyLoggerFactory = LoggerFactory.Create(builder => { builder.AddConsole(); });
optionsBuilder.UseLoggerFactory(MyLoggerFactory);

完整代码:

 internal class MyDbContext: DbContext
 {
     public static readonly ILoggerFactory MyLoggerFactory = LoggerFactory.Create(builder => { builder.AddConsole(); });

     public DbSet<Book> Books { get; set; }
     protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
     {
         string connStr = "Server=192.168.1.193;Database=demo1; User=sa;Password=@q123; Trusted_Connection=False;MultipleActiveResultSets=true;TrustServerCertificate=true";
         optionsBuilder.UseSqlServer(connStr);

         optionsBuilder.UseLoggerFactory(MyLoggerFactory);

     }
     protected override void OnModelCreating(ModelBuilder modelBuilder)
     {
         base.OnModelCreating(modelBuilder);
         modelBuilder.ApplyConfigurationsFromAssembly(this.GetType().Assembly);
     }

 }

在这里插入图片描述

方法2:简单日志

此方式不需要额外的引入Logging框架

optionsBuilder.LogTo(Console.WriteLine);
//可以自己写代码过滤一些不需要的消息

方法3:ToQueryString

EF Core的Where方法返回的是IQueryable类型,DbSet也实现了IQueryable接口。 IQueryable有扩展方法ToQueryString()可以获得SQL

 IQueryable<Book> books = dbContext.Books.Where(b => b.Id == 2);
 string sql = books.ToQueryString();
 Console.WriteLine(sql);

悲观并发控制

悲观并发控制一般采用行锁、表锁等排他锁对资源进行锁定,确保同时只有一个使用者操作被锁定的资源。

MYSQL方案:select * from T_Houses where Id=1 for update
如果有其他的查询操作也使用for update来查询Id=1的这条数据的话,那些查询就会被挂起,一直到针对这条数据的更新操作完成从而释放这个行锁,代码才会继续执行。

锁是和事务相关的,因此通过BeginTransactionAsync()创建一个事务,并且在所有操作完成后调用CommitAsync()提交事务。

Console.WriteLine("请输入您的姓名");
string name = Console.ReadLine();
using MyDbContext ctx = new MyDbContext();
using var tx = await ctx.Database.BeginTransactionAsync();
Console.WriteLine("准备Select " + DateTime.Now.TimeOfDay);
var h1 = await ctx.Houses.FromSqlInterpolated($"select * from T_Houses where Id=1 for update")
	.SingleAsync();
Console.WriteLine("完成Select " + DateTime.Now.TimeOfDay);
if (string.IsNullOrEmpty(h1.Owner))
{
	await Task.Delay(5000);
	h1.Owner = name;
	await ctx.SaveChangesAsync();
	Console.WriteLine("抢到手了");
}
else
{
	if (h1.Owner == name)
	{
		Console.WriteLine("这个房子已经是你的了,不用抢");
	}
	else
	{
		Console.WriteLine($"这个房子已经被{h1.Owner}抢走了");
	}
}
await tx.CommitAsync();
Console.ReadKey();

Tips:悲观锁是独占、排他的,如果系统并发量很大的话,会严重影响性能,如果使用不当的话,甚至会导致死锁。

乐观并发控制:并发令牌

Update T_Houses set Owner=新值
where Id=1 and Owner=旧值

当Update的时候,如果数据库中的Owner值已经被其他操作者更新为其他值了,那么where语句的值就会为false,因此这个Update语句影响的行数就是0,EF Core就知道“发生并发冲突”了,因此SaveChanges()方法就会抛出DbUpdateConcurrencyException异常。

1、把被并发修改的属性使用IsConcurrencyToken()设置为并发令牌。

builder.Property(h => h.Owner).IsConcurrencyToken();

2、示例代码:

Console.WriteLine("请输入您的姓名");
string name = Console.ReadLine();
using MyDbContext ctx = new MyDbContext();
var h1 = await ctx.Houses.SingleAsync(h => h.Id == 1);
if (string.IsNullOrEmpty(h1.Owner))
{
	await Task.Delay(5000);
	h1.Owner = name;
	try
	{
		await ctx.SaveChangesAsync();
		Console.WriteLine("抢到手了");
	}
	catch (DbUpdateConcurrencyException ex)
	{
		var entry = ex.Entries.First();
		var dbValues = await entry.GetDatabaseValuesAsync();
		string newOwner = dbValues.GetValue<string>(nameof(House.Owner));
		Console.WriteLine($"并发冲突,被{newOwner}提前抢走了");
	}
}
else
{
	if (h1.Owner == name)
	{
		Console.WriteLine("这个房子已经是你的了,不用抢");
	}
	else
	{
		Console.WriteLine($"这个房子已经被{h1.Owner}抢走了");
	}
}
Console.ReadLine();

乐观并发控制:RowVersion

SQLServer数据库可以用一个byte[]类型的属性做并发令牌属性,然后使用IsRowVersion()把这个属性设置为RowVersion类型,这样这个属性对应的数据库列就会被设置为ROWVERSION类型。对于ROWVERSION类型的列,在每次插入或更新行时,数据库会自动为这一行的ROWVERSION类型的列其生成新值。

class House
{
	public long Id { get; set; }
	public string Name { get; set; }
	public string Owner { get; set; }
	public byte[] RowVer { get; set; }
}
builder.Property(h => h.RowVer).IsRowVersion();

Tips:乐观并发控制能够避免悲观锁带来的性能、死锁等问题,因此推荐使用乐观并发控制而不是悲观锁。

总结:如果有一个确定的字段要被进行并发控制,那么使用IsConcurrencyToken()把这个字段设置为并发令牌即可;如果无法确定一个唯一的并发令牌列,那么就可以引入一个额外的属性设置为并发令牌,并且在每次更新数据的时候,手动更新这一列的值。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1535282.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

分布式链上随机数和keyless account

1. 引言 相关论文见&#xff1a; Aptos团队2024年论文 Distributed Randomness using Weighted VRFs 相关代码实现见&#xff1a; https://github.com/aptos-labs/aptos-core&#xff08;Rust&#xff09; 在链中生成和集成共享随机数&#xff0c;以扩展应用和强化安全。该…

基于python+vue体育场馆设施预约系统flask-django-php-nodejs

时代在飞速进步&#xff0c;每个行业都在努力发展现在先进技术&#xff0c;通过这些先进的技术来提高自己的水平和优势&#xff0c;体育场馆设施预约系统当然不能排除在外。整体设计包括系统的功能、系统总体结构、系统数据结构和对系统安全性进行设计&#xff1b;最后要对系统…

Tomcat启动报错 因为在清除过期缓存条目后可用空间仍不足

在Tomcat部署路径下的./conf/context.xml配置文件的标签内添加如下内容&#xff1a; <Resources cachingAllowed"true" cacheMaxSize"100000" />

图解 LFU 缓存淘汰算法以及在 Redis 中的应用(附带个人完整代码实现)

文章目录 LFU 算法理论介绍算法实现数据结构查询操作插入/更新操作 Redis 缓存淘汰算法缓存污染难题Redis LFU缓存淘汰策略 本篇博客的主要内容&#xff1a; 以图解的方式&#xff0c;介绍 LFU 算法的一种实现&#xff1b;介绍 LFU 算法在 Redis 中的应用。 LFU 算法 理论介…

Docker 搭建私人仓库

docker 搭建私人仓库有下面几种方式&#xff1a; 1、docker hub 官方私人镜像仓库2、本地私有仓库 官方私人镜像仓库搭建很简单(就是需要有魔法&#xff0c;否则就异步到第二种方法吧)&#xff0c;只需要 login、pull、tag、push 几种命令就完事了。而本地私人镜像仓库则比较麻…

探究BufferedOutputStream的奥秘

咦咦咦&#xff0c;各位小可爱&#xff0c;我是你们的好伙伴——bug菌&#xff0c;今天又来给大家普及Java IO相关知识点了&#xff0c;别躲起来啊&#xff0c;听我讲干货还不快点赞&#xff0c;赞多了我就有动力讲得更嗨啦&#xff01;所以呀&#xff0c;养成先点赞后阅读的好…

[音视频学习笔记]六、自制音视频播放器Part1 -新版本ffmpeg,Qt +VS2022,都什么年代了还在写传统播放器?

前言 参考了雷神的自制播放器项目&#xff0c;100行代码实现最简单的基于FFMPEGSDL的视频播放器&#xff08;SDL1.x&#xff09; 不过老版本的代码参考意义不大了&#xff0c;我现在准备使用Qt VS2022 FFmpeg59重写这部分代码&#xff0c;具体的代码仓库如下&#xff1a; …

本地化语音识别、视频翻译和配音工具:赋能音频和视频内容处理

随着人工智能技术的飞速发展&#xff0c;语音识别、视频翻译和配音等任务已经变得更加容易和高效。然而&#xff0c;许多现有的工具和服务仍然依赖于互联网连接&#xff0c;这可能会导致延迟、隐私问题和成本问题。为了克服这些限制&#xff0c;我们介绍了一种本地化、离线运行…

RCE漏洞

RCE漏洞概述 远程命令执行/代码注入漏洞&#xff0c;英文全称为Reote Code/CommandExecute&#xff0c;简称RCE漏洞。PHPJava等Web开发语言包含命令执行和代码执行函数,攻击者可以直接向后台服务器远程执行操作系统命今或者运行注入代码&#xff0c;进而获取系统信息、控制后台…

社交媒体的未来:探讨Facebook的发展趋势

引言 在数字化时代&#xff0c;社交媒体已经成为人们日常生活中不可或缺的一部分。作为全球最大的社交媒体平台之一&#xff0c;Facebook一直在不断地追求创新&#xff0c;以满足用户日益增长的需求和适应科技发展的变革。本文将探讨Facebook在未来发展中可能面临的挑战和应对…

10W字解析 SpringBoot技术内幕文档,实战+原理齐飞,spring事务实现原理面试

第3章&#xff0c;Spring Boot构造流程源码分析&#xff0c;Spring Boot的启动非常简单&#xff0c;只需执行一个简单的main方法即可&#xff0c;但在整个main方法中&#xff0c;Spring Boot都做了些什么呢&#xff1f;本章会为大家详细讲解Spring Boot启动过程中所涉及的源代码…

Linux下Docker部署中间件(Mysql、Redis、Nginx等)

我的自备文件 文件传输 内网下直接上传很慢 使用scp命令将另一台服务器上的文件传输过来&#xff1b;在已有文件的服务器往没有文件的服务器传输 scp -r 传输的文件夹/文件 root要传输的地址:放置的地址 scp -r tools root172.xx.x.xxx:/data/ 安装二进制文件、脚本及各中间件…

《深入解析 C#》—— C# 3 部分

文章目录 第三章 C#3&#xff1a;LINQ及相关特性3.1 自动实现属性&#xff08;*&#xff09;3.2 隐式类型 var&#xff08;*&#xff09;3.3 对象和集合初始化3.3.1 对象初始化器3.3.2 集合初始化器 3.4 匿名类型3.4.1 基本语法和行为3.4.2 编译器生成类型3.4.3 匿名类型的局限…

Hive和Hadoop版本对应关系

通过 Downloads (apache.org) 即可查看

MySQL的基本操作

目录 引言 一、SQL语句简介 &#xff08;一&#xff09;SQL通用语法 &#xff08;二&#xff09;SQL分类 &#xff08;三&#xff09;数据类型 1.数值类型 2.字符串类型 3.日期/时间类型 4.修饰符 二、登录mysql服务 三、SQL语句操作 &#xff08;一&#xff09;DD…

vue3 + ts +element-plus + vue-router + scss + axios搭建项目

本地环境&#xff1a; node版本&#xff1a;20.10.0 目录 一、搭建环境 二、创建项目 三、修改页面 四、封装路由vue-router 五、element-plus 六、安装scss 七、封装axios 一、搭建环境 1、安装vue脚手架 npm i -g vue/cli 2、查看脚手架版本 vue -V3、切换路径到需…

Studio One 6 Mac中文版破解版下载(附Mac版注册机)

Studio One 6 Mac版是一款强大的音乐创作与制作软件&#xff0c;其可通过更简单的方式来录制音频及进行MIDI制作&#xff0c;并提供丰富的专业功能。它具备音乐创作、录音混缩、MIDI编辑、音频处理、Loops拼接、视频配乐和母带与专辑制作等功能。软件提供了强大的音频性能&…

在iOS中安装

返回&#xff1a;OpenCV系列文章目录&#xff08;持续更新中......&#xff09; 上一篇&#xff1a;使用CUDA 为Tegra构建OpenCV-CSDN博客 下一篇&#xff1a; 警告&#xff01; 本教程可以包含过时的信息。 所需软件包 CMake 2.8.8 或更高版本Xcode 4.2 或更高版本 从 G…

笔试总结01

1、spring原理 1、spring原理 spring的最大作用ioc/di,将类与类的依赖关系写在配置文件中&#xff0c;程序在运行时根据配置文件动态加载依赖的类&#xff0c;降低的类与类之间的藕合度。它的原理是在applicationContext.xml加入bean标记,在bean标记中通过class属性说明具体类…

旅游小程序的市场与发展趋势

随着科技的发展&#xff0c;移动互联网已经成为我们生活中不可或缺的一部分。在这个时代&#xff0c;小程序已经成为了一种新的趋势&#xff0c;尤其是在旅游行业。那么&#xff0c;旅游小程序有哪些市场&#xff0c;发展趋势又怎么样呢&#xff1f; 一、旅游小程序的市场 1. 用…