写在开头

2018年11月的某一天,头脑发热开启了 FreeSql 开源项目之旅,时间一晃已经四年多,当初从舒服区走向一个巨大的坑,回头一看后背一凉。四年时间从无到有,经历了数不清的日夜奋战(有人问我花了多长时间投入,答案:全职x2 + 前两年无休息,以及后面两年的持续投入)。今天 FreeSql 已经很强大,感谢第一期、第二期、第N期持续提出建议的网友。

FreeSql 现如今已经是一个稳定的版本,主要体现:

  • API 已经确定,不会轻易推翻重作调整,坚持十年不变的原则,让使用者真真正正的不再关心 ORM 使用问题;
  • 单元测试覆盖面广,6336+ 个单元测试,小版本更新升级无须考虑修东墙、补西墙的问题;
  • 经历四年时间的生产考验,nuget下载量已超过900K+,平均每日750+;

感叹:有些人说 .Net 陷入 orm 怪圈,动手的没几个,指点江山的一堆,.Net orm 真的如他们所讲的简单吗?


项目介绍

FreeSql 是 .Net ORM,能支持 .NetFramework4.0+、.NetCore、Xamarin、MAUI、Blazor、以及还有说不出来的运行平台,因为代码绿色无依赖,支持新平台非常简单。目前单元测试数量:6336+,Nuget下载数量:900K+。QQ群:4336577(已满)、8578575(在线)、52508226(在线)

温馨提醒:以下内容无商吹成份,FreeSql 不打诳语

为什么要重复造轮子?

FreeSql 主要优势在于易用性上,基本是开箱即用,在不同数据库之间切换兼容性比较好。作者花了大量的时间精力在这个项目,肯请您花半小时了解下项目,谢谢。FreeSql 整体的功能特性如下:

  • 支持 CodeFirst 对比结构变化迁移;
  • 支持 DbFirst 从数据库导入实体类;
  • 支持 丰富的表达式函数,自定义解析;
  • 支持 批量添加、批量更新、BulkCopy;
  • 支持 导航属性,贪婪加载、延时加载、级联保存;
  • 支持 读写分离、分表分库,租户设计;
  • 支持 MySql/SqlServer/PostgreSQL/Oracle/Sqlite/Firebird/达梦/神通/人大金仓/翰高/MsAccess Ado.net 实现包,以及 Odbc 的专门实现包;

5500+个单元测试作为基调,支持10多数数据库,我们提供了通用Odbc理论上支持所有数据库,目前已知有群友使用 FreeSql 操作华为高斯、mycat、tidb 等数据库。安装时只需要选择对应的数据库实现包:

dotnet add packages FreeSql.Provider.MySql

FreeSql.Repository 是 FreeSql 项目的延申扩展类库,支持 .NETFramework4.0+、.NETCore2.0+、.NET5+、Xamarin 平台。

FreeSql.Repository 除了 CRUD 还有很多实用性功能,不防耐下心花10分钟看完。


01 安装

环境1:.NET Core 或 .NET 5.0+

dotnet add package FreeSql.Repository

环境2、.NET Framework

Install-Package FreeSql.DbContext
static IFreeSql fsql = new FreeSql.FreeSqlBuilder()
.UseConnectionString(FreeSql.DataType.Sqlite, connectionString)
.UseAutoSyncStructure(true) //自动迁移实体的结构到数据库
.Build(); //请务必定义成 Singleton 单例模式

02 使用方法

方法1、IFreeSql 的扩展方法;

var curd = fsql.GetRepository<Topic>();

注意:Repository 对象多线程不安全,因此不应在多个线程上同时对其执行工作。

  • fsql.GetRepository 方法返回新仓储实例
  • 不支持从不同的线程同时使用同一仓储实例

以下为了方便测试代码演示,我们都使用方法1,fsql.GetRepository 创建新仓储实例


方法2、继承实现;

public class TopicRepository: BaseRepository<Topic, int> {
public TopicRepository(IFreeSql fsql) : base(fsql, null, null) {} //在这里增加 CURD 以外的方法
}

方法3、依赖注入;

public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IFreeSql>(Fsql);
services.AddFreeRepository(null, this.GetType().Assembly);
} //在控制器使用
public TopicController(IBaseRepository<Topic> repo) {
}

03 添加数据

repo.Insert 插入数据,适配了各数据库优化执行 ExecuteAffrows/ExecuteIdentity/ExecuteInserted

1、如果表有自增列,插入数据后应该要返回 id。

var repo = fsql.GetRepository<Topic>();
repo.Insert(topic);

内部会将插入后的自增值填充给 topic.Id

2、批量插入

var repo = fsql.GetRepository<Topic>();
var topics = new [] { new Topic { ... }, new Topic { ... } };
repo.Insert(topics);

3、插入数据库时间

使用 [Column(ServerTime = DateTimeKind.Utc)] 特性,插入数据时,使用适配好的每种数据库内容,如 getutcdate()

4、插入特殊类型

使用 [Column(RereadSql = "{0}.STAsText()", RewriteSql = "geography::STGeomFromText({0},4236)")] 特性,插入和读取时特别处理

5、插入时忽略

使用 [Column(CanInsert = false)] 特性


04 更新数据

1、只更新变化的属性

var repo = fsql.GetRepository<Topic>();
var item = repo.Where(a => a.Id == 1).First(); //此时快照 item item.Name = "newtitle";
repo.Update(item); //对比快照时的变化
//UPDATE `Topic` SET `Title` = 'newtitle'
//WHERE (`Id` = 1)

2、手工管理状态

var repo = fsql.GetRepository<Topic>();
var item = new Topic { Id = 1 };
repo.Attach(item); //此时快照 item item.Title = "newtitle";
repo.Update(item); //对比快照时的变化
//UPDATE `Topic` SET `Title` = 'newtitle'
//WHERE (`Id` = 1)

3、直接使用 repo.UpdateDiy,它是 IFreeSql 提供的原生 IUpdate 对象,功能更丰富


05 级联保存数据

实践发现,N对1 不适合做级联保存。保存 Topic 的时候把 Type 信息也保存?我个人认为自下向上保存的功能太不可控了,FreeSql 目前不支持自下向上保存。因此下面我们只讲 OneToOne/OneToMany/ManyToMany 级联保存。至于 ManyToOne 级联保存使用手工处理,更加安全可控。

功能1:SaveMany 手工保存

完整保存,对比表已存在的数据,计算出添加、修改、删除执行。

递归保存导航属性不安全,不可控,并非技术问题,而是出于安全考虑,提供了手工完整保存的方式。

var repo = fsql.GetRepository<Type>();
var type = new Type
{
name = "c#",
Topics = new List<Topic>(new[]
{
new Topic { ... }
})
};
repo.Insert(type);
repo.SaveMany(type, "Topics"); //手工完整保存 Topics
  • SaveMany 仅支持 OneToMany、ManyToMany 导航属性
  • 只保存 Topics,不向下递归追朔
  • 当 Topics 为 Empty 时,删除 type 存在的 Topics 所有表数据,确认?
  • ManyToMany 机制为,完整对比保存中间表,外部表只追加不更新

如:

  • 本表 Topic
  • 外部表 Tag
  • 中间表 TopicTag

功能2:EnableCascadeSave 仓储级联保存

DbContext/Repository EnableCascadeSave 可实现保存对象的时候,递归追朔其 OneToOne/OneToMany/ManyToMany 导航属性也一并保存,本文档说明机制防止误用。

1、OneToOne 级联保存

v3.2.606+ 支持,并且支持级联删除功能(文档请向下浏览)

2、OneToMany 追加或更新子表,不删除子表已存在的数据

var repo = fsql.GetRepository<Type>();
repo.DbContextOptions.EnableCascadeSave = true; //需要手工开启
repo.Insert(type);
  • 不删除 Topics 子表已存在的数据,确认?
  • 当 Topics 属性为 Empty 时,不做任何操作,确认?
  • 保存 Topics 的时候,还会保存 Topics[0-..] 的下级集合属性,向下18层,确认?

向下18层的意思,比如【类型】表,下面有集合属性【文章】,【文章】下面有集合属性【评论】。

保存【类型】表对象的时候,他会向下检索出集合属性【文章】,然后如果【文章】被保存的时候,再继续向下检索出集合属性【评论】。一起做 InsertOrUpdate 操作。

3、ManyToMany 完整对比保存中间表,追加外部表

完整对比保存中间表,对比【多对多】中间表已存在的数据,计算出添加、修改、删除执行。

追加外部表,只追加不更新。

  • 本表 Topic
  • 外部表 Tag
  • 中间表 TopicTag

06 删除数据

var repo = fsql.GetRepository<Topic>();
repo.Delete(new Topic { Id = 1 }); //有重载方法 repo.Delete(Topic[]) var repo2 = fsql.GetRepository<Topic, int>(); //int 是主键类型,相比 repo 对象多了 Delete(int) 方法
repo2.Delete(1);

07 级联删除数据

第一种:基于【对象】级联删除

比如使用过 Include/IncludeMany 查询的对象,可以使用此方法级联删除它们。

var repo = fsql.GetRepository<Group>();
repo.DbContextOptions.EnableCascadeSave = true; //关键设置
repo.Insert(new UserGroup
{
GroupName = "group01",
Users = new List<User>
{
new User { Username = "admin01", Password = "pwd01", UserExt = new UserExt { Remark = "用户备注01" } },
new User { Username = "admin02", Password = "pwd02", UserExt = new UserExt { Remark = "用户备注02" } },
new User { Username = "admin03", Password = "pwd03", UserExt = new UserExt { Remark = "用户备注03" } },
}
}); //级联添加测试数据
//INSERT INTO "usergroup"("groupname") VALUES('group01') RETURNING "id"
//INSERT INTO "user"("username", "password", "groupid") VALUES('admin01', 'pwd01', 1), ('admin02', 'pwd02', 1), ('admin03', 'pwd03', 1) RETURNING "id" as "Id", "username" as "Username", "password" as "Password", "groupid" as "GroupId"
//INSERT INTO "userext"("userid", "remark") VALUES(3, '用户备注01'), (4, '用户备注02'), (5, '用户备注03') var groups = repo.Select
.IncludeMany(a => a.Users,
then => then.Include(b => b.UserExt))
.ToList();
repo.Delete(groups); //级联删除,递归向下遍历 group OneToOne/OneToMany/ManyToMany 导航属性
//DELETE FROM "userext" WHERE ("userid" IN (3,4,5))
//DELETE FROM "user" WHERE ("id" IN (3,4,5))
//DELETE FROM "usergroup" WHERE ("id" = 1)

第二种:基于【数据库】级联删除,不依赖数据库外键

根据设置的导航属性,递归删除 OneToOne/OneToMany/ManyToMany 对应数据,并返回已删除的数据。此功能不依赖数据库外键

var repo = fsql.GetRepository<Group>();
var ret = repo.DeleteCascadeByDatabase(a => a.Id == 1);
//SELECT a."id", a."username", a."password", a."groupid" FROM "user" a WHERE (a."groupid" = 1)
//SELECT a."userid", a."remark" FROM "userext" a WHERE (a."userid" IN (3,4,5))
//DELETE FROM "userext" WHERE ("userid" IN (3,4,5))
//DELETE FROM "user" WHERE ("id" IN (3,4,5))
//DELETE FROM "usergroup" WHERE ("id" = 1) //ret Count = 7 System.Collections.Generic.List<object>
// [0] {UserExt} object {UserExt}
// [1] {UserExt} object {UserExt}
// [2] {UserExt} object {UserExt}
// [3] {User} object {User}
// [4] {User} object {User}
// [5] {User} object {User}
// [6] {UserGroup} object {UserGroup} public class Group
{
[Column(IsIdentity = true)]
public int Id { get; set; }
public string GroupName { get; set; } [Navigate(nameof(User.GroupId))]
public List<User> Users { get; set; }
}
public class User
{
[Column(IsIdentity = true)]
public int Id { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public int GroupId { get; set; } [Navigate(nameof(Id))]
public UserExt UserExt { get; set; }
}
public class UserExt
{
[Column(IsPrimary = true)]
public int UserId { get; set; }
public string Remark { get; set; } [Navigate(nameof(UserId))]
public User User { get; set; }
}

08 添加或修改数据

var repo = fsql.GetRepository<Topic>();
repo.InsertOrUpdate(item);

如果内部的状态管理存在数据,则更新。

如果内部的状态管理不存在数据,则查询数据库,判断是否存在。

存在则更新,不存在则插入

缺点:不支持批量操作

提醒:IFreeSql 也定义了 InsertOrUpdate 方法,两者实现机制不同,它利用了数据库特性:

Database Features Database Features
MySql on duplicate key update 达梦 merge into
PostgreSQL on conflict do update 人大金仓 on conflict do update
SqlServer merge into 神通 merge into
Oracle merge into 南大通用 merge into
Sqlite replace into MsAccess 不支持
Firebird merge into
fsql.InsertOrUpdate<T>()
.SetSource(items) //需要操作的数据
//.IfExistsDoNothing() //如果数据存在,啥事也不干(相当于只有不存在数据时才插入)
.ExecuteAffrows();

09 批量编辑数据

var repo = fsql.GetRepository<BeginEdit01>();
var cts = new[] {
new BeginEdit01 { Name = "分类1" },
new BeginEdit01 { Name = "分类1_1" },
new BeginEdit01 { Name = "分类1_2" },
new BeginEdit01 { Name = "分类1_3" },
new BeginEdit01 { Name = "分类2" },
new BeginEdit01 { Name = "分类2_1" },
new BeginEdit01 { Name = "分类2_2" }
}.ToList();
repo.Insert(cts); repo.BeginEdit(cts); //开始对 cts 进行编辑 cts.Add(new BeginEdit01 { Name = "分类2_3" });
cts[0].Name = "123123";
cts.RemoveAt(1); var affrows = repo.EndEdit(); //完成编辑
Assert.Equal(3, affrows);
class BeginEdit01
{
public Guid Id { get; set; }
public string Name { get; set; }
}

上面的代码 EndEdit 方法执行的时候产生 3 条 SQL 如下:

INSERT INTO "BeginEdit01"("Id", "Name") VALUES('5f26bf07-6ac3-cbe8-00da-7dd74818c3a6', '分类2_3')

UPDATE "BeginEdit01" SET "Name" = '123123'
WHERE ("Id" = '5f26bf00-6ac3-cbe8-00da-7dd01be76e26') DELETE FROM "BeginEdit01" WHERE ("Id" = '5f26bf00-6ac3-cbe8-00da-7dd11bcf54dc')

场景:winform 加载表数据后,一顿添加、修改、删除操作之后,点击【保存】

提醒:该操作只对变量 cts 有效,不是针对全表对比更新。


10 弱类型 CRUD

var repo = fsql.GetRepository<object>();
repo.AsType(typeof(Topic)); var item = (object)new Topic { Title = "object title" };
repo.Insert(item);

11 无参数化命令

支持参数化、无参数化命令执行,有一些特定的数据库,使用无参数化命令执行效率更高哦,并且调试起来更直观。

var repo = fsql.GetRepository<object>();
repo.DbContextOptions.NoneParameter = true;

12 工作单元(事务)

UnitOfWork 可将多个仓储放在一个单元管理执行,最终通用 Commit 执行所有操作,内部采用了数据库事务。

方法1:随时创建使用

using (var uow = fsql.CreateUnitOfWork())
{
var typeRepo = fsql.GetRepository<Type>();
var topicRepo = fsql.GetRepository<Topic>();
typeRepo.UnitOfWork = uow;
topicRepo.UnitOfWork = uow; typeRepo.Insert(new Type());
topicRepo.Insert(new Topic()); uow.Orm.Insert(new Topic()).ExecuteAffrows();
//uow.Orm 和 fsql 都是 IFreeSql
//uow.Orm CRUD 与 uow 是一个事务
//fsql CRUD 与 uow 不在一个事务 uow.Commit();
}

方法2:使用 AOP + UnitOfWorkManager 实现多种事务传播

本段内容引导,如何在 asp.net core 项目中使用特性(注解) 的方式管理事务。

UnitOfWorkManager 支持六种传播方式(propagation),意味着跨方法的事务非常方便,并且支持同步异步:

  • Requierd:如果当前没有事务,就新建一个事务,如果已存在一个事务中,加入到这个事务中,默认的选择。
  • Supports:支持当前事务,如果没有当前事务,就以非事务方法执行。
  • Mandatory:使用当前事务,如果没有当前事务,就抛出异常。
  • NotSupported:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
  • Never:以非事务方式执行操作,如果当前事务存在则抛出异常。
  • Nested:以嵌套事务方式执行。

第一步:配置 Startup.cs 注入

public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IFreeSql>(fsql);
services.AddScoped<UnitOfWorkManager>();
services.AddFreeRepository(null, typeof(Startup).Assembly);
}
UnitOfWorkManager 成员 说明
IUnitOfWork Current 返回当前的工作单元
void Binding(repository) 将仓储的事务交给它管理
IUnitOfWork Begin(propagation, isolationLevel) 创建工作单元

第二步:定义事务特性

[AttributeUsage(AttributeTargets.Method)]
public class TransactionalAttribute : Attribute
{
/// <summary>
/// 事务传播方式
/// </summary>
public Propagation Propagation { get; set; } = Propagation.Requierd;
/// <summary>
/// 事务隔离级别
/// </summary>
public IsolationLevel? IsolationLevel { get; set; }
}

第三步:引入动态代理库

在 Before 从容器中获取 UnitOfWorkManager,调用它的 var uow = Begin(attr.Propagation, attr.IsolationLevel) 方法

在 After 调用 Before 中的 uow.Commit 或者 Rollback 方法,最后调用 uow.Dispose

提醒:动态代理,一定注意处理好异步 await,否则会出现事务异常的问题

第四步:在 Controller 或者 Service 中使用事务特性

public class TopicService
{
IBaseRepository<Topic> _repoTopic;
IBaseRepository<Detail> _repoDetail; public TopicService(IBaseRepository<Topic> repoTopic, IBaseRepository<Detail> repoDetail)
{
_repoTopic = repoTopic;
_repoDetail = repoDetail;
} [Transactional]
public virtual void Test1()
{
//这里 _repoTopic、_repoDetail 所有 CRUD 操作都是一个工作单元
this.Test2();
} [Transactional(Propagation = Propagation.Nested)]
public virtual void Test2() //嵌套事务,新的(不使用 Test1 的事务)
{
//这里 _repoTopic、_repoDetail 所有 CRUD 操作都是一个工作单元
}
}

是不是进方法就开事务呢?

不一定是真实事务,有可能是虚的,就是一个假的 unitofwork(不带事务)

也有可能是延用上一次的事务

也有可能是新开事务,具体要看传播模式

示例项目:https://github.com/dotnetcore/FreeSql/tree/master/Examples/aspnetcore_transaction

Autofac 动态代理参考项目:


13 手工分表

FreeSql 原生用法、FreeSql.Repository 仓储用法 都提供了 AsTable 方法对分表进行 CRUD 操作,例如:

var repo = fsql.GetRepository<Log>();
repo.AsTable(oldname => $"{oldname}_201903"); //对 Log_201903 表 CRUD repo.Insert(new Log { ... });

跨库,但是在同一个数据库服务器下,也可以使用 AsTable(oldname => $"db2.dbo.{oldname}")

//跨表查询
var sql = fsql.Select<User>()
.AsTable((type, oldname) => "table_1")
.AsTable((type, oldname) => "table_2")
.ToSql(a => a.Id); //select * from (SELECT a."Id" as1 FROM "table_1" a) ftb
//UNION ALL
//select * from (SELECT a."Id" as1 FROM "table_2" a) ftb

分表总结:

  • 分表、相同服务器跨库 可以使用 AsTable 进行 CRUD;
  • AsTable CodeFirst 会自动创建不存在的分表;
  • 不可在分表分库的实体类型中使用《延时加载》;

v3.2.500 按时间自动分表方案:https://github.com/dotnetcore/FreeSql/discussions/1066


写在最后

FreeSql 他是免费自由的 ORM,也可以说是宝藏 ORM。更多文档请前往 github wiki 查看。

FreeSql 已经步入第四个年头,期待少年的你十年后还能归来在此贴回复,兑现当初吹过十年不变的承诺。

多的不说了,希望民间的开源力量越来越强大。

希望作者的努力能打动到你,请求正在使用的、善良的您能动一动小手指,把文章转发一下,让更多人知道 .NET 有这样一个好用的 ORM 存在。谢谢!!

FreeSql 使用最宽松的开源协议 MIT https://github.com/dotnetcore/FreeSql ,完全可以商用,文档齐全,甚至拿去卖钱也可以。QQ群:4336577(已满)、8578575(在线)、52508226(在线)

.NET ORM 仓储层必备的功能介绍之 FreeSql Repository 实现篇的更多相关文章

  1. .NETCore 新型 ORM 功能介绍

    简介 FreeSql 是一个功能强大的 .NETStandard 库,用于对象关系映射程序(O/RM),支持 .NETCore 2.1+ 或 .NETFramework 4.6.1+. 定义 IFre ...

  2. ORM 创新解放劳动力 -SqlSugar 新功能介绍

    介绍 SqlSugar是一款 老牌 .NET 开源ORM框架,由果糖大数据科技团队维护和更新 ,Github star数仅次于EF 和 Dapper 优点: 简单易用.功能齐全.高性能.轻量级.服务齐 ...

  3. FreeSql.Repository 通用仓储层功能

    前言 好多年前,DAL 作为数据库访问层,其实是非常流行的命名方式. 不知道从什么时候开始,仓储层成了新的时尚名词.目前了解到,许多人只要在项目中看见 DAL 就会觉得很 low,但是比较可笑的一点是 ...

  4. x-pack 功能介绍及配置传输层安全性(TLS / SSL)

    x-pack 功能介绍及配置传输层安全性(TLS / SSL) 学习了:https://blog.csdn.net/wfs1994/article/details/80411047

  5. OSS.Core基于Dapper封装(表达式解析+Emit)仓储层的构思及实现

    最近趁着不忙,在构思一个搭建一个开源的完整项目,至于原因以及整个项目框架后边文章我再说明.既然要起一个完整的项目,那么数据仓储访问就必不可少,这篇文章我主要介绍这个新项目(OSS.Core)中我对仓储 ...

  6. VUE+Element 前端应用开发框架功能介绍

    前面介绍了很多ABP系列的文章<ABP框架使用>,一步一步的把我们日常开发中涉及到的Web API服务构建.登录日志和操作审计日志.字典管理模块.省份城市的信息维护.权限管理模块中的组织机 ...

  7. 带你走近AngularJS - 基本功能介绍

    带你走近AngularJS系列: 带你走近AngularJS - 基本功能介绍 带你走近AngularJS - 体验指令实例 带你走近AngularJS - 创建自定义指令 ------------- ...

  8. Python中模块之sys的功能介绍

    sys模块的功能介绍 1. sys的变量 argv 命令行参数 方法:sys.argv 返回值:list 例如:test1.py文件中有两句语句1.import sys 2.print(sys.arg ...

  9. EFCore+Mysql仓储层建设(分页、多字段排序、部分字段更新)

    前沿 园子里已有挺多博文介绍了EFCore+Mysql/MSSql如何进行使用,但实际开发不会把EF层放在Web层混合起来,需要多个项目配合结构清晰的进行分层工作,本文根据个人实践经验总结将各个项目进 ...

随机推荐

  1. Spring 支持的事务管理类型?

    Spring 支持两种类型的事务管理:编程式事务管理:这意味你通过编程的方式管理事务,给你带来极大的灵 活性,但是难维护.声明式事务管理:这意味着你可以将业务代码和事务管理分离,你只需用 注解和 XM ...

  2. redis主从复制与哨兵高可用

    redis主从复制 话不多说,直接看案例: 环境准备, 主从规划 主节点:6380 从节点:6381.6382 运行3个redis数据库,达到 1主 2从的配置 #主库 6379.conf port ...

  3. IList和DataSet性能差别 转自 http://blog.csdn.net/ilovemsdn/article/details/2954335

    IList和DataSet性能差别         分类:             NHibernate/Spring/NetTiers/Castle/Ibatis             C#    ...

  4. springboot+shiro 01 - 实现权限控制

    sb_shiro_session <?xml version="1.0" encoding="UTF-8"?> <project xmlns= ...

  5. 使用 vscode 插件可视化制作和管理脚手架及原理解析

    提到脚手架,大家想到的可能就是各种 xxx-cli,本文介绍的是另一种方式:以 vscode 插件的形式实现,提供 web 可视化操作,如下图: 下面介绍如何安装使用,以及实现原理. 安装使用 vsc ...

  6. 纯干货数学推导_傅里叶级数与傅里叶变换_Part3_周期为2L的函数展开

  7. 使用 Vuex + Vue.js 构建单页应用

    鉴于该篇文章阅读量大,回复的同学也挺多的,特地抽空写了一篇 vue2.0 下的 vuex 使用方法,传送门:使用 Vuex + Vue.js 构建单页应用[新篇] ------------------ ...

  8. 基于Node的React图片上传组件实现

    写在前面 红旗不倒,誓把JavaScript进行到底!今天介绍我的开源项目 Royal 里的图片上传组件的前后端实现原理(React + Node),花了一些时间,希望对你有所帮助. 前端实现 遵循R ...

  9. 小程序wx.getRecorderManager()录音管理

    小程序中提供了两种录音的API,wx.startRecord和wx.getRecorderManager(),前一个现在微信团队已经不再维护,所以在这里写一下新的录音管理,比之前要强大 1.小程序录音 ...

  10. AS之AlertDialog使用

    关于AlertDialog的使用,主要是去做一个弹窗. import android.content.DialogInterface; import android.os.Bundle; import ...