軟體分層架構模式

軟體分層架構模式 - 基本分層

最近在重構六年前做的產品,雖然當時已經有做分層架構,但還是有很多該改進的地方。
有些命名越看越不順眼,重構期間順便整理一下分層架構;不管在何時回頭看自己做的東西,都覺得很多進步空間。
本篇介紹一下常見的軟體分層架構模式 (Software Layered Architecture Pattern),以及推薦的命名方式。

分層架構簡介

基本分層

基本分層架構模式主分為:

  • 展示層 (Presentation Layer)
    • UI 互動相關的部分
  • 業務層 (Business Layer)
    • 處理業務邏輯的部分
  • 資料層 (Data Layer)
    • 處理資料存取的部分

在 Software Architecture Patterns – O’Reilly 書中 資料層 (Data Layer) 被分為 Persistence Layer 及 Database Layer,我個人比較喜歡 Microsoft Application Architecture Guide 用 Data Layer 的命名方式。畢竟資料來源不一定是資料庫,也可能是外部的 Services。

分層架構有一個很重要的特性,就是要把每一層的職責分離,不應該跨層互動,每層之間的關係只能是上下互動。
如圖:

軟體分層架構模式 - 基本分層

服務型分層

上述的三層為了做到職責分離,只能層層互動,卻缺少了一些彈性。如果要提供 API 給外部使用,就處於比較尷尬的位置;不屬於展示層,比較偏向業務層,但業務層直接打破隔離方式供人使用也怪怪的。
所以如果是服務型 (Service-Based) 的系統,會建議多出一層:

  • 服務層 (Service Layer)
    • 負責把封閉的分層開放給外部使用。

如圖:

軟體分層架構模式 - 服務型分層

命名方式

從網路上可以找到很多不同風格的命名方式,此章節只是我整理出我喜歡的命名風格,如果還沒有命名頭緒的話可以參考看看。 我大部分時間都是在開發 ASP.NET MVC/WebAPI2 所以會以 .NET 專案 為例。

Domain Project

專案相依:不應該相依於其他專案。
專案名稱:CompanyName.ProjectName.Domain

這個專案主要是用來分離各層相依關係的,內容含如下:

建議的命名範例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// DTO 範例
// Class 命名規則:不後綴 Entity。  
namespace CompanyName.ProjectName.Domain.Entities
{
  public class User {
    // ...
  }
}

// Enum 範例
// Class 命名規則:不後綴 Enum。 
namespace CompanyName.ProjectName.Domain.Enums
{
  public enum UserStatus {
    // ...
  }
}

Data Layer

專案相依:CompanyName.ProjectName.Domain
專案名稱:CompanyName.ProjectName.DataLayer

常見的命名有:UserDAL、UserEngine、UserManager、UserRepository 等。

DAL 全名 Data Access Layer,名稱應該是從 3-tier Architecture with ASP.NET 2.0 誕生出來的。

建議的命名Class 命名規則:名稱加上後綴 Manager
如果有用 Repository Pattern,就在 Class 名稱加上後綴 Repository
範例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Class 命名規則:加上後綴 Manager。 
namespace CompanyName.ProjectName.DataLayer.Managers
{
  public class RedisManager : IRedisManager {
    // ...
  }
}

// Repository Pattern 範例。 
// Class 命名規則:加上後綴 Repository 
namespace CompanyName.ProjectName.DataLayer.Repositorys
{
  public class UserRepository : IUserRepository {
    // ...
  }
}

Business Layer

專案相依:CompanyName.ProjectName.Domain 及 CompanyName.ProjectName.DataLayer 專案名稱:CompanyName.ProjectName.BusinessLayer

常見的命名有:UserBLL、UserLogic 等。

BLL 全名是 Business Logic Layer,名稱出現同 DAL。

建議的命名:Class 名稱加上後綴 Logic

網路上非常多的範例適用 BLL,尤其是 ASP.NET 的範例。
但我不推用 BLL 的原因是,Class 名稱出現連續的全大寫,看久了有點不舒服,還是比較習慣 Pascal Case

範例:

1
2
3
4
5
6
namespace CompanyName.ProjectName.BusinessLayer.Logics
{
  public class UserLogic : IUserLogic {
    // ...
  }
}

Service Layer

專案相依:CompanyName.ProjectName.Domain 及 CompanyName.ProjectName.BusinessLayer

API Library

由於 Service Layer 是屬於對外開放的接口,所以我並沒有特別推薦命名方式,不要太突兀就好。
可以參考許多第三方套件的 API 命名方式,例如常見的 Newtonsoft.Json

1
var json = Newtonsoft.Json.JsonConvert.SerializeObject(new { });

JsonConvert 就沒有特別加什麼後綴,用比較直觀式的命名方式,讓使用方容易懂就好。

Web API

專案如果是 Web API,我會直接取名為 CompanyName.ProjectName.WebService
命名方式建議使用 RESTful 風格,用起來比較乾淨俐落,好處可以參考 Wiki。

前陣子有人問我:

如果把 Web API 符合 RESTful,是不就變成 Data Layer 了?

這兩個層級的職責是完全不一樣的:

  • Service Layer:提供資源給外部使用,負責轉手資料。
  • Data Layer:負責提供及存取 Business Layer 收送的資料。

但如果從另一個角度來看,對調用方來說,Web API 也是它的 Data Layer,把 Web API 符合 RESTful 只是為了讓調用方更容易使用。

Presentation Layer

專案相依:CompanyName.ProjectName.Domain 及 CompanyName.ProjectName.ServiceLayer (或 CompanyName.ProjectName.BusinessLayer ) 專案名稱:CompanyName.ProjectName.Website

以 Web 專案來說,這層是屬與 HTML、jQuery 或 Angular 這類的前端框架。
如果有用前端框架,命名方式就依照該框架建議的指南命名。

如果是純前端框架,其實根本不用相宜於任何專案,是以 Web API 作為相依關係。

範例架構

專案相依關係:

軟體分層架構模式 - 專案相依關係

檔案架構大致如下:

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
CompanyName.ProjectName.Domain/         # Domain 專案
  Entities/
    User.cs
  Enums/
    UserStatus.cs
  Interfaces/                           # 分層隔離用到的介面
    Logics/
      IUserLogic.cs
    Managers/
      IRedisManager.cs
    Repositorys/
      IUserRepository.cs

CompanyName.ProjectName.DataLayer/      # Data Layer 專案
  Managers/
    RedisManager.cs
  Repositorys/
    UserRepository.cs

CompanyName.ProjectName.BusinessLayer/  # Business Layer 專案
  Logics/
    UserLogic.cs

CompanyName.ProjectName.WebService/     # Service Layer 專案
  Controllers/
    UserController.cs

CompanyName.ProjectName.Website/        # Angular 為例
  index.html                            # 起始頁面
  app/                                  # Angular 的主要目錄
    main.ts                             # bootstrap 的程式進入點

參考

Software Architecture Patterns – O’Reilly Media(推薦閱讀)
Chapter 5: Layered Application Guidelines – Microsoft Application Architecture Guide
Naming conventions DAL, BAL, and UI Layer
應用程式的分層設計 (1) – 入門範例

[鐵人賽 Day03] ASP.NET Core 2 系列 – Middleware

過去 ASP.NET 中使用的 HTTP Modules 及 HTTP Handlers,在 ASP.NET Core 中已不復存在,取而代之的是 Middleware。
Middleware 除了簡化了 HTTP Modules/Handlers 的使用方式,還帶入了 Pipeline 的概念。
本篇將介紹 ASP.NET Core 的 Middleware 概念及用法。

iT 邦幫忙 2018 鐵人賽 – Modern Web 組參賽文章:
[Day03] ASP.NET Core 2 系列 – Middleware

Middleware 概念

ASP.NET Core 在 Middleware 的官方說明中,使用了 Pipeline 這個名詞,意旨 Middleware 像水管一樣可以串聯在一起,所有的 Request 及 Response 都會層層經過這些水管。
用圖例可以很容易理解,如下圖:

[鐵人賽 Day03] ASP.NET Core 2 系列 - Middleware - 概念

App.Use

Middleware 的註冊方式是在 Startup.cs 的 Configure 對 IApplicationBuilder 使用 Use 方法註冊。
大部分擴充的 Middleware 也都是以 Use 開頭的方法註冊,例如:

  • UseMvc():MVC 的 Middleware
  • UseRewriter():URL rewriting 的 Middleware

一個簡單的 Middleware 範例。如下:

Startup.cs

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
// ...
public class Startup
{
    // ...
    public void Configure(IApplicationBuilder app)
    {
        app.Use(async (context, next) => 
        {
            await context.Response.WriteAsync("First Middleware in. \r\n");
            await next.Invoke();
            await context.Response.WriteAsync("First Middleware out. \r\n");
        });

        app.Use(async (context, next) => 
        {
            await context.Response.WriteAsync("Second Middleware in. \r\n");
            await next.Invoke();
            await context.Response.WriteAsync("Second Middleware out. \r\n");
        });

        app.Use(async (context, next) => 
        {
            await context.Response.WriteAsync("Third Middleware in. \r\n");
            await next.Invoke();
            await context.Response.WriteAsync("Third Middleware out. \r\n");
        });

        app.Run(async (context) =>
        {
            await context.Response.WriteAsync("Hello World! \r\n");
        });
    }
}

用瀏覽器打開網站任意連結,輸出結果:

1
2
3
4
5
6
7
First Middleware in. 
Second Middleware in. 
Third Middleware in. 
Hello World! 
Third Middleware out. 
Second Middleware out. 
First Middleware out.

在 Pipeline 的概念中,註冊順序是很重要的事情。資料經過的順序一定是先進後出

Request 流程如下圖:

[鐵人賽 Day03] ASP.NET Core 2 系列 - Middleware

Middleware 也可以作為攔截使用,如下:

Startup.cs

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
37
38
39
// ...
public class Startup
{
    // ...
    public void Configure(IApplicationBuilder app)
    {
        app.Use(async (context, next) => 
        {
            await context.Response.WriteAsync("First Middleware in. \r\n");
            await next.Invoke();
            await context.Response.WriteAsync("First Middleware out. \r\n");
        });

        app.Use(async (context, next) => 
        {
            await context.Response.WriteAsync("Second Middleware in. \r\n");
            
            // 水管阻塞,封包不往後送
            var condition = false;
            if(condition) {
                await next.Invoke();
            }

            await context.Response.WriteAsync("Second Middleware out. \r\n");
        });

        app.Use(async (context, next) => 
        {
            await context.Response.WriteAsync("Third Middleware in. \r\n");
            await next.Invoke();
            await context.Response.WriteAsync("Third Middleware out. \r\n");
        });

        app.Run(async (context) =>
        {
            await context.Response.WriteAsync("Hello World! \r\n");
        });
    }
}

輸出結果:

1
2
3
4
First Middleware in. 
Second Middleware in. 
Second Middleware out. 
First Middleware out.

在 Second Middleware 中,因為沒有達成條件,所以封包也就不在往後面的水管傳送。流程如圖:

[鐵人賽 Day03] ASP.NET Core 2 系列 - Middleware - 概念

App.Run

Run 是 Middleware 的最後一個行為,以上面圖例來說,就是最末端的 Action。
它不像 Use 能串聯其他 Middleware,但 Run 還是能完整的使用 Request 及 Response。

App.Map

Map 是能用來處理一些簡單路由的 Middleware,可依照不同的 URL 指向不同的 Run 及註冊不同的 Use
新增一個路由如下:

Startup.cs

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
// ...
public class Startup
{
    // ...
    public void Configure(IApplicationBuilder app)
    {
        app.Use(async (context, next) => 
        {
            await context.Response.WriteAsync("First Middleware in. \r\n");
            await next.Invoke();
            await context.Response.WriteAsync("First Middleware out. \r\n");
        });

        app.Map("/second", mapApp =>
        {
            mapApp.Use(async (context, next) => 
            {
                await context.Response.WriteAsync("Second Middleware in. \r\n");
                await next.Invoke();
                await context.Response.WriteAsync("Second Middleware out. \r\n");
            });
            mapApp.Run(async context =>
            {
                await context.Response.WriteAsync("Second. \r\n");
            });
        });

        app.Run(async context =>
        {
            await context.Response.WriteAsync("Hello World! \r\n");
        });
    }
}

開啟網站任意連結,會顯示:

1
2
3
First Middleware in. 
Hello World! 
First Middleware out.

開啟網站 http://localhost:5000/second,則會顯示:

1
2
3
4
5
First Middleware in. 
Second Middleware in. 
Second. 
Second Middleware out. 
First Middleware out.

建立 Middleware 類別

如果 Middleware 全部都寫在 Startup.cs,程式碼應該很難維護,所以應該把自製的 Middleware 邏輯獨立出來。
建立 Middleware 類別不需要額外繼承其它類別或介面,一般的類別即可,範例如下:

FirstMiddleware.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class FirstMiddleware
{
    private readonly RequestDelegate _next;

    public FirstMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task Invoke(HttpContext context)
    {
        await context.Response.WriteAsync($"{nameof(FirstMiddleware)} in. \r\n");

        await _next(context);

        await context.Response.WriteAsync($"{nameof(FirstMiddleware)} out. \r\n");
    }
}

全域註冊

在 Startup.Configure 註冊 Middleware 就可以套用到所有的 Request。如下:

Startup.cs

1
2
3
4
5
6
7
8
9
10
// ...
public class Startup
{
    // ...
    public void Configure(IApplicationBuilder app)
    {
        app.UseMiddleware<FirstMiddleware>();
        // ...
    }
}

區域註冊

Middleware 也可以只套用在特定的 Controller 或 Action。註冊方式如下:

Controllers\HomeController.cs

1
2
3
4
5
6
7
8
9
10
11
12
// ..
[MiddlewareFilter(typeof(FirstMiddleware))]
public class HomeController : Controller
{
    // ...

    [MiddlewareFilter(typeof(SecondMiddleware))]
    public IActionResult Index()
    {
        // ...
    }
}

Extensions

大部分擴充的 Middleware 都會用一個靜態方法包裝,如:UseMvc()UseRewriter()等。
自製的 Middleware 當然也可以透過靜態方法包,範例如下:

Extensions\CustomMiddlewareExtensions.cs

1
2
3
4
5
6
7
public static class CustomMiddlewareExtensions
{
    public static IApplicationBuilder UseFirstMiddleware(this IApplicationBuilder builder)
    {
        return builder.UseMiddleware<FirstMiddleware>();
    }
}

註冊 Extension Middleware 的方式如下:

Startup.cs

1
2
3
4
5
6
7
8
9
10
// ...
public class Startup
{
    // ...
    public void Configure(IApplicationBuilder app)
    {
        app.UseFirstMiddleware();
        // ...
    }
}

參考

ASP.NET Core Middleware Fundamentals
Creating Custom Middleware In ASP.Net Core

[鐵人賽 Day02] ASP.NET Core 2 系列 – 程式生命週期 (Application Lifetime)

要了解程式的運作原理,要先知道程式的進入點及生命週期。
過往 ASP.NET MVC 啟動方式,是繼承 HttpApplication 做為網站開始的進入點。
ASP.NET Core 改變了網站啟動的方式,變的比較像是 Console Application。
本篇將介紹 ASP.NET Core 的程式生命週期 (Application Lifetime) 及補捉 Application 停啟事件。

iT 邦幫忙 2018 鐵人賽 – Modern Web 組參賽文章:
[Day02] ASP.NET Core 2 系列 – 程式生命週期 (Application Lifetime)

程式進入點

.NET Core 把 Web 及 Console 專案都變成一樣的啟動方式,預設從 Program.cs 的 Program.Main 做為程式進入點,再從程式進入點把 ASP.NET Core 網站實例化。
我個人是覺得比 ASP.NET MVC 繼承 HttpApplication 的方式簡潔許多。

透過 .NET Core CLI 建置的 Program.cs 內容大致如下:
Program.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;

namespace MyWebsite
{
    public class Program
    {
        public static void Main(string[] args)
        {
            BuildWebHost(args).Run();
        }

        public static IWebHost BuildWebHost(string[] args) =>
            WebHost.CreateDefaultBuilder(args)
                .UseStartup<Startup>()
                .Build();
    }
}

Program.Main 透過 BuildWebHost 方法取得 WebHost 後,再啟動 WebHost;WebHost 就是 ASP.NET Core 的網站實體。

  • WebHost.CreateDefaultBuilder
    透過此方法建立 WebHost Builder。WebHost Builder 是用來產生 WebHost 的物件。
    可以在 WebHost 產生之前設定一些前置準備動作,當 WebHost 建立完成時,就可以使用已準備好的物件等。
  • UseStartup
    設定該 Builder 產生的 WebHost 啟動後,要執行的類別。
  • Build
    當前置準備都設定完成後,就可以跟 WebHost Builder 呼叫此方法實例化 WebHost,並得到該實例。
  • Run
    啟動 WebHost。

Startup.cs

當網站啟動後,WebHost 會實例化 UseStartup 設定的 Startup 類別,並且呼叫以下兩個方法:

  • ConfigureServices
  • Configure

透過 .NET Core CLI 建置的 Startup.cs 內容大致如下:
Startup.cs

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
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;

namespace MyWebsite
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            // ...
        }

        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.Run(async (context) =>
            {
                await context.Response.WriteAsync("Hello World!");
            });
        }
    }
}

對 WebHost 來說 Startup.cs 並不是必要存在的功能。
可以試著把 Startup.cs 中的兩個方法,都改成在 WebHost Builder 設定,變成啟動的前置準備。如下:

Program.cs

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
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;

namespace MyWebsite
{
    public class Program
    {
        public static void Main(string[] args)
        {
            BuildWebHost(args).Run();
        }

        public static IWebHost BuildWebHost(string[] args) =>
            WebHost.CreateDefaultBuilder(args)
                .ConfigureServices(services =>
                {
                    // ...
                })
                .Configure(app =>
                {
                    app.Run(async (context) =>
                    {
                        await context.Response.WriteAsync("Hello World!");
                    });
                })
                .Build();
    }
}

把 ConfigureServices 及 Configure 都改到 WebHost Builder 註冊,網站的執行結果會是一樣的。

兩者之間最大的差異就是呼叫時間點不同。

  • 在 WebHost Builder 註冊,是在 WebHost 實例化之前呼叫。
  • 在 Startup.cs 註冊,是在 WebHost 實例化之後呼叫。

但 Configure 無法使用除了 IApplicationBuilder 以外的參數。
因為在 WebHost 實例化前,自己都還沒被實例化,怎麼可能會有物件能注入給 Configure

Application Lifetime

除了程式進入點外,WebHost 的停起也是網站事件很重要一環,ASP.NET Core 不像 ASP.NET MVC 用繼承的方式補捉啟動及停止事件。 是透過 Startup.Configure 注入 IApplicationLifetime 來補捉 Application 停啟事件。

IApplicationLifetime 有三個註冊監聽事件及終止網站事件可以觸發。如下:

1
2
3
4
5
6
7
public interface IApplicationLifetime
{
  CancellationToken ApplicationStarted { get; }
  CancellationToken ApplicationStopping { get; }
  CancellationToken ApplicationStopped { get; }
  void StopApplication();
}
  • ApplicationStarted
    當 WebHost 啟動完成後,會執行的啟動完成事件
  • ApplicationStopping
    當 WebHost 觸發停止時,會執行的準備停止事件
  • ApplicationStopped
    當 WebHost 停止事件完成時,會執行的停止完成事件
  • StopApplication
    可以透過此方法主動觸發終止網站

IApplicationLifetime 需要 Microsoft.AspNetCore.Hosting 套件。
不過 ASP.NET Core 2.0 以上版本,預設是參考 Microsoft.AspNetCore.All,已經包含 Microsoft.AspNetCore.Hosting,所以不用再安裝。
如果是 ASP.NET Core 1.0 的版本,可以透過 .NET Core CLI 在專案資料夾執行安裝指令:

1
dotnet add package Microsoft.AspNetCore.Hosting

範例程式

透過 Console 輸出執行的過程,範例如下:
Program.cs

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
37
38
39
40
41
42
43
using System;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;

namespace MyWebsite
{
    public class Program
    {
        public static void Main(string[] args)
        {
            Output("Application - Start");
            var webHost = BuildWebHost(args);
            Output("Run WebHost");
            webHost.Run();
            Output("Application - End");
        }

        public static IWebHost BuildWebHost(string[] args)
        {
            Output("Create WebHost Builder");
            var webHostBuilder = WebHost.CreateDefaultBuilder(args)
                .ConfigureServices(services =>
                {
                    Output("webHostBuilder.ConfigureServices - Called");
                })
                .Configure(app =>
                {
                    Output("webHostBuilder.Configure - Called");
                })
                .UseStartup<Startup>();

            Output("Build WebHost");
            var webHost = webHostBuilder.Build();

            return webHost;
        }

        public static void Output(string message)
        {
            Console.WriteLine($"[{DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss")}] {message}");
        }
    }
}

Startup.cs

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
using System.Threading;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;

namespace MyWebsite
{
    public class Startup
    {
        public Startup()
        {
            Program.Output("Startup Constructor - Called");
        }

        public void ConfigureServices(IServiceCollection services)
        {
            Program.Output("Startup.ConfigureServices - Called");
        }

        public void Configure(IApplicationBuilder app, IApplicationLifetime appLifetime)
        {
            appLifetime.ApplicationStarted.Register(() =>
            {
                Program.Output("ApplicationLifetime - Started");
            });

            appLifetime.ApplicationStopping.Register(() =>
            {
                Program.Output("ApplicationLifetime - Stopping");
            });

            appLifetime.ApplicationStopped.Register(() =>
            {
                Thread.Sleep(5 * 1000);
                Program.Output("ApplicationLifetime - Stopped");
            });

            app.Run(async (context) =>
            {
                await context.Response.WriteAsync("Hello World!");
            });

            // For trigger stop WebHost
            var thread = new Thread(new ThreadStart(() =>
            {
                Thread.Sleep(5 * 1000);
                Program.Output("Trigger stop WebHost");
                appLifetime.StopApplication();
            }));
            thread.Start();

            Program.Output("Startup.Configure - Called");
        }
    }
}

執行結果

[鐵人賽 Day02] ASP.NET Core 2 系列 - 程式生命週期 (Application Lifetime) - 執行結果

輸出內容少了 webHostBuilder.Configure – Called,因為 Configure 只能有一個,後註冊的 Configure 會把之前註冊的蓋掉。

物件執行流程如下:

[鐵人賽 Day02] ASP.NET Core 2 系列 - 程式生命週期 (Application Lifetime) - 物件執行流程

參考

Application startup in ASP.NET Core
Hosting in ASP.NET Core

[鐵人賽 Day01] ASP.NET Core 2 系列 – 從頭開始

來勢洶洶的 .NET Core 似乎要取代 .NET Framework,ASP.NET 也隨之發佈 .NET Core 版本。雖說名稱沿用 ASP.NET,但相較於 ASP.NET 確有許多架構上的差異,可說是除了名稱外,已是兩個不同的框架。
本系列文將介紹 ASP.NET Core 入門教學及一些實務運用的範例,本篇主要介紹基本的 ASP.NET Core 環境準備及如何用 Visual Studio Code (VS Code) 開發 ASP.NET Core。

iT 邦幫忙 2018 鐵人賽 – Modern Web 組參賽文章:
[Day01] ASP.NET Core 2 系列 – 從頭開始

前言

要開發 .NET Core 必需要安裝 .NET Core SDK,所以先到官網下載 .NET Core SDK 的安裝檔,官網下載位置點我

.NET Core 是跨作業系統的框架,不再像 .NET Framework 要依附在 Windows 的作業系統才能執行,所以你可以依照你需要的版本進行下載及安裝。
雖然我的電腦是 Windows 作業系統,但接下來的系列教學都會是以指令為主。
(安裝軟體步驟太簡單,除了按下一步以外,幾乎沒什麼好解說的,所以不介紹怎麼安裝軟體。)

安裝完成後,可以透過 .NET Core CLI (Command-Line Interface)確認 .NET Core SDK 安裝的版本,指令如下:

1
dotnet --version

建立網站專案

先建立一個專案資料夾 MyWebsite,然後在該資料夾執行 .NET Core CLI 建置網站的指令:

1
dotnet new web

[鐵人賽 Day01] ASP.NET Core 2 系列 - 從頭開始 - 建立專案

.NET Core CLI 會在該資料夾,建立一個空的 ASP.NET Core 專案,內容如下:

[鐵人賽 Day01] ASP.NET Core 2 系列 - 從頭開始 - 專案目錄

1
2
3
4
5
obj/                            # 專案暫存目錄
wwwroot/                        # 預設網站根目錄 (空的)
MyWebsite.csproj                # 專案檔
Program.cs                      # 程式進入檔
Startup.cs                      # 啟動網站設定

啟動網站

建立完成後,就可以用 .NET Core CLI 啟動網站了。啟動網站指令:

1
dotnet run

.NET Core CLI 預設會起一個http://localhost:5000/的站台,用瀏覽器打開此連結就可以看到 ASP.NET Core 網站了。如下:

[鐵人賽 Day01] ASP.NET Core 2 系列 - 從頭開始 - 啟動網站

Visual Studio Code

.NET Core 都已經跨作業系統了,開發工具當然也就不再限制於 Visual Studio IDE (Visual Studio 2017/2015 等)。基本上純文字編輯器搭配 .NET Core CLI 就可以開發 ASP.NET Core 了,但沒有中斷點除錯或 Autocomplete 開發有些辛苦。如果是 Windows 作業系統,最推薦的當然還是 Visual Studio IDE,再來就是 Visual Studio Code (簡稱 VS Code)。

VS Code 是一套可安裝擴充套件的文字編輯器,有支援 Windows、Mac 及 Linux 版本,極輕量又免費。
只要安裝擴充套件就變成了 IDE,並且支援多種不同的程式語言。下載位置點我

安裝擴充套件

打開 VS Code 可以在左邊看到五個 Icon,點選最下面的那個 Extensions 圖示,並在 Extensions 搜尋列輸入 C# ,便可以找到 C# 的擴充套件安裝。如下圖:

[鐵人賽 Day01] ASP.NET Core 2 系列 - 從頭開始 - VS Code C# 擴充套件

開啟專案

VS Code 跟一般文字編輯器有些不同,它是以資料夾為工作區域,開啟一個目錄,就等通於是開啟一個專案。從上方工具列 File -> Open Folder 選擇 ASP.NET Core 專案目錄,大概隔幾秒後,VS Code 會提示是否要幫此專案加入 Build/Debug 的設定。如下圖:

[鐵人賽 Day01] ASP.NET Core 2 系列 - 從頭開始 - VS Code 開啟專案

Build/Debug 設定

如果沒有自動提示加入 Build/Debug 設定,可以在左邊 Icon,點選倒數第二個 Debug 圖示,手動加入 Build/Debug 設定。如下步驟:

[鐵人賽 Day01] ASP.NET Core 2 系列 - 從頭開始 - VS Code Build/Debug 設定[鐵人賽 Day01] ASP.NET Core 2 系列 - 從頭開始 - VS Code Build/Debug 設定

設定完成後,VS Code 會自動建立 .vscode 目錄及設定檔 launch.jsontasks.json。目錄結構如下:

1
2
3
4
5
6
7
8
.vscode/                        # VS Code 設定檔目錄
  launch.json                   # 用 VS Code 啟動程式的設定檔
  tasks.json                    # 定義 launch.json 會用道的指令設定檔
obj/                            # 專案暫存目錄
wwwroot/                        # 預設網站根目錄 (空的)
MyWebsite.csproj                # 專案檔
Program.cs                      # 程式進入點
Startup.cs                      # 啟動網站設定

中斷點除錯

在程式碼行號左邊點擊滑鼠就可以下中斷點了,跟一般 IDE 差不多。然後在 Debug 側欄啟動偵錯:

[鐵人賽 Day01] ASP.NET Core 2 系列 - 從頭開始 - VS Code 中斷點除錯

當執行到該中斷點後,就會停下來,並在 Debug 側欄顯示當前變數狀態等,也可以用滑鼠移到變數上面檢視該變數的內容。如下:

[鐵人賽 Day01] ASP.NET Core 2 系列 - 從頭開始 - VS Code 中斷點除錯

偵錯方式跟大部分的 IDE 都差不多,可以 Step over、Step in/out 等。
如此一來就可以用 VS Code 輕鬆開發 ASP.NET Core。

ProGet – 架設內部 NuGet Server

系統規模較大或模組較多時,並不適合用專案相依,避免編譯太久及程式碼管理的問題等。
常見的方式是將 DLL 編譯出來,再給需要的專案參考,但同步 DLL 的過程需要控管,以免拿錯版本。
比較好的方式是透過 Dependency Service 解決專案相依的問題,而 .NET 的 Dependency Service 主要是 NuGet。
本篇介紹如何透過 ProGet 架設內部 NuGet Server。

前言

ProGet 是一套支援多種 Dependency Service 工具,它支援以下 Feed 服務:

  • Bower
  • Chocolatey
  • Docker
  • Maven
  • npm
  • NuGet (本篇重點)
  • PowerShell
  • Ruby Gems
  • Universal
  • VSIX

並且有 Windows 版本及 Linux(Docker) 版本可以架設,本篇重點將以 Windows 版本架設為主。
ProGet 有分付費版免費版,免費版支援的 Feed 跟付費版一樣,功能也沒被閹割太多,詳細差異可以看 Features by Edition。如果是要公司內部自用就放心的架吧!

安裝 ProGet

先下載 ProGet 安裝檔:下載

安裝步驟如下:
ProGet - 架設內部 NuGet Server

選擇版本,我直接選免費版,要試用企業版的人,也可以選企業版:
ProGet - 架設內部 NuGet Server

輸入註冊資訊(必填):
ProGet - 架設內部 NuGet Server

安裝路徑:
ProGet - 架設內部 NuGet Server

選擇 SQL Server 位置:

  • 如果沒有 SQL Server,選第一個 New Instance of SQL Express,它會自動幫你下載 SQL Express 及安裝。
    ProGet - 架設內部 NuGet Server
  • 如果已經有現成的 SQL Server 可以用,先建立好一個名稱為 ProGet 的資料庫,並給它資料庫的連線字串。
    ProGet - 架設內部 NuGet Server

選擇 Web Server,我在這邊是把 ProGet 架在 IIS 上面,如果沒有安裝 IIS 可以選擇有 Windows Service 的方式運行 ProGet:
ProGet - 架設內部 NuGet Server

設定 ProGet Server 運行的權限:
ProGet - 架設內部 NuGet Server

設定完成開始安裝:
ProGet - 架設內部 NuGet Server

新增 NuGet Feed

安裝好後用瀏覽器開啟 ProGet 用 Admin 登入,打開 Feeds 頁面,選擇 Create New Feed

Admin 預設帳號密碼都是 Admin
例如:安裝在本機 Prot 81 的話,開啟 URL 就是 http://localhost:81

ProGet - 架設內部 NuGet Server - Create New Feed

Feed Type 選擇 NuGet Feed,Feed Name 自訂:
ProGet - 架設內部 NuGet Server - Create New Feed

NuGet Feed 新增完成:ProGet - 架設內部 NuGet Server - Create New Feed

NuGet Feed 新增完成後,就可以透過 NuGet Push 指令把 NuGet Package 上傳到 ProGet 囉~

NuGet Package

在 Feeds 清單中,點進剛剛建立的 Feed,選擇 Add Package,就可以看到上傳 NuGet Package 的方式。
如下:

ProGet - 架設內部 NuGet Server - NuGet Package

API endpoint URL 就是 NuGet Feed 的 URL,可以透過這個 URL 上傳或下載 NuGet Package。

ProGet 有提供四種上傳 NuGet Package 的方式:

  • 從頁面上傳 *.nupkg
  • 透過 NuGet Push 指令上傳
  • 從其他 NuGet Server 同步過來
  • 從實體路徑載入

本篇以 NuGet Push 指令為主,NuGet.exe 可以到 NuGet 官網下載

打包

假設要打包 SampleLibrary 的專案,先用 Visual Studio 或 MSBuild 建置,建置完成後就可以透過 NuGet pack 指令打包 *.nupkg 檔案。指令如下:

1
NuGet.exe pack C:\SampleLibrary\SampleLibrary.csproj -Version 1.0.0.1 -Properties "Configuration=Release;OutDir=C:\SampleLibrary\SampleLibrary\bin\Release"
  • Version:要上傳到 NuGet Server 的版本不能重複。
  • OutDir:編譯後 DLL 的位置。

如果是 .NET Core 專案,用 dotnet pack 指令打包,參數可以參考官網

上傳

用 NuGet pack 打包完成後,就可以把 *.nupkg 上傳到 NuGet Server。
指令如下:

1
NuGet.exe push SampleLibrary.1.0.0.1.nupkg -ApiKey Admin:Admin -Source http://localhost:81/nuget/internal/
  • ApiKey:預設可以用 ProGet 的帳號密碼當做 NuGet ApiKey,從 ProGet 的管理中也能設定專用的 ApiKey,有興趣的可以研究看看。

在 NuGet 管理中新增 NuGet Feed,如下:

ProGet - 架設內部 NuGet Server - NuGet Package

上傳完成就可以在 NuGet 管理中,看到自製的 NuGet Package 了。

ProGet - 架設內部 NuGet Server - NuGet Package

【详细】【转】CentOS 7部署ASP.NET Core应用程序

很早就看过关于net core部署在Linux上的文章,自己也曾亲自将项目部署在Linux上,今天看到这篇文章,为其格式之工整而转!

1.环境准备

网上看了一下,Linux云服务器还挺贵的,那就只好先用VMware虚拟机搭建个吧。这里我选装的Linux系统版本的是CentOS,Linux系统众多发行版之一,相信各位园友也不陌生。

软件版本信息如下:

虚拟机安装、系统镜像配置过程,大家参考网上教程应该没啥问题,我这不再赘述。

成功安装并登陆CentOS 7 Minimal系统后,发现无法上网,后来搜索找到原因:CentOS 7 Minimal 默认没有启动网络配置。

接下来就通过vi命令编辑网卡配置文件ifcfg-ens33(其他版本名称可能略有不同,但路径一致)。具体命令如下:

vi /etc/sysconfig/network-scripts/ifcfg-ens33

ONBOOT=no改为ONBOOT=yes,设置随系统开机运行。然后:wq强制保存并退出编辑文件即可。

最后,需要重启一下网络服务。命令如下:

service network restart

目前应该可以上网了,可以尝试用命令ping www.baidu.com来检查下网络是否通畅。

如上图,可以接收响应,证明网络畅通。反之,证明你的网络还是不通,就需要考虑其他原因了。

暂时还没有安装VMware Tools,直接在虚拟机中编(复)写(制)命令多有不便,这里可以通过连接工具PuTTY在Window系统中连接虚拟机中的CentOS系统,连接服务器也同样适用。

软件截图:

选择SSH连接协议,输入CentOS系统IP地址,点击【Open】按钮连接。输入系统用户名密码即可登录系统。

问:怎么知道虚拟机中CentOS系统的IP地址呢?

答:可以通过ifconfig命令来获取系统的IP地址。说明一点,因为这里我选的是CentOS 7 Minimal最小化安装,需要联网后先执行命令yum install net-tools下载网络工具包,然后才能使用ifconfig命令。

2.安装.NET Core SDK

Linux各个发行版如何安装.NET Core SDK,微软官方已经给出了标准答案,我这里就把在CentOS上的安装方法做一个简要说明。

Step1:安装dotnet产品提要

要开始安装.NET,您需要注册Microsoft签名密钥并添加Microsoft产品提要。每台机器只需要做一次。 打开命令提示符并运行以下命令:

sudo rpm --import https://packages.microsoft.com/keys/microsoft.asc
sudo sh -c 'echo -e "[packages-microsoft-com-prod]\nname=packages-microsoft-com-prod \nbaseurl= https://packages.microsoft.com/yumrepos/microsoft-rhel7.3-prod\nenabled=1\ngpgcheck=1\ngpgkey=https://packages.microsoft.com/keys/microsoft.asc" > /etc/yum.repos.d/dotnetdev.repo'

Step2:安装.NET SDK

更新可用于安装的产品,安装.NET所需的组件,然后安装.NET SDK。
在命令提示符下,运行以下命令:

sudo yum update
sudo yum install libunwind libicu
sudo yum install dotnet-sdk-2.1.3  #最新版本请关注官网

两步即可完成.NET Core SDK在CentOS7系统下的安装,最后可执行一下命令dotnet --version,如果正常输出版本号则说明安装没有出现问题。

Linux其他发行版安装.NET Core SDK教程请参见官方教程 Get started with .NET in 10 minutes

3.部署ASP.NET Core应用程序

下面就尝试把我用ASP.NET Core Web API 开发的一个接口网站部署到我们已经安装.NET SDK的CentOS系统(下文简称服务器)中。

程序发布过程省略,把编译后的程序发布到了本地F:\wwwroot\Scorpio文件夹。

然后借助FTP工具FileZilla把程序文件传输到服务器/home/wwwroot/scorpio文件夹。

上传截图:

上传完毕后,需要先通过cd命令进入网站根目录/home/wwwroot/scorpio/,再输入如下命令启动网站程序:

dotnet Scorpio.WebApi.dll 

如果在任意非站点根目录,通过下面这种方式直接运行,程序会抛异常,不知是程序原因还是其他原因。

dotnet /home/wwwroot/scorpio/Scorpio.WebApi.dll 

如果你可以看到如下界面则表示程序启动成功。

4.Nginx配置反向代理

Nginx是一个高性能的Web服务器软件。这是一个比Apache HTTP Server更加灵活和轻量级的程序。

我们的网站程序启动的端口是5000,可以借助Nginx把程序5000端口映射到80端口。

Nginx官方文档 & Nginx开发从入门到精通 – Tengine

4.1.安装Nginx

首先,我们需要在服务器上安装Nginx。

Step1:添加Nginx存储库

要添加CentOS 7 EPEL仓库,请打开终端并使用以下命令:

sudo yum install epel-release

Step2:安装Nginx

现在Nginx存储库已经安装在您的服务器上,请使用以下yum命令安装Nginx:

sudo yum install nginx

Step3:启动Nginx

Nginx不会自行启动。要运行Nginx,请输入:

sudo systemctl start nginx

如果您正在运行防火墙,请运行以下命令以允许HTTP和HTTPS通信:

sudo firewall-cmd --permanent --zone=public --add-service=http 
sudo firewall-cmd --permanent --zone=public --add-service=https
sudo firewall-cmd --reload

此时,可以在本机的浏览器中访问服务器的IP地址http://192.168.83.128来验证Nginx是否成功运行。

如果能看到Nginx的默认转发网页则说明一切正常。如下截图:

部分截图

如果拒绝访问,考虑服务器80端口是否开放。可尝试通过下面两条命令开放80端口、重启防火墙使修改即时生效。

firewall-cmd --zone=public --add-port=80/tcp --permanent
systemctl restart firewalld

4.2.设置Nginx开机启动

避免开机需要手动开启Nginx,可以通过如下快捷命令把Nginx配置成系统服务,并设置为开机启动:

systemctl enable nginx  #设置开机启动

其他命令:

systemctl disable nginx   #禁止开机启动
systemctl status nginx     #查看运行状态
systemctl restart nginx    #重启服务

4.3.修改Nginx配置文件

首先,拿到Nginx的默认配置文件/etc/nginx/nginx.conf,把默认80端口转发配置server节点用#符注释掉。

然后,我们新建一个配置文件netcore.conf,内容如下:

server {
    listen 80;
    location / {
        proxy_pass http://localhost:5000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection keep-alive;
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
    }
}

保存并上传到Nginx的配置加载目录/etc/nginx/conf.d,最后执行命令nginx -s reload重启Nginx即可。

在本地浏览器上访问服务器地址,运行结果如下:

这个问题是由于SELinux保护机制所导致,我们需要将Nginx添加至SELinux的白名单。执行命令:

yum install policycoreutils-python

sudo cat /var/log/audit/audit.log | grep nginx | grep denied | audit2allow -M mynginx

sudo semodule -i mynginx.pp

再次访问,运行结果如下:

可以看到,访问的接口成功返回数据,证明Nginx已经完成对我们部署应用程序的转发。

5.Supervisor配置守护进程

Supervisor是用Python开发的Linux/Unix系统下的一个进程管理工具。它可以使进程脱离终端,变为后台守护进程(daemon)。实时监控进程状态,异常退出时能自动重启。

Supervisor不支持任何版本的Window系统;仅支持在Python2.4或更高版本,但不能在任何版本的Python 3下工作。

其主要组成部分:

supervisord:Supervisor的守护进程服务,用于接收进程管理命令;

supervisorctl:Supervisor命令行工具,用于和守护进程通信,发送管理进程的指令;

Web Server:Web端进程管理工具,提供与supervisorctl类似功能,管理进程;

XML-RPC Interface:提供XML-RPC接口,请参阅XML-RPC API文档

5.1.安装Supervisor

联网状态下,官方推荐首选安装方法是使用easy_install,它是setuptools(Python包管理工具)的一个功能。所以先执行如下命令安装 setuptools:

yum install python-setuptools

请更换root用户,执行如下命令安装Supervisor:

easy_install supervisor

5.2.配置Supervisor

运行supervisord服务的时候,需要指定Supervisor配置文件,如果没有显示指定,默认会从以下目录中加载:

$CWD/supervisord.conf  #$CWD表示运行supervisord程序的目录
$CWD/etc/supervisord.conf
/etc/supervisord.conf
/etc/supervisor/supervisord.conf (since Supervisor 3.3.0)
../etc/supervisord.conf (Relative to the executable)
../supervisord.conf (Relative to the executable)

所以,先通过如下命令创建目录,以便让Supervisor成功加载默认配置:

mkdir /etc/supervisor

加载目录有了,然后通过echo_supervisord_conf程序(用来生成初始配置文件)来初始化一个配置文件:

echo_supervisord_conf > /etc/supervisor/supervisord.conf

打开supervisord.conf文件,可以看到echo_supervisord_conf已经帮我们初始化好了一个样例配置,我们需要简单修改一下。

尾部找到如下文本片段:

;[include]
;files = relative/directory/*.ini

改为:

[include]
files = conf.d/*.conf

即,把注释去除、设置/etc/supervisor/conf.d为Supervisor进程配置文件加载目录。

这样,Supervisor会自动加载该目录下.conf后缀的文件作为共同服务配置。Supervisor管理的每个进程单独写一个配置文件放在该目录下,supervisord.conf配置文件中保留公共配置。

创建进程配置加载目录:

mkdir /etc/supervisor/conf.d

接下来就需要为我们已经部署的ASP .NET Core程序的宿主进程创建一个进程配置文件netcore.conf,保存并上传到/etc/supervisor/conf.d目录。

配置文件netcore.conf内容如下:

[program:Scorpio.WebApi]                        ;自定义进程名称
command=dotnet Scorpio.WebApi.dll               ;程序启动命令
directory=/home/wwwroot/scorpio                 ;命令执行的目录
autostart=true                                  ;在Supervisord启动时,程序是否启动
autorestart=true                                ;程序退出后自动重启
startretries=5                                  ;启动失败自动重试次数,默认是3
startsecs=1                                     ;自动重启间隔
user=root                                       ;设置启动进程的用户,默认是root
priority=999                                    ;进程启动优先级,默认999,值小的优先启动
stderr_logfile=/var/log/Scorpio.WebApi.err.log  ;标准错误日志
stdout_logfile=/var/log/Scorpio.WebApi.out.log  ;标准输出日志
environment=ASPNETCORE_ENVIRONMENT=Production   ;进程环境变量
stopsignal=INT                                  ;请求停止时用来杀死程序的信号

启动Supervisor服务,命令如下:

supervisord -c /etc/supervisor/supervisord.conf

这时,在会发现我们部署的网站程序不在shell中通过dotnet xxx.dll启动,同样可以访问。

5.3.设置Supervisor开机启动

首先为Supervisor新建一个启动服务脚本supervisor.service,然后保存并上传至服务器/usr/lib/systemd/system/目录。

脚本内容如下:

# supervisord service for systemd (CentOS 7.0+)
# by ET-CS (https://github.com/ET-CS)
[Unit]
Description=Supervisor daemon

[Service]
Type=forking
ExecStart=/usr/bin/supervisord -c /etc/supervisor/supervisord.conf
ExecStop=/usr/bin/supervisorctl $OPTIONS shutdown
ExecReload=/usr/bin/supervisorctl $OPTIONS reload
KillMode=process
Restart=on-failure
RestartSec=42s

[Install]
WantedBy=multi-user.target

设置开启启动:

systemctl enable supervisor

验证是否成功:

systemctl is-enabled supervisor

如果输出enabled则表示设置成功,也可重启服务器验证。

其它Linux发行版开机启动脚本 User-contributed OS init scripts for Supervisor

5.4.Supervisorctl管理进程

Supervisor服务启动后,受其管理的进程会在后台运行。可以通过supervisorctl客户端管理进程。

输入如下命令进入supervisorctl交互终端,按Ctrl+C键退出:

supervisorctl

输入help查询帮助:

supervisor> help

default commands (type help <topic>):
=====================================
add    exit      open  reload  restart   start   tail
avail  fg        pid   remove  shutdown  status  update
clear  maintail  quit  reread  signal    stop    version

输入help ****查询详细命令,比如输入help stop

supervisor> help stop

stop <name>             Stop a process
stop <gname>:*          Stop all processes in a group
stop <name> <name>      Stop multiple processes or groups
stop all                Stop all processes

如何启动、停止、重启进程等命令,我这里就不在记录,大家自行查找吧。

除此之外,Supervisor还提供了Web管理界面用来管理进程,如何配置启动请参考官方文档。

至此,我们已经完成了ASP.NET Core应用程序在CentOS7服务器上的部署。

6.相关阅读

【转】:http://www.cnblogs.com/esofar/p/8043792.html

欢迎大家关注我都我的微信 公众号,公众号涨粉丝人数,就是你们对我的喜爱程度!

Entity Framework Core 2.0 入门

http://www.cnblogs.com/cgzl/p/8543772.html

该文章比较基础, 不多说废话了, 直接切入正题.

该文分以下几点:

  • 创建Model和数据库
  • 使用Model与数据库交互
  • 查询和保存关联数据

EF Core支持情况

EF Core的数据库Providers:

此外还即将支持CosmosDB和 Oracle.

EFCore 2.0新的东西:

查询:

  • EF.Functions.Like()
  • Linq解释器的改进
  • 全局过滤(按类型)
  • 编译查询(Explicitly compiled query)
  • GroupJoin的SQL优化.

映射:

  • Type Configuration 配置
  • Owned Entities (替代EF6的复杂类型)
  • Scalar UDF映射
  • 分表

性能和其他

  • DbContext Pooling, 这个很好
  • Raw SQL插入字符串.
  • Logging
  • 更容易定制配置

1.创建数据库和Model

准备.net core项目

项目结构如图:

由于我使用的是VSCode, 所以需要使用命令行:

复制代码
mkdir LearnEf && cd LearnEf
dotnet new sln // 创建解决方案

mkdir LearnEf.Domains && cd LearnEf.Domains
dotnet new classlib // 创建LearnEf.Domains项目

cd ..
mkdir LearnEf.Data && cd LearnEf.Data
dotnet new classlib // 创建LearnEf.Data项目

cd ..
mkdir LearnEf.UI && cd LearnEf.UI
dotnet new console // 创建控制台项目

cd ..
mkdir LearnEf.Tests && cd LearnEf.Tests
dotnet new xunit // 创建测试项目
复制代码

为解决方案添加项目:

dotnet sln add LearnEf.UI/LearnEf.UI.csproj
dotnet sln add LearnEf.Domains/LearnEf.Domains.csproj
dotnet sln add LearnEf.Data/LearnEf.Data.csproj
dotnet sln add LearnEf.Tests/LearnEf.Tests.csproj

 

为项目之间添加引用:

LearnEf.Data依赖LearnEf.Domains:

cd LearnEf.Data
dotnet add reference ../LearnEf.Domains/LearnEf.Domains.csproj

 

LearnEf.Console依赖LearnEf.Domains和LearnEf.Data:

cd ../LearnEf.UI
dotnet add reference ../LearnEf.Domains/LearnEf.Domains.csproj ../LearnEf.Data/LearnEf.Data.csproj

 

LearnEf.Test依赖其它三个项目:

cd ../LearnEf.Tests
dotnet add reference ../LearnEf.Domains/LearnEf.Domains.csproj ../LearnEf.Data/LearnEf.Data.csproj ../LearnEf.UI/LearnEf.UI.csproj

 

(可能需要执行dotnet restore)

在Domains项目下直接建立两个Model, 典型的一对多关系Company和Department:

复制代码
using System;
using System.Collections.Generic;

namespace LearnEf.Domains
{
    public class Company
    {
        public Company()
        {
            Departments = new List<Department>();
        }

        public int Id { get; set; }
        public string Name { get; set; }
        public DateTime StartDate { get; set; }
        public List<Department> Departments { get; set; }
    }
}
复制代码
复制代码
namespace LearnEf.Domains
{
    public class Department
    {
        public int Id { get; set; }
        public int CompanyId { get; set; }
        public Company Company { get; set; }
    }
}
复制代码

 

添加Entity Framework Core库:

首先Data项目肯定需要安装这个库, 而我要使用sql server, 参照官方文档, 直接在解决方案下执行这个命令:

dotnet add ./LearnEf.Data package Microsoft.EntityFrameworkCore.SqlServer
dotnet restore

 

创建DbContext:

在Data项目下创建MyContext.cs:

复制代码
using LearnEf.Domains;
using Microsoft.EntityFrameworkCore;

namespace LearnEf.Data
{
    public class MyContext : DbContext
    {
        public DbSet<Company> Companies { get; set; }
        public DbSet<Department> Departments { get; set; }
    }
}
复制代码

指定数据库Provider和Connection String:

在EFCore里, 必须明确指定Data Provider和Connection String.

可以在Context里面override这个Onconfiguring方法:

有一个错误, 应该是Server=localhost;

(这里无需调用父类的方法, 因为父类的方法什么也没做).

UseSqlServer表示使用Sql Server作为Data Provider. 其参数就是Connection String.

在运行时EfCore第一次实例化MyContext的时候, 就会触发这个OnConfiguring方法. 此外, Efcore的迁移Api也可以获得该方法内的信息.

EF Core迁移:

简单的来说就是 Model变化 –> 创建migration文件 –> 应用Migration到数据库或生成执行脚本.

添加Migration (迁移):

由于我使用的是VSCode+dotnet cli的方法, 所以需要额外的步骤来使dotnet ef命令可用.

可以先试一下现在的效果:

可以看到, dotnet ef 命令还不可用.

所以参考官方文档: https://docs.microsoft.com/en-us/ef/core/miscellaneous/cli/dotnet

可执行项目(Startup project)需要EFCore迁移引擎库, 所以对LearnEf.UI添加这个库:

dotnet add ./LearnEf.UI package Microsoft.EntityFrameworkCore.Design
dotnet restore

 

然后打开LearnEf.UI.csproj 添加这段代码, 这个库是EF的命令库:

 <ItemGroup>
    <DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="2.0.0" />
  </ItemGroup>

 

最后内容如下:

然后再执行dotnet ef命令, 就应该可用了:

现在, 添加第一个迁移:

cd LearnEf.UI
dotnet ef migrations add Initial --project=../LearnEf.Data

 

–project参数是表示需要使用的项目是哪个.

命令执行后, 可以看到Data项目生成了Migrations目录和一套迁移文件和一个快照文件:

检查这个Migration.

前边带时间戳的那两个文件是迁移文件.

另一个是快照文件, EFCore Migrations用它来跟踪所有Models的当前状态. 这个文件非常重要, 因为下次你添加迁移的时候, EFcore将会读取这个快照并将它和Model的最新版本做比较, 就这样它就知道哪些地方需要有变化.

这个快照文件解决了老版本Entity Framework的一个顽固的团队问题.

使用迁移文件创建脚本或直接生成数据库.

生成创建数据库的SQL脚本:

dotnet ef migrations script --project=../LearnEf.Data/LearnEf.Data.csproj

 

Sql脚本直接打印在了Command Prompt里面. 也可以通过指定–output参数来输出到具体的文件.

这里, 常规的做法是, 针对开发时的数据库, 可以通过命令直接创建和更新数据库. 而针对生产环境, 最好是生成sql脚本, 然后由相关人员去执行这个脚本来完成数据库的创建或者更新.

直接创建数据库:

dotnet ef database update --project=../LearnEf.Data/LearnEf.Data.csproj --verbose

 

–verbose表示显示执行的详细过程, 其结果差不多这样:

这里的执行过程和逻辑是这样的: 如果数据库不存在, 那么efcore会在指定的连接字符串的地方建立该数据库, 并应用当前的迁移. 如果是生成的sql脚本的话, 那么这些动作必须由您自己来完成.

然后查看一下生成的表.

不过首先, 如果您也和我一样, 没有装Sql server management studio或者 Visual Studio的话, 请您先安装VSCode的mssql这个扩展:

重启后, 建立一个Sql文件夹, 然后建立一个Tables.sql文件, 打开命令面板(windows: Shift+Ctrl+P, mac: Cmd+Shift+P), 选择MS SQL: Connect.

然后选择Create Connection Profile:

输入Sql的服务器地址:

再输入数据库名字:

选择Sql Login(我使用的是Docker, 如果windows的话, 可能使用Integrated也可以):

输入用户名:

密码:

选择是否保存密码:

最后输入档案的名字:

随后VSCode将尝试连接该数据库, 成功后右下角会这样显示 (我这里输入有一个错误, 数据库名字应该是LearnEF):

随后在该文件中输入下面这个sql语句来查询所有的Table:

--  Table 列表
SELECT * FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_TYPE='BASE TABLE';

 

执行sql的快捷键是windows: Shift+Ctrp+E, mac: Cmd+Shift+E, 或者鼠标右键.

结果如图:

OK表是创建成功了(还有一个迁移历史表, 这个您应该知道).

接下来我看看表的定义:

-- Companies表:
exec sp_help 'Companies';

 

其中Name字段是可空的并且长度是-1也就是nvarchar(Max).

Departments表的Name字段也是一样的.

再看看那个MigrationHistory表:

-- MigrationHistory:
SELECT * FROM dbo.__EFMigrationsHistory;

可以看到, efcore到migration 历史表里面只保存了MigrationId.

在老版本到ef里, migration历史表里面还保存着当时到迁移的快照, 创建迁移的时候还需要与数据库打交道. 这就是我上面提到的如果团队使用ef和源码管理的话, 就会遇到这个非常令人头疼的问题.

如果使用asp.net core的话.

在解决方案里再建立一个asp.net core mvc项目:

mkdir LearnEf.Web && cd LearnEf.Web
dotnet new mvc

 

在解决方案里添加该项目:

dotnet sln add ./LearnEf.Web/LearnEf.Web.csproj

 

为该项目添加必要的引用:

cd LearnEf.Web
dotnet add reference ../LearnEf.Domains/LearnEf.Domains.csproj ../LearnEf.Data/LearnEf.Data.csproj

 

为测试项目添加该项目引用:

cd ../*Tests
dotnet add reference ../LearnEf.Web/LearnEf.Web.csproj

 

操作完之后, 我们可以做以下调整, 去掉MyContext里面的OnConfiguring方法, 因为asp.net core有内置的依赖注入机制, 我可以把已经构建好的DbContextOptions直接注入到构造函数里:

这样的话, 我们可以让asp.net core来决定到底使用哪个Data Provider和Connection String:

这也就意味着, Web项目需要引用EfCore和Sql Provider等, 但是不需要, 因为asp.net core 2.0这个项目模版引用了AspNetCore.All这个megapack, 里面都有这些东西了.

虽然这个包什么都有, 也就是说很大, 但是如果您使用Visual Studio Tooling去部署的话, 那么它只会部署那些项目真正用到的包, 并不是所有的包.

接下来, 在Web项目的Startup添加EfCore相关的配置:

复制代码
 public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();
            services.AddDbContext<MyContext>
                (options => options.UseSqlServer("Server=localhost; Database=LearnEf; User Id=sa; Password=Bx@steel1;"));
        }
复制代码

 

这句话就是把MyContext注册到了asp.net core的服务容器中, 可以供注入, 同时在这里指定了Data Provider和Connection String.

与其把Connection String写死在这里, 不如使用appSettings.json文件:

然后使用内置的方法读取该Connection String:

复制代码
 public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();
            services.AddDbContext<MyContext>
                (options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
        }
复制代码

 

回到命令行进入Web项目, 使用dotnet ef命令:

说明需要添加上面提到的库, 这里就不重复了.

然后, 手动添加一个Migration叫做InitialAspNetCore:

dotnet ef migrations add InitialAspNetCore --project=../LearnEf.Data

 

看一下迁移文件:

是空的, 因为我之前已经使用UI那个项目进行过迁移更新了. 所以我要把这个迁移删掉:

dotnet ef migrations remove --project=../LearnEf.Data

 

然后这两个迁移文件就删掉了:

多对多关系和一对一关系:

这部分的官方文档在这: https://docs.microsoft.com/en-us/ef/core/modeling/relationships

对于多对多关系, efcore需要使用一个中间表, 我想基本ef使用者都知道这个了, 我就直接贴代码吧.

建立一个City.cs:

复制代码
namespace LearnEf.Domains
{
    public class City
    {
        public int Id { get; set; }
        public string Name { get; set; }
    }
}
复制代码

 

Company和City是多对多的关系, 所以需要建立一个中间表,叫做 CompanyCity:

复制代码
namespace LearnEf.Domains
{
    public class CompanyCity
    {
        public int CompanyId { get; set; }
        public int CityId { get; set; }
        public Company Company { get; set; }
        public City City { get; set; }
    }
}
复制代码

 

修改Company:

修改City:

尽管Efcore可以推断出来这个多对多关系, 但是我还是使用一下FluentApi来自定义配置一下这个表的主键:

MyContext.cs:

复制代码
using LearnEf.Domains;
using Microsoft.EntityFrameworkCore;

namespace LearnEf.Data
{
    public class MyContext : DbContext
    {
        public MyContext(DbContextOptions<MyContext> options)
            : base(options)
        {

        }
        public DbSet<Company> Companies { get; set; }
        public DbSet<Department> Departments { get; set; }
        public DbSet<CompanyCity> CompanyCities { get; set; }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<CompanyCity>()
                .HasKey(c => new { c.CompanyId, c.CityId });
        }

        // protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        // {
        //     optionsBuilder.UseSqlServer("Server=localhost; Database=LearnEf; User Id=sa; Password=Bx@steel1;");
        //     base.OnConfiguring(optionsBuilder);
        // }
    }
}
复制代码

 

完整的写法应该是:

其中红框里面的部分不写也行.

接下来建立一个一对一关系, 创建Model叫Owner.cs:

复制代码
namespace LearnEf.Domains
{
    public class Owner
    {
        public int Id { get; set;}
        public int CompanyId { get; set; }
        public string Name { get; set; }
        public Company Company { get; set; }
    }
}
复制代码

 

修改Company:

配置关系:

复制代码
protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<CompanyCity>()
                .HasKey(c => new { c.CompanyId, c.CityId });

            modelBuilder.Entity<CompanyCity>().HasOne(x => x.Company)
                .WithMany(x => x.CompanyCities).HasForeignKey(x => x.CompanyId);

            modelBuilder.Entity<CompanyCity>().HasOne(x => x.City)
                .WithMany(x => x.CompanyCities).HasForeignKey(x => x.CityId);

            modelBuilder.Entity<Owner>().HasOne(x => x.Company).WithOne(x => x.Owner)
                .HasForeignKey<Owner>(x => x.CompanyId);
        }
复制代码

 

 

这里面呢, 这个Owner对于Company 来说 是可空的. 而对于Owner来说, Company是必须的. 如果针对Owner想让Company是可空的, 那么CompanyId的类型就应该设置成int?.

再添加一个迁移:

dotnet ef migrations add AddRelationships --project=../LearnEf.Data

 

查看迁移文件:

查看一下快照;

没问题, 那么更新数据库:

dotnet ef database update AddRelationships --project=../LearnEf.Data --verbose

 

更新成功:

对现有数据库的反向工程.

这部分请查看官方文档吧, 很简单, 我实验了几次, 但是目前还没有这个需求.

使用Model与数据库交互

输出Sql语句.

对于asp.net core 2.0项目, 参考官方文档: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/logging/?tabs=aspnetcore2x

实际上, 项目已经配置好Logging部分了, 默认是打印到控制台和Debug窗口的. 源码: https://github.com/aspnet/MetaPackages/blob/dev/src/Microsoft.AspNetCore/WebHost.cs

而对于console项目, 文档在这: https://docs.microsoft.com/en-us/ef/core/miscellaneous/logging

需要对LearnEf.Data项目添加这个包:

cd LearnEf.Data
dotnet add package Microsoft.Extensions.Logging.Console
dotnet restore

 

然后为了使用console项目, 需要把MyContext改回来:

这部分首先是使用LoggerFactory创建了一个特殊的Console Logger. .net core的logging可以显示很多的信息, 这里我放置了两个过滤: 第一个表示只显示Sql命令, 第二个表示细节的显示程度是Information级别.

最后还要在OnConfiguring方法里告诉modelBuilder使用MyLoggerFactory作为LoggerFactory.

这就配置好了.

插入数据.

这部分很简单, 打开UI项目的Program.cs:

这里都懂的, 创建好model之后, 添加到context的DbSet属性里, 这时context就开始追踪这个model了.

SaveChanges方法, 会检查所有被追踪的models, 读取他们的状态. 这里用到是Add方法, context就会知道这个model的状态是new, 所以就应该被插入到数据库. 然后它就根据配置会生成出相应的sql语句, 然后把这个SQL语句执行到数据库. 如果有返回数据的话, 就取得该数据.

下面就运行一下这个console程序:

dotnet run --project=./LearnEf.UI

 

看下控制台:

可以看到输出了sql语句, 而且这个出入动作后, 做了一个查询把插入数据生成的Id取了回来.

默认情况下log不显示传进去的参数, 这是为了安全. 但是可以通过修改配置来显示参数:

然后控制台就会显示这些参数了:

批量插入操作.

可以使用AddRange添加多条数据. 其参数可以是params或者集合.

可以看到这个和之前Add的Sql语句是完全不同的:

这个语句我不是很明白.

批量添加不同类型的数据:

使用context的AddRange或Add方法, DbContext可以推断出参数的类型, 并执行正确的操作. 上面的方法就是使用了DbContext.AddRange方法, 一次性添加了两种不同类型的model.

这两个方法对于写一些通用方法或者处理复杂的情况是很有用的.

Sql Server对于批量操作的限制是, 一次只能最多处理1000个SQL命令, 多出来的命令将会分批执行.

如果想更改这个限制, 可以这样配置参数:

简单查询.

针对DbSet, 使用Linq的ToList方法, 会触发对数据库对查询操作:

首先把Company的ToString方法写上:

这样方便输入到控制台.

然后写查询方法:

看结果:

EfCore到查询有两类语法, 一种是Linq方法, 另一种是Linq查询语法:

这种是Linq方法:

下面这种是Linq查询语法:

我基本都是使用第一种方法.

除了ToList(Async)可以触发查询以外, 遍历foreach也可以触发查询:

但是这种情况下, 可能会有性能问题. 因为:

在遍历开始的时候, 数据库连接打开, 并且会一直保持打开的状态, 直到遍历结束.

所以如果这个遍历很耗时, 那么可能会发生一些问题.

最好的办法还是首先执行ToList, 然后再遍历.

查询的过滤.

这部分和以前的EF基本没啥变化.

这个很简单, 不说了.

这里列一下可触发查询的Linq方法:

还有个两个方法是DbSet的方法, 也可以触发查询动作:

上面这些方法都应该很熟悉, 我就不写了.

过滤的条件可以直接家在上面的某些方法里面, 例如:

通过主键查询, 就可以用DbSet的Find方法:

这个方法有个优点, 就是如果这条数据已经在Context里面追踪了, 那么查询的时候就不查数据库了, 直接会返回内存中的数据.

EF.Functions.Like 这个方法是新方法, 就像是Sql语句里面的Like一样, 或者字符串的Contains方法:

这个感觉更像Sql语句, 输出到Console的Sql语句如下:

这里还要谈的是First/FirstOrDefault/Last/LastOrDefaut方法.

使用这些方法必须先使用OrderBy/OrderByDescending排序. 虽然不使用的话也不会报错, 但是, 整个过程就会变成这样, context把整个表的数据家在到内存里, 然后返回第一条/最后一条数据. 如果表的数据比较多的话, 那么就会有性能问题了.

更新数据.

很简单, context所追踪的model属性变化后, SaveChanges就会更新到数据库.

当然, 多个更新操作和插入等操作可以批量执行.

离线更新.

就是这种情况, 新的context一开始并没有追踪one这个数据. 通过使用Update方法, 追踪并设置状态为update. 然后更新到数据库.

可以看到, 在这种情况下, EfCore会更新该model到所有属性.

Update同样也有DbSet的UpdateRange方法, 也有context到Update和UpdateRange方法, 这点和Add是一样的.

还有一种方法用于更新, 这个以后再说.

删除数据.

DbContext只能删除它追踪的model.

非常简单, 从log可以看到, 删除动作只用到了主键:

如果是删除的离线model, 那么Remove方法首先会让Dbcontext追踪这个model, 然后设置状态为Deleted.

删除同样有RemoveRange方法.

Raw SQL查询/命令:

这部分请看文档:

命令: DbContext.Database.ExecuteSqlCommand();

查询: DbSet.FromSql() https://docs.microsoft.com/en-us/ef/core/querying/raw-sql;

这个方法目前还有一些限制, 它只能返回实体的类型, 并且得返回domain model所有的属性, 而且属性的名字必须也得一一对应. SQL语句不可以包含关联的导航属性, 但是可以配合Include使用以达到该效果(https://docs.microsoft.com/en-us/ef/core/querying/raw-sql#including-related-data).

更多的传递参数方式还需要看文档.

查询和保存关联数据.

插入关联数据.

我之前忘记在Department里面添加Name字段了, 现在添加一下, 具体过程就不写了.

插入关联数据有几种情况:

1.直接把要添加的Model的导航属性附上值就可以了, 这里的Department不需要写外键.

看一下Sql:

这个过程一共分两步: 1 插入主表, 2,使用刚插入主表数据的Id, 插入子表数据.

2.为数据库中的数据添加导航属性.

这时, 因为该数据是被context追踪的, 所以只需在它的导航属性添加新记录, 然后保存即可.

3.离线数据添加导航属性.

这时候就必须使用外键了.

预加载关联数据 Eager Loading.

也就是查询的时候一次性把数据和其导航属性的数据一同查询出来.

看看SQL:

这个过程是分两步实现的, 首先查询了主表, 然后再查询的子表. 这样做的好处就是性能提升.

(FromSql也可以Include).

预加载子表的子表:

可以使用ThenInclude方法, 这个可以老版本ef没有的.

这里查询Department的时候, 将其关联表Company也查询了出来, 同时也把Company的关联表Owner也查询了出来.

查询中映射关联数据.

使用Select可以返回匿名类, 里面可以自定义属性.

这个匿名类只在方法内有效.

看下SQL:

可以看到SQL中只Select了匿名类里面需要的字段.

如果需要在方法外使用该结果, 那么可以使用dynamic, 或者建立一个对应的struct或者class.

使用关联导航属性过滤, 但是不加载它们.

SQL:

这个比较简单. 看sql一切就明白了.

修改关联数据.

也会分两种情况, 被追踪和离线数据.

被追踪的情况下比较简单, 直接修改关联数据的属性即可:

看一下SQL:

确实改了.

这种情况下, 删除关联数据库也很简单:

看下SQL:

删除了.

下面来看看离线状态下的操作.

这里需要使用update, 把该数据添加到context的追踪范围内.

看一下SQL:

这个就比较怪异了.

它update了该departmt和它的company以及company下的其他department和company的owner. 这些值倒是原来的值.

这是因为, 看上面的代码, 查询的时候department的关联属性company以及company下的departments和owner一同被加载了.

尽管我只update了一个department, 但是efcore把其他关联的数据都识别出来了.

从DbContext的ChangeTracker属性下的StateManger可以看到有多少个变化.

这一点非常的重要.

如何避免这个陷阱呢?

可以这样做: 直接设置dbContext.Entry().State的值

这时, 再看看SQL:

嗯. 没错, 只更新了需要更新的对象.

 

 

2.1版本将于2018年上半年发布, 请查看官网的路线图: https://github.com/aspnet/EntityFrameworkCore/wiki/roadmap

完.

草根专栏, 草根的.net core专栏
分类: .Net Core

由于系统缓冲区空间不足或队列已满,不能执行套接字上的操作

utorrent 出现 “由于系统缓冲区空间不足或队列已满,不能执行套接字上的操作”tracker红肿如何解决:

答:
可能是你留作种的原因,所以tcp的端口(UserPort)请求已经达到你pc上本地设置的界限(MaxUserPort),可以试着修改此键值;
方法如下(修改前请备份好你的注册表文件,以免发生意外):
.启动注册表编辑器。
..在注册表中,找到以下子项,然后单击$参数(Parameters 翻译过来就是>>参数<<的意思)
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters
…在编辑菜单中,单击新建,然后添加以下注册表项:
值名称: MaxUserPort
值类型: 双字节
值数据: 65534
有效范围: 5000-65534 (十进制)
默认值: 0x1388 (5000 十进制)
….退出注册表编辑器,然后重新启动计算机
参考:

TcpTimedWaitDelay和MaxUserPort设置与网络吞吐量

TcpTimedWaitDelay

    • 描述:确定 TCP/IP 在释放已关闭的连接并再次使用其资源前必须经过的时间。关闭与释放之间的这段时间称为 TIME_WAIT 状态或者两倍最大段生存期(2MSL)状态。此时间期间,重新打开到客户机和服务器的连接的成本少于建立新连接。通过减少此条目的值,TCP/IP 可以更快地释放关闭的连接,并为新连接提供更多资源。如果运行中的应用程序要求快速释放连接或创建新连接,或者由于多个连接处于 TIME_WAIT 状态而导致吞吐量较低,请调整此参数。
    • 如何查看或设置:

1.     使用regedit命令,访问 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\TCPIP\Parameters 注册表子键,然后创建新的 REG_DWORD 值TcpTimedWaitDelay。

2.     将此值设置为十进制30,即十六进制 0x0000001e。此值将等待时间设置为 30 秒。

3.     停止并重新启动系统。

    • 缺省值:0xF0,此值将等待时间设置为 240 秒(4 分钟)。
    • 建议值:最小值为0x1E,此值将等待时间设置为 30 秒。
  • MaxUserPort
    • 描述:确定当应用程序向系统请求获取可用的用户端口时,TCP/IP 可指定的最高端口号。
    • 如何查看或设置:

1.     使用regedit命令,访问 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\TCPIP\Parameters 注册表子键,然后创建新的 REG_DWORD 值MaxUserPort。

2.     将此值至少设置为十进制32768。

3.     停止并重新启动系统。

    • 缺省值:
    • 建议值:至少为十进制32768。
  • 最大连接储备
    • 描述:如果同时接收到许多连接尝试,请增大操作系统支持的缺省暂挂连接数。
    • 如何查看或设置:

1.     使用regedit命令并访问 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\AFD\Parameters 注册表子键。

2.     根据需要创建并设置下列值:

3. “EnableDynamicBacklog”=dword:00000001

4. “MinimumDynamicBacklog”=dword:00000020

5. “MaximumDynamicBacklog”=dword:00001000

6. “DynamicBacklogGrowthDelta”=dword:00000010

7.     这些值将最小可用连接数设置为 20,将最大可用连接数设置为 1000。每当可用连接数小于最小可用连接数时,可用连接数都会增加 10。

8. 停止并重新启动系统。

  • KeepAliveInterval
    • 描述:确定 TCP 在未接收到响应时重新尝试保持活动传输的频率。
    • 如何查看或设置:

1.     使用regedit命令,访问 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\AFD\Parameters 注册表子键,然后创建新的 REG_DWORD 值KeepAliveInterval。

2.     将此值设置为1秒。

3.     停止并重新启动系统。

  • 缺省值:1秒
  • 建议值:
参考:

windows 分页缓冲池 非分页缓冲池

最近在windows server 2012机器上在做性能测试时,发现8G物理内存,内存使用率占到了90%多,在“进程”列表中所有进程内存相加才2个多G,同时任务管理器->“性能”标签一项中,非内存缓冲池很高占到了5个多G。

在网上找了一些资料,说是windows8系列有内存泄露的BUG,会引起“非内存缓冲池”一直占用很高。当前系统初步怀疑可能也是这个问题。

参考文档:

一次DB服务器性能低下引发的对Nonpaged Pool Leak问题的诊断

 http://tieba.baidu.com/p/2728129582

 

先使用poolmon.exe来分析哪个组件占用内存高,再对这个组件做相应的处理。

 

对于分页缓冲池与非页面缓冲池

PagedPool 和 NoPagedPool的区别Windows kernel pool

1、页面一直锁定在物理内存中,不会被换出到页面交换文件中

2、Windows把虚拟地址分为用户地址空间和系统地址空间,用户地址空间是给应用程序使用的,系统地址空间是给系统核心和驱动程序使用的。系统地址空间分为分页池和非分页池,分页池是指映射到分页文件的虚拟地址,当要使用该地址时才交换到物理内存中,由系统来调度;非分页池是指直接在物理内存中分配的内存。“页面缓冲池”就是进程占用的分页池中的虚拟内存,是进程调用某些系统功能时,由系统核心或者驱动程序分配的。如果一个程序占用的页面缓冲池内存不断增大,就是内存泄露,通常应该是创建或打开了句柄没有关闭。

 

在perfmon计数器里统计这两个参数时,momory对象与process对象里都存在相关的值(两种pool都会被映射到每一个进程空间内)。

Perfmon – Windows 自带系统监测工具

解决windows系统因TCP端口不足导致mysql数据库无法访问的问题

在windows服务器上面批量处理数据的时候,遇到下面的异常,意思是说连接数用完了,无法再建立连接。

com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: The driver was unable to create a connection due to an inability to establish the client portion of a socket.

This is usually caused by a limit on the number of sockets imposed by the operating system. This limit is usually configurable. 

For Unix-based platforms, see the manual page for the 'ulimit' command. Kernel or system reconfiguration may also be required.

For Windows-based platforms, see Microsoft Knowledge Base Article 196271 (Q196271).
    at sun.reflect.GeneratedConstructorAccessor16.newInstance(Unknown Source)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:27)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

默认情况下,Windows 允许用于使用5000 个临时(短命)TCP 端口。任何端口关闭后,它将在TIME_WAIT 状态保持120 秒。与重新初始化全新的连接相比,该状态允许以更低的开销重新使用连接。 但是,在该时间逝去前,无法再次使用该端口。
对于小的可用TCP 端口堆栈(5000 ),以及具有TIME_WAIT 状态的大量在短时间内打开和关闭的 TCP 端口,你很可能遇到端口耗尽问题。
我们可以通过修改注册表配置来解决问题:
1,启动注册表编辑器(Regedt32.exe )。

2,在注册表中确定下述键值的位置:
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters
在“编辑”菜单上点击“添加值”,然后增加下述注册值:
Value Name: MaxUserPort
Data Type: REG_DWORD
Value: 65534
它用于设置为任何用户提供的临时端口数。有效范围介于5000 和65534 之间(十进制)。默认值为0x1388 (5000 ,十进制)。

3,在“编辑”菜单上点击“添加值”,然后增加下述注册值:
Value Name: TcpTimedWaitDelay
Data Type: REG_DWORD
Value: 30
它用于设置关闭之前将TCP 端口连接保持在TIME_WAIT 状态的秒数。 有效范围介于0 秒和300 秒之间。默认值为0x78 (120 秒)。

4,退出注册表编辑器。

5,重启服务器。