深度优先

这个家伙好懒,除了文章什么都没留下

0%

接口和钩子

在介绍生命周期的相关概念之前,可以先复习一下TypeScript对于接口的概念。
在这里主要使用的是类接口及其实现:

1
2
3
4
5
6
7
8
9
interface ClockInterface {
currentTime: Date;
}
// 该写法表示明确的强制一个Clock类符合ClockInterface接口
// 该接口中currentTime是一个Date类型的数据(并没有实际使用),创造Clock实例时需要传入参数h和m,都是数字类型。
class Clock implements ClockInterface {
currentTime: Date;
constructor(h: number, m: number) { }
}

在Angular中,也针对生命周期的不同时刻给予了一些接口,你可以在代码中强制自己的组件/指令在创建时实现这些接口。
而每个接口都有唯一的一个钩子方法,它们的名字是由接口名再加上ng前缀构成的。比如,OnInit接口的钩子方法叫做ngOnInit,Angular在创建组件后立刻调用它。

接口是可选的?

由于我们最终执行的代码是JavaScript,而在JS中是没有接口概念的,接口只是用来强化类的概念,经过编译之后接口消失了。
所以如果需要使用生命周期,只要在我们的代码中写上钩子函数即可,Angular会找到并调用像ngOnInit()这样的钩子方法,有没有接口无所谓。
但是最好在写代码的时候不要省去接口,享受TypeScript所带来的强类型好处~

生命周期的顺序

指令和组件的实例有一个生命周期:新建、更新和销毁。
当Angular使用构造函数新建一个组件或指令后,就会按下面的顺序在特定时刻调用这些生命周期钩子方法:

ngOnChanges()

用处:当Angular设置数据绑定输入属性发生变化时响应。
时机:当被绑定的输入属性的值发生变化时调用,不过首次调用是会发生在ngOnInit()之前的。
ngOnChanges()方法获取了一个对象,它可以同时观测1个/多个绑定的属性值,它把每个发生变化的属性名都映射到了一个SimpleChange对象, 该对象中有属性的当前值和前一个值。

1
2
3
4
// Angular定义SimpleChanges类构造函数三个参数分别为上一个值,当前值和是否第一次变化(firstChange: boolean),这些changes都可以调用。
ngOnChanges(changes: SimpleChanges) {
... some code about changes here...
}

ngOnInit()

用处:在Angular第一次显示数据绑定和设置指令/组件的输入属性之后,初始化指令/组件。
时机:在第一轮ngOnChanges()完成之后调用,只调用一次。
要明确一点就是,虽然接口中有一个constructor构造函数,但是在这里最好只对局部变量进行初始化之外什么都不做,通过ngOnInit()获取初始数据,而且是放在构造函数后面。

ngDoCheck()

用处:检测那些Angular自身无法捕获的变更
时机:在每个Angular变更检测周期中调用,ngOnChanges()和ngOnInit()之后。
谨慎使用,因为一些你意想不到的事情也会被视为变更了

ngAfterContentInit()和ngAfterContentChecked()

只适用于组件
用处:在外来内容被投影到组件中之后/投影组件内容的变更检测之后调用
时机:
ngAfterContentInit()–第一次ngDoCheck()之后调用,且只调用一次;
ngAfterContentChecked()–每次ngDoCheck()之后调用,如果需要调用ngAfterContentInit(),则在ngAfterContentInit()调用之后

ngAfterViewInit()和ngAfterViewChecked()

只适用于组件
用处:初始化完/检测变更完组件视图及其子视图之后调用。
时机:跟相应的content钩子类似,在对应的content钩子后面。

ngOnDestroy

用处:反订阅可观察对象和分离事件处理器,以防内存泄漏
时机:销毁指令/组件之前调用并清扫

释放那些不会被垃圾收集器自动回收的各类资源的地方。取消那些对可观察对象和DOM事件的订阅。停止定时器。注销该指令曾注册到全局服务或应用级服务中的各种回调函数。

我们在使用EF Core的时候,很多时候需要在Visual Studio的输出窗口中知道EF Core在后台生成的SQL语句是什么,这个需求可以通过自定义EF Core的ILoggerFactory和ILogger类来实现:

首先定义一个实现了ILogger接口的类EFLogger,主要目的是将EF Core生成的Log信息输出到Visual Studio的输出窗口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
using Microsoft.Extensions.Logging;
using System;
using System.Diagnostics;

namespace WebCore.Utils
{
public class EFLogger : ILogger
{
protected string categoryName;

public EFLogger(string categoryName)
{
this.categoryName = categoryName;
}

public IDisposable BeginScope<TState>(TState state)
{
return null;
}

public bool IsEnabled(LogLevel logLevel)
{
return true;
}

public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
//通过Debugger.Log方法来将EF Core生成的Log信息输出到Visual Studio的输出窗口
Debugger.Log(0, categoryName, "=============================== EF Core log started ===============================\r\n");
Debugger.Log(0, categoryName, formatter(state,exception)+"\r\n");
Debugger.Log(0, categoryName, "=============================== EF Core log finished ===============================\r\n");
}
}
}

然后定义一个实现了ILoggerFactory接口的类EFLoggerFactory,用于创建上面定义的EFLogger类的实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
using Microsoft.Extensions.Logging;

namespace WebCore.Utils
{
public class EFLoggerFactory : ILoggerFactory
{
public void AddProvider(ILoggerProvider provider)
{
}

public ILogger CreateLogger(string categoryName)
{
return new EFLogger(categoryName);//创建EFLogger类的实例
}

public void Dispose()
{

}
}
}

最后在DbContext的OnConfiguring方法中,调用optionsBuilder.UseLoggerFactory来将EFLoggerFactory类的实例注入给EF Core,这样所有DbContext的Log信息,都会由EFLogger类输出到Visual Studio的输出窗口了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata;
using WebCore.Utils;

namespace WebCore.Entities
{
public partial class TestDBContext : DbContext
{
public TestDBContext()
{
this.Database.SetCommandTimeout(0);//设置SqlCommand永不超时
}

public TestDBContext(DbContextOptions<TestDBContext> options)
: base(options)
{
}

public virtual DbSet<Person> Person { get; set; }

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
optionsBuilder.UseLoggerFactory(new EFLoggerFactory());//将EFLoggerFactory类的实例注入给EF Core,这样所有DbContext的Log信息,都会由EFLogger类输出到Visual Studio的输出窗口了
optionsBuilder.UseSqlServer("Server=localhost;User Id=sa;Password=1qaz!QAZ;Database=TestDB");
}
}

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
//省略代码...
}
}
}

注意OnConfiguring这个方法是virtual的,所以我们也可以选择在DbContext的子类中来重写这个方法,在子类的OnConfiguring中注入EFLoggerFactory类的实例到DbContext。

最后我们来看看EF Core日志输出的效果,还是很规整的,最重要的是我们可以实时的得到EF Core在后台生成的SQL语句:

每次对数据库连接时,我们有时候会碰到连接超时或者命令超时,这两个超时是不一样的。以ADO.NET为例,当客户端和服务器端连接时,碰到的超时情况主要有下面几种:
当从连接池获取一个连接时,碰到超时。
当建立一个全新连接(而不是从连接池获取)时,碰到超时。
当发送一个命令(command)到SQL Server时,超时。
当发送命令(连接字符串带有“context connection=true”属性)到SQL Server时,超时。
当不是显示的发送一个命令(implicitly)到SQL Server时,碰到超时。
当执行异步命令时,(BeginExecute)碰到超时。
当从服务器端,获取行时,碰到超时。
当用Bulk copy方式,上传数据时,碰到超时。
这些超时主要是通过连接字符串中的Connect Timeout和SqlCommand.CommandTimeout来进行控制。前面两种是登录超时由Connection Timeout来决定什么时候超时,后面几种是命令超时由Command Timeout来决定什么时候超时。

SqlConnection.ConnectionTimeout :

  1. 说明: 获取在尝试建立连接时终止尝试并生成错误之前所等待的时间。
  2. 默认值:等待连接打开的时间(以秒为单位)。默认值为 15 秒。
  3. 备注:值 0 指示无限制
  4. 只读

注意ConnectionTimeout可以在数据库连接字符串上直接进行设置,如下所示:

1
Server=(localdb)\mssqllocaldb;Database=yourDb;Trusted_connection=true;Connect Timeout=100;

这也是目前使用EF Core时,设置ConnectionTimeout比较好的一种方式

SqlCommand.CommandTimeout** :**

  1. 说明:获取或设置在终止执行命令的尝试并生成错误之前的等待时间。
  2. 默认值:等待命令执行的时间(以秒为单位)。默认为 30 秒。
  3. 备注:值 0 指示无限制,在 CommandTimeout 中应避免值 0,否则会无限期地等待执行命令。
  4. 可读可写

在EF Core中可以通过DbContext来调用SetCommandTimeout和GetCommandTimeout方法,来设置和获取CommandTimeout的值,如下所示:

1
2
3
4
DbContext dbContext = ...//创建DbContext对象

dbContext.Database.SetCommandTimeout(100);
int? commandTimeout = dbContext.Database.GetCommandTimeout();

特别注意:
“超时时间已到。在操作完成之前超时时间已过或服务器未响应”
类似这种错误,一般是 SqlCommand.CommandTimeout 或者 SqlBulkCopy.BulkCopyTimeout 的时间超时, 而不是 SqlConnection.ConnectionTimeout。