CSharpEntityFramework与CodeFirst实践
CSharpEntityFramework与CodeFirst实践
前言
当我们进行开发的时候,常常会用到数据库来对数据进行持久化的操作,有的时候,我们并不想要在进行代码开发的过程中,还去关注数据库的构建,表的构建等等。于是,就有了Code First模式。何为Code First模式呢?它思想就是先定义模型中的类,再通过这些类生成数据库。这种开发模式适合于全新的项目,它使得我们可以以代码为核心进行设计而不是先构造数据库。这样一来,使得我们更加关注代码的开发。在c#中,我们使用EntityFramework来实现Code First场景。
背景
试想一下,现在有一个图书管理项目,里面会用到Book实体类,Book会唯一编号Id、书名Title、价格Price,在数据库优先的情形下,我们可能会首先创建Book对应的表,里面创建对应于Id、Title和Price的字段,然后回到代码中继续来开发,亦或者先在代码中进行开发,然后在需要DA(数据访问)的时候创建数据库以及表结构。无论怎样,我们作为开发都要与数据库进行打交道,来回切换关注的东西,还要注意数据库的表建立的对不对,数据类型对不对等等。好在c#中有了EF这样的强大的框架以及Code First的思想。带给我们全新的开发体验。
实践
基础配置
创建一个项目,并利用Nuget引入EntityFramework6
当我们引入EF时,我们发现项目下app.config其中的配置会发生更改,这个配置文件更改的内容就是EF为我们创建的,也是我们配置数据库连接的地方。
回到项目中,接下来我们创建Book实体类,为其添加Id、Title以及Price属性,同时使用特性在属性以及类名上标注该实体类在数据库中的体现方式:
namespace CodeFirstDemo
{
[Table("Book")]
public class Book
{
private Guid id;
public Guid Id
{
get
{
id = id == null ? Guid.NewGuid() : id;
return id;
}
}
[Required]
public string Title { get; set; }
[Required]
public double Price { get; set; }
}
}
使用Table特性来表名该实体类Book将对应数据库中的book表(不需要此刻已经有Book表),使用[Required]特性来表明字段是否可为空,此外,由于EF默认将Id属性视为主键,所以无需使用[Key]特性来指明上面的Id为主键。
接下来,我们需要使用继承EF的DbContext来构建数据库上下文类,我们直接使用VS自带生成工具即可生成对应的数据库上下文模型:
点击完成后我们就得到了如下的一个配置类
public class BookDbDemo : DbContext
{
//您的上下文已配置为从您的应用程序的配置文件(App.config 或 Web.config)
//使用“BookDbDemo”连接字符串。默认情况下,此连接字符串针对您的 LocalDb 实例上的
//“CodeFirstDemo.BookDbDemo”数据库。
//
//如果您想要针对其他数据库和/或数据库提供程序,请在应用程序配置文件中修改“BookDbDemo”
//连接字符串。
public BookDbDemo()
: base("name=BookDbDemo")
{
}
//为您要在模型中包含的每种实体类型都添加 DbSet。有关配置和使用 Code First 模型
//的详细信息,请参阅 http://go.microsoft.com/fwlink/?LinkId=390109。
// public virtual DbSet<MyEntity> MyEntities { get; set; }
}
//public class MyEntity
//{
// public int Id { get; set; }
// public string Name { get; set; }
//}
一个十分简单的数据库上下文类就建立完成。
正如生成的DbContext所说:“为您要在模型中包含的每种实体类型都添加 DbSet。”,我们在该类中添加如下的DbSet属性,并将注释删除,更加直观的看一看当前的结构:
public class BookDbDemo : DbContext
{
public BookDbDemo()
: base("name=BookDbDemo")
{
}
public virtual DbSet<Book> Books { get; set; }
}
在这个类中,我们声明了一个DbSet属性books,这就对应了数据库中的book表。换句话说,继承了DbContext的类就对应了某一个数据库,其连接属性由配置文件中的连接配置决定,并在DbContext中设置进去(基类构造函数设置),这个DbContext中的所有DbSet就对应到数据库中的表。
注意到,构造函数调用了基类构造函数,传入了"name=BookDbDemo"字符串,这个字符串就是指app.config配置文件中的数据库连接名,然后我们查看App.config文件,发现vs已经为我们生成了一个连接字符串节点:
<connectionStrings>
<add name="BookDbDemo" connectionString="data source=(LocalDb)\MSSQLLocalDB;initial catalog=CodeFirstDemo.BookDbDemo;integrated security=True;MultipleActiveResultSets=True;App=EntityFramework" providerName="System.Data.SqlClient" />
</connectionStrings>
但是这还段配置还是有点问题,首先连接地址数据库用户名等等都不对,更主要的是,我是MySQL数据库,怎么能用SQL呢,所以,我们要解决EF进行MySQL的连接问题。
使用EF进行MySQL数据库连接配置
如果使用ado.net链接mysql数据库则只需要MySql.Data.dll,即使用Nuget安装Mysql.Data就行了,并不需要安装mysql-connector-net驱动程序;
如果使用EF的话一般来说需要安装mysql-connector-net驱动程序;
其中mysql-connector-net驱动程序安装目录包含了
MySql.Data.dll;
MySql.Data.Entity.EF5.dll;
MySql.Data.Entity.EF6.dll;
MySql.Fabric.Plugin.dll;
MySql.Web.dll;
注意:就算把mysql-connector-net安装目录下所有的类库都拷到bin目录而不在应用环境上安装mysql-connector-net驱动程序,EF代码还是会报错,因为mysql-connector-net安装不仅装了各种dll类库,还在机器上.net环境的全局machine.config里加过如下配置:
<system.data>
<DbProviderFactories>
<add name="MySQL Data Provider"
invariant="MySql.Data.MySqlClient"
description=".Net Framework Data Provider for MySQL"
type="MySql.Data.MySqlClient.MySqlClientFactory, MySql.Data, Version=6.9.6.0, Culture=neutral, PublicKeyToken=c5687fc88969c44d" />
</DbProviderFactories>
</system.data>
所以要使用EF必须在站点服务器安装mysql-connector-net启动程序(数据库所在的服务器不需要安装,只需要c#程序所运行的电脑需要安装)或者在应用的配置文件(app.config或web.config)里configuration节点下加上上面的配置。
这里,为了我们机器环境的纯净,我们使用nuget安装对应项目需要的库(MySql.Fabric.Plugin.dll和MySql.Web.dll这两个库如果没有需要不用安装)到项目中:
务必注意!MySql.Data和MySql.Data.Entity必须要保持相同的版本!否则会出现 “找到的程序集清单定义与程序集引用不匹配。 (异常来自 HRESULT:0x80131040)” 的错误,所以这里的Mysql.Data版本我并没有选择最新的
并且在配置文件中加如上述system.data配置,此时app.config配置主要有如下的变化:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
...
</configSections>
<startup>
...
</startup>
<entityFramework>
<defaultConnectionFactory
...
</defaultConnectionFactory>
<providers>
...
<!-- 新增了 MySql.Data.MySqlClient Provider -->
<provider invariantName="MySql.Data.MySqlClient" type="MySql.Data.MySqlClient.MySqlProviderServices, MySql.Data.Entity.EF6, Version=6.10.8.0, Culture=neutral, PublicKeyToken=c5687fc88969c44d"/>
</providers>
</entityFramework>
<connectionStrings>
<add name="BookDbDemo" connectionString="data source=(LocalDb)\MSSQLLocalDB;initial catalog=CodeFirstDemo.BookDbDemo;integrated security=True;MultipleActiveResultSets=True;App=EntityFramework" providerName="System.Data.SqlClient" />
</connectionStrings>
<!-- 引入MySql.Data写入的 -->
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="MySql.Data" publicKeyToken="c5687fc88969c44d" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-6.9.6.0" newVersion="6.9.6.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
<!-- 手动新增system.data配置节点 -->
<system.data>
<DbProviderFactories>
<add name="MySQL Data Provider"
invariant="MySql.Data.MySqlClient"
description=".Net Framework Data Provider for MySQL"
type="MySql.Data.MySqlClient.MySqlClientFactory, MySql.Data, Version=6.9.6.0, Culture=neutral, PublicKeyToken=c5687fc88969c44d" />
</DbProviderFactories>
</system.data>
</configuration>
PS:实际上本人在实践过程中发现,引入Mysql.Data后写如的节点(见上面xml)作用似乎和手动新增的一样,没有求证差别。
实际数据库配置
完成EF的Mysql连接环境配置后,最基础的数据库还是需要建立的,所以去数据库创建一个名为bookdbdemo的数据库,按道理来说,我们只需要在这个地方触碰到数据库,况且这还是DBA的事情。创建好的数据库如下:
于是,我们将其中的连接字符串connectionString内容修改为我们当前的数据库连接字符串:
<connectionStrings>
<add name="BookDbDemo" connectionString="Data Source=localhost; Database=bookdbdemo; User ID=root; Password=;" providerName="MySql.Data.MySqlClient"/>
</connectionStrings>
注意:providerName写的是在引入MySql.Data.Entity之后在app.config中出现的
<provider invariantName="MySql.Data.MySqlClient" type="MySql.Data.MySqlClient.MySqlProviderServices, MySql.Data.Entity.EF6, Version=6.10.8.0, Culture=neutral, PublicKeyToken=c5687fc88969c44d">节点
完成上面的配置之后,我们接下来就要使用EF最强大的Migration数据库迁移功能。
EF Database Migration EF数据库迁移
首先启用迁移功能。在Nuget命令行中输入:Enable-Migrations(有个s,注意)
PM> enable-migrations
注意:如果此处提示:具有固定名称“MySql.Data.MySqlClient”的 ADO.NET 提供程序未在计算机或应用程序配置文件中注册或无法加载。需要在上下文继承类中(此处就是BookDbDemo)上添加特性[DbConfigurationType(typeof(MySql.Data.Entity.MySqlEFConfiguration))]
...
[DbConfigurationType(typeof(MySql.Data.Entity.MySqlEFConfiguration))]
public class BookDbDemo : DbContext
{
...
}
启动迁移功能后,Nuget命令行提示:
PM> enable-migrations
正在检查上下文的目标是否为现有数据库...
已为项目 CodeFirstDemo 启用 Code First 迁移。
此处提示我们,EF的数据迁移功能已经启用,在项目中我们会发现创建了一个名为Migtaions的文件夹,里面还存在一个Configuration配置类,这个类中,我们需要将AutomaticMigrationsEnabled设置为true,即启用自动迁移功能
public Configuration()
{
AutomaticMigrationsEnabled = true;
}
此时,我们的数据库还没有创建的任何的表。
接下来,我们使用Add-Migtaion XXX命令来添加一个变更模块。
初始化以及创建表
PM> add-migration InitDb
正在为迁移“InitDb”搭建基架。
此迁移文件的设计器代码包含当前 Code First 模型的快照。在下一次搭建迁移基架时,将使用此快照计算对模型的更改。如果对要包含在此迁移中的模型进行其他更改,则您可通过再次运行“Add-Migration InitDb”重新搭建基架。
注意后面的命名,InitDb只是我们取的名字,为了区分对数据库进行的变更,这里是我们第一次构建,所以我取名为InitDb,完成该命令后,你会发现Migrations文件夹下出现了一个以你迁移模块创建时刻+下划线+刚刚迁移模块的命名的类文件:
其类文件内容如下:
public partial class InitDb : DbMigration
{
public override void Up()
{
CreateTable(
"dbo.Book",
c => new
{
Id = c.Guid(nullable: false),
Title = c.String(nullable: false),
Price = c.Double(nullable: false),
})
.PrimaryKey(t => t.Id);
}
public override void Down()
{
DropTable("dbo.Book");
}
}
我们可以看到里面重写了DbMigration的Up和Down方法,同时从方法中的代码我们能大致推测,Up方法就是发生的变更,这里会创建表Book(dbo是EF的一些配置语法),设置表字段以及设置主键;而Down方法就是回滚操作,将表Book删除。
但是此时,我们数据库中的表还是没有发生变化,因为我们现在添加了变更模块,只是根据当前的代码来的,要将变更同步到数据库,需要Update-Database命令(添加-Versbose方便我们查看数发生执行的SQL语句):
PM> update-database -verbose
Using StartUp project 'CodeFirstDemo'.
Using NuGet project 'CodeFirstDemo'.
指定“-Verbose”标志以查看应用于目标数据库的 SQL 语句。
目标数据库为: “bookdbdemo”(DataSource: localhost,提供程序: MySql.Data.MySqlClient,来源: Configuration)。
正在应用显式迁移: [201809131330306_InitDb]。
正在应用显式迁移: 201809131330306_InitDb。
create table `Book` (`Id` CHAR(36) BINARY default '' not null ,`Title` longtext not null ,`Price` double not null ,primary key ( `Id`) ) engine=InnoDb auto_increment=0
create table `__MigrationHistory` (`MigrationId` nvarchar(150) not null ,`ContextKey` nvarchar(300) not null ,`Model` longblob not null ,`ProductVersion` nvarchar(32) not null ,primary key ( `MigrationId`) ) engine=InnoDb auto_increment=0
INSERT INTO `__MigrationHistory`(
`MigrationId`,
`ContextKey`,
`Model`,
`ProductVersion`) VALUES (
'201809131330306_InitDb',
'CodeFirstDemo.Migrations.Configuration',
#中间有一大段经过摘要的字符串,应该是变更的摘要
,
'6.2.0-61023');
正在运行 Seed 方法。
我们可以从输出中很容易的看到执行了创建book数据库的sql语句,以及创建了一个MigrationHistory表,这个表就是记录了数据库迁移的一些摘要,供我们以后来进行回退操作。
完成了迁移之后,查看数据库:
我们可以看到,表及其结构按照我们预期创建成功了。
删除表
为了证明EF再删除表的时候,并不会影响其他的表,我再次利用创建了一个新的实体类EBook并更新DbContext以及进行EF迁移功能:
namespace CodeFirstDemo
{
[Table("EBook")]
public class EBook
{
private Guid id;
public Guid Id
{
get
{
id = id == null ? Guid.NewGuid() : id;
return id;
}
set
{
id = value;
}
}
[Required]
public string EBookTitle { get; set; }
}
}
更新DbContext:
[DbConfigurationType(typeof(MySql.Data.Entity.MySqlEFConfiguration))]
public class BookDbDemo : DbContext
{
public BookDbDemo()
: base("name=BookDbDemo")
{
}
public virtual DbSet<Book> Books { get; set; }
// 新增的DbSet
public virtual DbSet<EBook> EBooks { get; set; }
}
进行EF数据库迁移,此时我们不需要再次进行启用迁移功能了,而是使用add-migration检测并增加行的变更模块:
PM> add-migration AddEBookEntity
正在为迁移“AddEBookEntity”搭建基架。
此迁移文件的设计器代码包含当前 Code First 模型的快照。在下一次搭建迁移基架时,将使用此快照计算对模型的更改。如果对要包含在此迁移中的模型进行其他更改,则您可通过再次运行“Add-Migration AddEBookEntity”重新搭建基架。
此时Migrations文件夹下面又增加了新的类文件:
其内容和前面大致,创建表以及回滚,这里不再展示。之后我们再次使用update-database命令将变更更新到数据库中,得到当前的数据库内容:
此时我们将book表中填充一些数据:
然后,我们将DbContext中的DbSet属性删除,再次进行迁移:
进行更新以后,我们可以看到Ebook表已经删除了,但是book表内容没有发生任何变化:
变更属性
有的时候,我们可能很少会将实体类,更多的是对现有实体类中进行属性的增加、删除以及修改,变相的,对数据库中的表的字段进行增加、删除和修改:
增加属性
现在,我们想要将Book实体类中增加一个Abstract摘要属性,它会影响我们上面我们已有的数据吗?试一试就知道了。
首先,在Book类中增加这一属性,不设置Required特性:
...
[Required]
public string Title { get; set; }
// 增加的摘要属性
public string Abstract { get; set; }
[Required]
public double Price { get; set; }
...
此时我们增加变更模块:
PM> add-migration AddNewProp
正在为迁移“AddNewProp”搭建基架。
此迁移文件的设计器代码包含当前 Code First 模型的快照。在下一次搭建迁移基架时,将使用此快照计算对模型的更改。如果对要包含在此迁移中的模型进行其他更改,则您可通过再次运行“Add-Migration AddNewProp”重新搭建基架。
我们可以看到变更模块类:
public partial class AddNewProp : DbMigration
{
public override void Up()
{
AddColumn("dbo.Book", "Abstract", c => c.String(unicode: false));
}
public override void Down()
{
DropColumn("dbo.Book", "Abstract");
}
}
从这个类的Up方法中,我们看,他调用了AddColumn增加列的方法。而Down回退方法则调用了DropColumn删除列的方法。当我们执行update-database命令有什么效果呢?
PM> update-database -verbose
Using StartUp project 'CodeFirstDemo'.
Using NuGet project 'CodeFirstDemo'.
指定“-Verbose”标志以查看应用于目标数据库的 SQL 语句。
目标数据库为: “bookdbdemo”(DataSource: localhost,提供程序: MySql.Data.MySqlClient,来源: Configuration)。
正在应用显式迁移: [201809131406177_AddNewProp]。
正在应用显式迁移: 201809131406177_AddNewProp。
alter table `Book` add column `Abstract` longtext
INSERT INTO `__MigrationHistory`(
...
)
重点看到这句SQL:“alter table Book
add column Abstract
longtext ”,这句SQL就是对我们表添加了一个字段,类型为longtext。我们进入数据库中,看一看变化:
可以看到数据库中其他字段的值都没有发生变化,仅仅多出了这个字段,同时符合我们设置的可以为空的预期
删除属性
删除与增加同理,我们直接将Book实体类的属性删除,然后增加变更,最后同步更新变更到数据库。这里我们将Price和Abstract属性都删除,变更模块如下:
public partial class RemoveProp : DbMigration
{
public override void Up()
{
DropColumn("dbo.Book", "Abstract");
DropColumn("dbo.Book", "Price");
}
public override void Down()
{
AddColumn("dbo.Book", "Price", c => c.Double(nullable: false));
AddColumn("dbo.Book", "Abstract", c => c.String(unicode: false));
}
}
得到如下的结果:
重命名属性
重命名比起前面的增删属性有所不同,就当前情形为例子,当前Book类属性如下:
namespace CodeFirstDemo
{
[Table("Book")]
public class Book
{
private Guid id;
public Guid Id
{
get
{
id = id == null ? Guid.NewGuid() : id;
return id;
}
set
{
id = value;
}
}
[Required]
public string Title { get; set; }
}
}
对应数据库为
此时如果我们想要修改属性名,我们将Book中的Title属性改为Name属性:
...
[Required]
public string Name { get; set; }
...
然后执行Add-Migration命令:
PM> add-migration ChangePropName
我们看到变更类的内容为:
public partial class ChangePropName : DbMigration
{
public override void Up()
{
AddColumn("dbo.Book", "Name", c => c.String(nullable: false, unicode: false));
DropColumn("dbo.Book", "Title");
}
public override void Down()
{
AddColumn("dbo.Book", "Title", c => c.String(nullable: false, unicode: false));
DropColumn("dbo.Book", "Name");
}
}
我们发现,只要更新到数据库,EF会在表中先添加一个字段Name,然后删除字段Title,很显然,这样更新,会将我们现有的数据清空。如何不造成这样的情况呢?其实我们首先知道,在进行Update-database的时候,EF框架会执行Up方法,同时,AddColumn、DropColumn很显然是DbMigration这个类中的方法,我们找一找看有没有重命名的方法呢。令我们欣喜的是,有:
public override void Up()
{
//AddColumn("dbo.Book", "Name", c => c.String(nullable: false, unicode: false));
//DropColumn("dbo.Book", "Title");
RenameColumn("dbo.Book", "Title", "Name");
}
注释掉Add和DropColumn,使用RenameColumn,填入表、原字段名、新字段名;接下来我们执行Update-Database -Verbose:
PM> UPDATE-DATABASE -VERBOSE
...
Fatal error encountered during command execution. ---> MySql.Data.MySqlClient.MySqlException (0x80004005): Parameter '@columnType' must be defined.
...
我们发现报了一个错误,实际上解决方法在链接字符串中加入这样一句话“;Allow User Variables=True”就可以了,即如下:
<connectionStrings>
<add name="BookDbDemo" connectionString="Data Source=localhost; Database=bookdbdemo; User ID=root; Password=;Allow User Variables=True" providerName="MySql.Data.MySqlClient"/>
</connectionStrings>
这样一来,再次运行,不报错。查看数据库:
CSharpEntityFramework与CodeFirst实践的更多相关文章
- EF6 CodeFirst 实践系列文章列表
2015 Jul.16 EF6 CodeFirst+Repository+Ninject+MVC4+EasyUI实践(一) 来自:wangweimutou 本系列源自对EF6 CodeFirst的探索 ...
- EF Core CodeFirst实践 ( 使用MS SqlServer)
这里使用 MS SQLSERVER ,网上大多使用 SQLite 先来一个CodeFirst 新建项目 这里我们选择 ASP.NET Core Web Application (.NET Core) ...
- EF6 CodeFirst+Repository+Ninject+MVC4+EasyUI实践(四)
前言 这一篇,我们终于到了讲解Entity Framework CodeFirst 的时刻了,首先创建实体对象模型,然后会通过配置Fluent API的方式来对实体对象模型进行完整的数据库映射操作. ...
- EF6 CodeFirst+Repository+Ninject+MVC4+EasyUI实践(一)
前言 本系列源自对EF6 CodeFirst的探索,但后来发现在自己项目中构建的时候遇到了一些问题以及一些解决方法,因此想作为一个系列写下来. 本系列并不是教你怎么做架构设计,但可以参照一下里面的方法 ...
- EF6 CodeFirst+Repository+Ninject+MVC4+EasyUI实践(完)
前言 这一篇是本系列的最后一篇,虽然示例讲到这里就停止呢,但对于这些技术的学习远不能停止.虽然本示例讲的比较基础,但是正如我第一篇说到的,这个系列的目的不是说一些高端的架构设计,而是作为一个入门级,对 ...
- EF6 CodeFirst+Repository+Ninject+MVC4+EasyUI实践(九)
前言 这一篇我们将完成系统的权限设置功能以及不同角色用户登录系统后动态加载菜单.注意:此示例权限只针对菜单级,如果园友需要更复杂的系统权限设置,可以拓展到按钮级或属性级. 用户的登录采用Form认证来 ...
- EF6 CodeFirst+Repository+Ninject+MVC4+EasyUI实践(八)
前言 本篇幅将对系统的菜单管理模块进行说明,系统的菜单采用树形结构,这样可以更好地方便层级设计和查看.本示例将说明如何通过EntityFramework读取递归的菜单树形结构,以及结合EasyUI的t ...
- EF6 CodeFirst+Repository+Ninject+MVC4+EasyUI实践(七)
前言 上一篇文章我们完成了系统角色管理的基本功能实现,也对系统层次结构进行了了解.这一篇我们将继续对系统的用户管理模块进行代码编写.代码没有做封装,所以大部分的逻辑代码都是相通的,只是在一些前端的细节 ...
- EF6 CodeFirst+Repository+Ninject+MVC4+EasyUI实践(六)
前言 在接下来的篇幅里将对系统的模块功能进行编写.主要以代码实现为主.这一篇我们需要完成系统模块“角色管理”的相关功能.完成后可以对系统框架结构有进一步了解. Abstract层 之前说过,Abstr ...
随机推荐
- Python的dict
dict把key和value关联起来,可以通过 key来查找 value. 花括号 {} 表示这是一个dict,然后按照 key: value, 写出来即可.最后一个 key: value 的逗号可以 ...
- Struts2之OGNL与ValueStack
时间:2017-1-12 12:02 --OGNL1.OGNL表达式是什么 OGNL的全称是Object-Graph Navigation Language的缩写,中文名是对象图导航语言,它是一 ...
- java 查询当天0点0分0秒
由于业务需求,要计算客户今日收益,本周本月,本年等收益, 1.查询当天0点0分0秒 2.查询本月一号0点0分0秒 ...... Calendar calendar = Calendar.getInst ...
- .Net中异步任务的取消和监控
相关类型: CancellationTokenSource 主要用来创建或取消令牌 CancellationToken 监听令牌状态,注册令牌取消事件 OperationCanceledExcepti ...
- vue+Element-ui 的 el-cascader 做高德地图的省市区三级联动并且是异步加载,点击省市区跳转到对应的区(地图可以通过后端返回的点进行标点)
// 完整版高德地图,可以复制代码直接使用 index.html <script type="text/javascript" src="https://webap ...
- React Native startReactApplication 方法简析
在 React Native 启动流程简析 这篇文章里,我们梳理了 RN 的启动流程,最后的 startReactApplication 由于相对复杂且涉及到最终执行前端 js 的流程,我们单独将其提 ...
- AtomicStampedReference AtomicReference解决CAS机制中ABA问题
AtomicStampedReference AtomicReference解决CAS机制中ABA问题 AtomicStampedReference AtomicStampedReference它内部 ...
- Salesforce LWC学习(三十六) Quick Action 支持选择 LWC了
本篇参考: https://developer.salesforce.com/docs/component-library/documentation/en/lwc/lwc.use_quick_act ...
- 【进阶之路】持续集成、持续交付与持续部署(CI/CD)
由来 记得7月份刚刚换工作的时候,中午和老大一起去吃饭,回来的路上老大问我:"南橘,CI/CD有没有研究过?" 我隐隐约约在哪里听过这个名词,但是又想不起来,秉着实事求是的态度,我 ...
- NOIP模拟13「工业题·卡常题·玄学题」
T1:工业题 基本思路 这题有一个重要的小转化: 我们将原来的函数看作一个矩阵,\(f(i,j-1)*a\)相当于从\(j-1\)向右走一步并贡献a,\(f(i-1,j)*b\)相当于从\(i-1 ...