2018年4月15日 星期日

在 ASP.NET Core Web API 內,使用 SQLite 資料庫開發教學

在這篇文章中,我們將要來實際建立一個 ASP.NET Core 的 Web API 專案,並且在這個專案內,我們將會要使用 SQLite 資料庫 database之教學練習。在這個練習中,我使用的 Visual Studio 2017 為 15.6 的版本。

本篇文章的專案範例原始碼,可以從 https://github.com/vulcanlee/CSharpNotes2018/tree/master/XamarinCore 取得

建立一個 ASP.NET Core Web API 專案

  • 打開任何一種版本的 Visual Studio 2017
  • 從功能表中點選 [檔案] > [新增] > [專案]
  • 從 [新增專案] 對話窗中,點選 [已安裝] > [Visual C#] > [.NET Core] > [ASP.NET Core Web 應用程式]
  • Visual Studio 2017 新增專案
  • 在該對話窗下方的 [名稱] 欄位,輸入這個專案的名稱,最後點選 [確定] 按鈕
  • 此時,您將會看到 [新增 ASP.NET Core Web 應用程式對話窗],請確認 [.NET Core] 選擇的版本是現在最新的 [ASP.NET Core 2.0]
  • 專案類型,請選擇 [API]
  • 在最左方的 [用於建立 ASP.NET Core 應用程式的專案範本,附有 RESTful HTTP 服務的控制器範例,此範本也可用於 ASP.NET Core MVC 的檢視及控制器] 文字下方,請確認選擇 [變更驗證] 的下方為 [無驗證]
  • 完成之後,請點選 [確定] 按鈕
    新增 ASP.NET Core Web 應用程式對話窗
  • 稍微等候一段時間,這個ASP.NET Core Web 應用程式專案就會建立完成,您可以從 Visual Studo 2017 的方案總管中,看到建立好的專案內容,如下圖所示:
    ASP.NET Core Web 應用程式 專案內容

安裝 NuGet 套件

  • 使用滑鼠右擊 [相依性] 節點,選擇 [管理 NuGet 套件]
  • 在 [管理 NuGet 套件] 視窗中,點選 [瀏覽] 標籤頁次,接著在搜尋文字輸入盒中,輸入 [Microsoft.EntityFrameworkCore]
  • 當搜尋到 [Microsoft.EntityFrameworkCore] 之後,請安裝這個套件
    管理 NuGet 套件
  • 在 [管理 NuGet 套件] 視窗中,點選 [瀏覽] 標籤頁次,接著在搜尋文字輸入盒中,輸入 [Microsoft.EntityFrameworkCore.SQlite]
  • 當搜尋到 [Microsoft.EntityFrameworkCore.SQlite] 之後,請安裝這個套件
    管理 NuGet 套件

建立 Context 類別

  • 滑鼠右擊專案節點,選擇 [加入] > [新增資料夾],接著輸入 [Models]
  • 滑鼠右擊 [Models] 資料夾,選擇 [加入] > [類別]
  • 在 [新增項目] 對話窗中名稱欄位,輸入 [DatabaseContext]
    新增項目對話窗
  • 使用底下程式碼替換剛剛建立好的檔案內容,在這裡,我們剛剛建立的 DatabaseContext 類別,將會繼承 DbContext 類別,並且我們需要覆寫 OnConfiguring 方法,並且在這個方法類,指定使用 SQLite 資料庫的名稱,也就是使用這行敘述 optionsBuilder.UseSqlite("Filename=MyDatabase.db")
C Sharp / C#
using Microsoft.EntityFrameworkCore;

namespace XamarinCore.Models
{
    public class DatabaseContext : DbContext
    {
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.UseSqlite("Filename=MyDatabase.db");
        }
    }
}

修正 Startup 類別

  • 請在 Visual Studio 2017 方案總管中,打開 [Startup.cs] 檔案
  • 找到 [ConfigureServices] 方法,依照底下程式碼進行修正
C Sharp / C#
public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc();
    services.AddEntityFrameworkSqlite().AddDbContext<DatabaseContext>();
}
  • 接下來,我們需要建立 Startup 的建構式,依照底下程式碼進行修正。在這裡,我們將會使用建構式注入 (Constructor Dependency Injection) 方法,注入 IHostingEnvironment 這個介面實作物件,並且在這裡設定要產生 SQLite 的資料庫檔案。
C Sharp / C#
public Startup(IHostingEnvironment env)
{
    using(var client = new DatabaseContext())
    {
        client.Database.EnsureCreated();
    }
}

建立資料模型與修正 DatabaseContext 類別

  • 滑鼠右擊 [Models] 資料夾,選擇 [加入] > [類別]
  • 在 [新增項目] 對話窗中名稱欄位,輸入 [Todo]
    新增項目對話窗
  • 使用底下程式碼來宣告 Todo 這個類別
C Sharp / C#
public class Todo
{
    public int Id { get; set; }

    public string Title { get; set; }
    public bool HasCompleted { get; set; }
}
  • 打開 [DatabaseContext.cs] 檔案,在 DatabaseContext 類別中,參考底下程式碼,加入 Todos 這個屬性的宣告
C Sharp / C#
using Microsoft.EntityFrameworkCore;

namespace XamarinCore.Models
{
    public class DatabaseContext : DbContext
    {
        public virtual DbSet<Todo> Todos { get; set; }
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.UseSqlite("Filename=MyDatabase.db");
        }
    }
}

使用移轉來建立資料庫

  • 滑鼠右擊專案節點,選擇 [編輯 XamarinCore.csproj]
  • 找到 <DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="2.0.2" />宣告,在這行後面,加入 <DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="2.0.1" />,完成後的結果如下所示,最後,請按下儲存功能。
XML
<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>netcoreapp2.0</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <Folder Include="wwwroot\" />
  </ItemGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.5" />
    <PackageReference Include="Microsoft.EntityFrameworkCore" Version="2.0.1" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.0.1" />
  </ItemGroup>

  <ItemGroup>
    <DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="2.0.2" />
    <DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="2.0.1" />
  </ItemGroup>

</Project>
  • 請 [建置] 這個專案
  • 滑鼠右擊專案節點,選擇 [在檔案總管中開啟資料夾]
    新增項目對話窗
  • 當檔案總管開啟之後,請在位置列中輸入 [cmd],通知作業系統開啟命令提示字元視窗
  • 請在命令提示字元視窗中,輸入 dotnet ef migrations add InitialCreate,並且按下 [Enter]。當您看到這段文字 Done. To undo this action, use 'ef migrations remove' 出現,那就表示這段命令已經成功執行。
Console
D:\Vulcan\Projects\XamarinCore\XamarinCore>dotnet ef migrations add InitialCreate
Done. To undo this action, use 'ef migrations remove'
  • 請在命令提示字元視窗中,輸入 dotnet ef database update,並且按下 [Enter]。當您看到這段文字 Applying migration '20180308090145_InitialCreate'. Done. 出現,那就表示這段命令已經成功執行。
  • 底下畫面截圖,就是剛剛在命令提示字元視窗中,所下達的兩個命令。
    使用移轉來建立資料庫
  • 現在,我們可以查看 Visual Studio 2017 的方案總管,您將會看到在這個專案節點內,多了一個 [Migrations] 資料夾,並且在該資料夾內也有兩個檔案存在;另外,我們也看到了 [MyDatabase.db] 這個 SQLite Database 資料庫檔案也產生出來了。
    SQLite資料庫已經建立完成

結論

好的,現在我們可以透過 Entity Framework Core 所提供的各項功能與機制,開始進行 SQLite 資料庫的資料表存取動作了。

其他相關文章

更多關於 Xamarin / Xamarin.Forms 教學、技術分享、用法文章,請參考 I ♥ Xamarin

2018年2月28日 星期三

如何 Microsoft SQL Server Management Studio 17 使用匯出資料庫的綱要 Schema

今天是 228 ,放假一天,所以,來研究一下,如何把資料庫的綱要 Schema 匯出,修改一些內容,最後想要產生一個新的資料庫。
理所當然,我就打開 Visual Studio 2017,並且切換到 SQL Server 物件總管,不過,當我使用滑鼠右鍵點選項要匯出的資料庫時候,從彈出功能表中,卻沒有看到任何可以匯出資料庫綱要的選項。
SQL Server 物件總管
這個時候,只好參考這份文件 下載 SQL Server Managemen這個時候,只好參考這份文件 ,下載 下載 SQL Server Management Studio 17.5 並且安裝到我的電腦上;當安裝完成之後,我這裡需要重新開機,因此,在電腦重新開機之後,我就啟動了 Microsoft SQL Server Management Studio
現在,可以從電腦中找到 Microsoft SQL Server Management Studio ,執行這個應用程式,我們可以在 [Connect to Server] 對話窗中,在 [Server name] 的欄位中,輸入 (localdb)\MSSQLLocalDB 這個字串,這樣,我們就可以連線到本機的 LocalDB。
Microsoft SQL Server Management Studio
在最左方的 [Object Explorer] 視窗中,請展開 [Databases] 節點,您就可以看到這台機器上的 Local DB 現在有哪些資料庫存在,使用滑鼠右擊您想要產生資料庫綱要的資料庫。
接下來,我們可以從彈出功能表,依序選擇 [Tasks] > [Generate Scripts] 選項
Microsoft SQL Server Management Studio
此時,您會看到 [Generate and Publish Scripts] 對話窗出現,請在第一個對話窗畫面 [Introduction] 上,點選 [Next] 按鈕。
Generate and Publish Scripts
在 [Choose Objects] 畫面中,我們一樣使用預設值,點選 [Next] 按鈕。
Generate and Publish Scripts
最後,在 [Set Scripting Options] 畫面中,可以依照您的需要,將這個資料庫 Schema 匯出到您期望的目的地與儲存格式,點選 [Next] 按鈕。
Generate and Publish Scripts
這樣,您就成功取得了這個資料庫綱要定義 SQL 指令了。

2017年11月11日 星期六

.NET Core 與 .NET Framework 在 GC 釋放物件的不同處理方式

在這裡,我們將會比較 .NET Core 與 .NET Framework 中的 CLR 對於 GC 的處理運作差異點。我們會針對這兩個 .NET 平台的 Debug / Release 模式下,跑同樣的測試程式,看看會有甚麼樣的結果。

可以追蹤該別現在多少物件正在使用中

我們首先建立這個 CountObject 類別,這個類別有個靜態屬性 TotalObjects,這個屬性會當該類別的建構函式被呼叫的時候(也就是,用戶端有使用 new 運算子要建立一個新的物件),就會把這個靜態屬性 TotalObjects加一,表示,現在該類別在 CLR 中,有一個物件生成了。
而當 CLR (通用語言執行階段) 進行 GC (記憶體回收程序) 過程中,若發現到有物件沒有被任何物件或者變數參考使用到,此時,GC 就會進行該物件的記憶體回收工作;而由於我們在這個類別中,有加入解構函式 ( ~CountObject() )的宣告,因此,當有物件要被回收的時候,該類別的解構函式就會被呼叫到,而在解構函式內,每被呼叫一次,就會自動將靜態屬性 TotalObjects減一,表示該這個類別在 CLR 中的物件又減少了一個。
public class CountObject
{
    /// <summary>
    /// 記錄下現在記憶體中,總共存在這多少個物件
    /// </summary>
    private static int _TotalObjects;

    public static int TotalObjects
    {
        get { return _TotalObjects; }
        set { _TotalObjects = value; }
    }

    string ObjectName;

    public CountObject()
    {
        // 若有該類別的物件產生,則計數器會加一
        CountObject.TotalObjects += 1;
        Console.WriteLine($"現在總共有 {CountObject.TotalObjects} 物件");
    }

    public CountObject(string objectName) : this()
    {
        Console.WriteLine($"有新的物件要產生 {objectName}");
        ObjectName = objectName;
    }

    ~CountObject()
    {
        Console.WriteLine($"有物件要回收 {ObjectName}");
        // 若有物件被回收,則該計數器會減一
        CountObject.TotalObjects -= 1;
    }
}

測試步驟

接下來的測試動作,將會分成

建立六個物件,並且執行 GC,看看剩下多少物件

我們首先呼叫 new 運算子,建立四個 CountObject 類別物件,不過,這四個物件,是沒有指定給任何物件變數(也就是,只要 GC 一執行,這四個物件一定會被回收的)。
接下來,建立兩個 CountObject 類別物件,但是,這裡將會指定到兩個變數中,也就是,這兩個物件將會被參考使用中。
接著,我們將會強制執行 GC 運行,並且等候一段時間,讓沒有被參考到的物件,可以順利被回收(想要了解這方面的進階資訊,請參考 GC 的運作模式相關文件)。
最後,我們將會顯示出靜態屬性 CountObject.TotalObjects 的值,看看現在還有多少物件存在於記憶體中。
Console.WriteLine("現在要產生六個物件");
new CountObject("物件1");
new CountObject("物件2");
new CountObject("物件3");
new CountObject("物件4");
var fooObject = new CountObject("物件5");
var fooTempObject = new CountObject("物件6");
Console.WriteLine($"Total Objects is {CountObject.TotalObjects}");
Console.WriteLine("Press any key for continuing...");
Console.ReadKey();

// 進行記憶體回收工作
Console.WriteLine("進行記憶體回收工作");
GC.Collect(2, GCCollectionMode.Forced);

// 將要休息三秒鐘,讓背景記憶體回收程序,可以有足夠的時間,進行記憶體回收
Console.WriteLine("將要休息三秒鐘,讓背景記憶體回收程序,可以有足夠的時間,進行記憶體回收");
Thread.Sleep(3000);
Console.WriteLine($"Total Objects is {CountObject.TotalObjects}");
Console.WriteLine("Press any key for continuing...");
Console.ReadKey();

將其中一個物件變數設定為 null,並且執行 GC,看看剩下多少物件

在這個測試程序中,將會把 fooTempObject 物件變數設定為 null,也就是原先該變數指向的物件,現在就沒有任何人正在參考與使用中,另外一個物件,是有 fooObject 變數指向它。
此時,我們將會強制執行 GC 運行,並且等候一段時間,讓沒有被參考到的物件,可以順利被回收(想要了解這方面的進階資訊,請參考 GC 的運作模式相關文件)。
最後,我們將會顯示出靜態屬性 CountObject.TotalObjects 的值,看看現在還有多少物件存在於記憶體中。
Console.WriteLine("將其中一個物件變數 fooTempObject,設為空值 null,因此,該變數 fooTempObject 所指向的物件,將會被記憶體回收");
fooTempObject = null;
// 進行記憶體回收工作
Console.WriteLine("進行記憶體回收工作");
GC.Collect(2, GCCollectionMode.Forced);

// 將要休息三秒鐘,讓背景記憶體回收程序,可以有足夠的時間,進行記憶體回收
Console.WriteLine("將要休息三秒鐘,讓背景記憶體回收程序,可以有足夠的時間,進行記憶體回收");
Thread.Sleep(3000);
Console.WriteLine($"Total Objects is {CountObject.TotalObjects}");
Console.WriteLine("Press any key for continuing...");
Console.ReadKey();

在 .NET Core 平台下進行測試 (Debug 模式)

建立六個物件,並且執行 GC,看看剩下多少物件

輸出結果
現在要產生六個物件
現在總共有 1 物件
有新的物件要產生 物件1
現在總共有 2 物件
有新的物件要產生 物件2
現在總共有 3 物件
有新的物件要產生 物件3
現在總共有 4 物件
有新的物件要產生 物件4
現在總共有 5 物件
有新的物件要產生 物件5
現在總共有 6 物件
有新的物件要產生 物件6
Total Objects is 6
Press any key for continuing...
進行記憶體回收工作
將要休息三秒鐘,讓背景記憶體回收程序,可以有足夠的時間,進行記憶體回收
Total Objects is 6
Press any key for continuing...

將其中一個物件變數設定為 null,並且執行 GC,看看剩下多少物件

輸出結果
將其中一個物件變數 fooTempObject,設為空值 null,因此,該變數 fooTempObject 所指向的物件,將會被記憶體回收
進行記憶體回收工作
將要休息三秒鐘,讓背景記憶體回收程序,可以有足夠的時間,進行記憶體回收
Total Objects is 6
Press any key for continuing...

在 .NET Core 平台下進行測試 (Release 模式)

建立六個物件,並且執行 GC,看看剩下多少物件

輸出結果
現在要產生六個物件
現在總共有 1 物件
有新的物件要產生 物件1
現在總共有 2 物件
有新的物件要產生 物件2
現在總共有 3 物件
有新的物件要產生 物件3
現在總共有 4 物件
有新的物件要產生 物件4
現在總共有 5 物件
有新的物件要產生 物件5
現在總共有 6 物件
有新的物件要產生 物件6
Total Objects is 6
Press any key for continuing...
進行記憶體回收工作
將要休息三秒鐘,讓背景記憶體回收程序,可以有足夠的時間,進行記憶體回收
有物件要回收 物件6
有物件要回收 物件5
有物件要回收 物件4
有物件要回收 物件3
有物件要回收 物件2
有物件要回收 物件1
Total Objects is 0
Press any key for continuing...

將其中一個物件變數設定為 null,並且執行 GC,看看剩下多少物件

輸出結果
將其中一個物件變數 fooTempObject,設為空值 null,因此,該變數 fooTempObject 所指向的物件,將會被記憶體回收
進行記憶體回收工作
將要休息三秒鐘,讓背景記憶體回收程序,可以有足夠的時間,進行記憶體回收
Total Objects is 0
Press any key for continuing...

在 .NET Framework 平台下進行測試 (Debug 模式)

建立六個物件,並且執行 GC,看看剩下多少物件

輸出結果
現在要產生六個物件
現在總共有 1 物件
有新的物件要產生 物件1
現在總共有 2 物件
有新的物件要產生 物件2
現在總共有 3 物件
有新的物件要產生 物件3
現在總共有 4 物件
有新的物件要產生 物件4
現在總共有 5 物件
有新的物件要產生 物件5
現在總共有 6 物件
有新的物件要產生 物件6
Total Objects is 6
Press any key for continuing...
進行記憶體回收工作
將要休息三秒鐘,讓背景記憶體回收程序,可以有足夠的時間,進行記憶體回收
有物件要回收 物件4
有物件要回收 物件3
有物件要回收 物件2
有物件要回收 物件1
Total Objects is 2
Press any key for continuing...

將其中一個物件變數設定為 null,並且執行 GC,看看剩下多少物件

輸出結果
將其中一個物件變數 fooTempObject,設為空值 null,因此,該變數 fooTempObject 所指向的物件,將會被記憶體回收
進行記憶體回收工作
將要休息三秒鐘,讓背景記憶體回收程序,可以有足夠的時間,進行記憶體回收
有物件要回收 物件6
Total Objects is 1
Press any key for continuing...

在 .NET Framework 平台下進行測試 (Release 模式)

建立六個物件,並且執行 GC,看看剩下多少物件

輸出結果
現在要產生六個物件
現在總共有 1 物件
有新的物件要產生 物件1
現在總共有 2 物件
有新的物件要產生 物件2
現在總共有 3 物件
有新的物件要產生 物件3
現在總共有 4 物件
有新的物件要產生 物件4
現在總共有 5 物件
有新的物件要產生 物件5
現在總共有 6 物件
有新的物件要產生 物件6
Total Objects is 6
Press any key for continuing...
進行記憶體回收工作
將要休息三秒鐘,讓背景記憶體回收程序,可以有足夠的時間,進行記憶體回收
有物件要回收 物件6
有物件要回收 物件5
有物件要回收 物件4
有物件要回收 物件3
有物件要回收 物件2
有物件要回收 物件1
Total Objects is 0
Press any key for continuing...

將其中一個物件變數設定為 null,並且執行 GC,看看剩下多少物件

輸出結果
將其中一個物件變數 fooTempObject,設為空值 null,因此,該變數 fooTempObject 所指向的物件,將會被記憶體回收
進行記憶體回收工作
將要休息三秒鐘,讓背景記憶體回收程序,可以有足夠的時間,進行記憶體回收
Total Objects is 0
Press any key for continuing...

差異比較

在 Debug 模式下,在第一階段的時候
  • .NET Core
    並不會回收任何沒有被參考到的物件
  • .NET Framework
    會將沒有被參考到到的物件,自動回收
在 Relase 模式下,這兩個 .NET Core 與 .NET Framework 平台的運作方式都一樣。

測試總管於 VS2017 15.4.3 版本下, .NET Framework 之單元測試運作不正常

今日原本要繼續進行未完成的單元測試專案,發現到我的測試總管無法正常運作,也就是無法顯示出已經建立好的單元測試清單項目。
我這裡的 Visual Studio 2017 本版是 15.4.3
在底下,我將分別針對 .NET Core 與 .NET Framework 的單元測試專案,做個比較

.NET Core 的單元測試專案

首先,選擇 [檔案] > [新增] > [專案] > [.NET Core] > [單元測試專案 (.NET Core)],建立起該 .NET Core 用的單元測試專案
建立.NET Core單元測試
打開 [測試總管] 視窗
滑鼠右擊該專案節點,選擇 [建置]
在建置完成之後,可以從測試總管視窗中,看到所有的單元測試項目之測試結果
建立.NET Core單元測試

.NET Framework 的單元測試專案

首先,選擇 [檔案] > [新增] > [專案] > [.NET Core] > [單元測試專案 (.NET Framework)],建立起該 .NET Framework 用的單元測試專案
建立.NETFramework單元測試專案
打開 [測試總管] 視窗
滑鼠右擊該專案節點,選擇 [建置]
在建置完成之後,卻無法從測試總管視窗中,看到所有的單元測試項目之測試結果
測試總管.NETFramework

使用別的 Visual Studio 2017 版本進行測試

在這裡,我開啟另外一台電腦上的 Visual Studio 2017 ,這台電腦上的 VS2017 版本是 15.3.3
單元測試地當時VS2017版本
我將上述關於 .NET Framwork 單元測試專案的建立、建置過程,重新全部都做一次,發現到如下圖,這就是我之前操作測試總管所可以看到的正常結果。在這裡,我都是把 [Live Unit Testing] 這個選項都是關閉的。
在這個版本中的即時單元測試功能表中,有個 [重新清理] 選項,這個選項在我的 VS2017 15.4.3 版本中,是看不到的。
正常之單元測試畫面
由於我可以在測試總管中看到這些單元測試項目,因此,我可以針對不同單元測試項目,進行除錯檢測。
因為在 VS2017 15.4.3 版本看不到這些單元測試項目,因此,我也就無法進行單元測試的除錯了。
正常的單元測試除錯

最後找的解決方案,那就是將這個目錄刪除掉,就正常了

C:\Users\%username%\AppData\Local\Temp\VisualStudioTestExplorerExtensions