2020年10月2日 星期五

Memory Overflow 造成的原因

Memory Overflow 造成的原因

在前面的文章 Stack Overflow 造成的原因 ,討論到造成 Stack Overflow 的現象,在這裡,將會來研究另外一種 記憶體 Overflow 問題,通常當這個問題發生的時候,會看到 [記憶體不足] 這樣的訊息出現,而且,此時,這個處理程序就異常終止,並且會看到如下的畫面。

會發生這樣的現象,那是因為這個處理程序耗用了過多的 Heap 堆積內的記憶體空間,導致當其他程式碼需要產生執行個體的時候,因為沒有足夠的記憶體,而造成整個處理成緒崩潰。

現在,來了解發生這樣問題的真正原因。當在 .NET 系統底下,需要建立一個 參考 Reference 型別的物件,通常會使用 new 這個運算子,後面緊接著某個型別的建構式,此時, .NET CLR 將會從 Heap 堆積記憶體空間,配置一塊記憶體來作為儲存該執行個體內容之用;而當這個物件不被其他變數參考到的時候,便會被 GC 垃圾回收機制回收,接著是放掉這個物件所參考到的記憶體空間。

若系統不斷的要求超過位址空間所允許的記憶體,便會發生了記憶體不足的問題,除了不斷的要求建立一個新的物件,但是持續有變數參考到這個物件,便會造成記憶體不足的問題,這還會發生到 GC Generation 2 的時候,因為架構上的設計,導致記憶體空間破碎,雖然剩餘的記憶體空間還足夠,但無法真正的找到一塊連緒且符合要求的記憶體空間,這樣也會造成記憶體不足的問題。

在上面的說明中,可以看到對於 .NET 系統中,將會有兩種類型的記憶體儲存空間,一個是 Heap,這裡將會存放各種參考型別物件值,而且這裡可用的空間非常大,另外一種是 Stack,這裡的空間會使用當前執行緒所配置的空間來使用,而在 .NET 中,一個執行緒在建立的時候,預設將會擁有 1MB 的記憶體空間,任何在方法呼叫過程中,對於參數與區域變數,將會使用這裡的空間。

因此,底下的範例,將會設計兩個類別 [MyLargeClass] 與 [MySmallClass] ,這兩個類別都是參考型別,因此,當建立執行個體的時候,會從 Heap 中要求一塊記憶體空間來用;前者類別當產生物件的時候,將會佔據至少 86KB 的記憶體空間,因為該類別類宣告了一個長度為 86KB 的 byte 陣列,後者類別將會從 Heap 中要求至少 20KB 的記憶體空間。

所以,在 Main 方法內,將會建立一個超大迴圈,不斷地建立這兩個型別的物件,並且把取得的記憶體空間,儲存到 List 或者 List 集合型別內,也因為這樣,這些產生的物件並不會因為 GC 記憶體回收機制的運作,而把這些記憶體歸還給系統,經過不斷的執行,終於造成了記憶體不足的問題,也就會出現體底下的異常畫面。

在下圖,將可以看到這個測試程式碼執行時候,耗用記憶體空間的過程。

Memory Overflow

public class MyLargeClass
{
    // 這將會讓產生的執行個體會占用 > 85000 bytes 的記憶體大小
    public byte[] bytes = new byte[1024 * 86];
}
 
public class MySmallClass
{
    // 這將會讓產生的執行個體會占用 < 85000 bytes 的記憶體大小
    public byte[] bytes = new byte[1024 * 20];
}
class Program
{
    static void Main(string[] args)
    {
 
        Console.WriteLine("Press any key for continuing...");
        Console.ReadKey();
        //這裡將會模擬產生大量的大物件執行個體,耗用大量的堆積記憶體
        List<MyLargeClass> listLargeObject = new List<MyLargeClass>();
        for (int i = 0; i < 1000 * 1000; i++)
        {
            listLargeObject.Add(new MyLargeClass());
        }
 
        //這裡將會模擬產生大量的物件執行個體,耗用大量的堆積記憶體
        List<MySmallClass> listSmallObject = new List<MySmallClass>();
        for (int i = 0; i < 1000 * 1000; i++)
        {
            listSmallObject.Add(new MySmallClass());
        }
    }
}




 

2020年10月1日 星期四

Stack Overflow 造成的原因

Stack Overflow 造成的原因

在進行程式設計的時候,經常會聽到 記憶體不足 這樣的問題,其中,對於記憶體不足這樣的現象,會發生在兩種情況下,在這裡將會展示出如何過度使用 執行緒推疊 Stack Overflow。

當在設計類別中的方法時候,對於該方法所使用的參數或者區域變數,若這兩種物件的型別為 數值型別 Value Type,則該物件將會從堆疊結構中取得一個空間來作為儲存、存放的地方,然而,若是一個 參考型別 Reference Type ,則會從堆疊結構中取得一個該參考型別的位置儲存空間,並且要求 堆積 Heap 取得一塊空間,用來儲存該參考物件的相關執行個體內容。

在上面的說明中,可以看到對於 .NET 系統中,將會有兩種類型的記憶體儲存空間,一個是 Heap,這裡將會存放各種參考型別物件值,而且這裡可用的空間非常大,另外一種是 Stack,這裡的空間會使用當前執行緒所配置的空間來使用,而在 .NET 中,一個執行緒在建立的時候,預設將會擁有 1MB 的記憶體空間,任何在方法呼叫過程中,對於參數與區域變數,將會使用這裡的空間。

因此,底下的範例,將會設計一個方法 [NestRecursionMethod] ,這個方法將會自我遞迴呼叫,在這個方法內,將宣告五個 decimal 數值型別的區域變數,每個 decimal 物件,將會耗用 16 bytes的記憶體空間,所以,五個 decimal 物件,將會耗用 16*5 = 80 bytes 記憶體空間;當然在呼叫方法的過程中,還會有其他的資料會儲存在 Thread Stack 這個資料結構空間內。

在這個程式中,因為會反覆呼叫 [NestRecursionMethod] 方法,進而造成 Stack 記憶體空間不斷減少,一旦該 Stack 內沒有足夠的記憶體空間,將會造成 Stack 空間不足,也就是會拋出 Stack Overflow 的例外異常。

這裡是最後執行後的螢幕輸出結果

.
.
.
Level 3818
Level 3819
Level 3820
Level 3821
Level 3822
Level 3823
Level 3824
Level 3825
Level 3826
Stack overflow.

而在 Visual Studio 2019 內,將會看到這樣的例外異常提示畫面

這裡將會是該例外異常的詳細資訊

System.StackOverflowException
  HResult=0x800703E9
  Source=<無法評估例外狀況來源>
  StackTrace: 
<無法評估例外狀況堆疊追蹤>
class Program
{
    static void Main(string[] args)
    {
        int level = 1;
        Console.WriteLine("Hello World!");
        NestRecursionMethod(level);
    }
 
    private static void NestRecursionMethod(int level)
    {
        // 一個 decimal 使用 16 bytes 空間
        decimal decimal1=1;
        decimal decimal2= decimal1+1;
        decimal decimal3 = decimal2 + 1;
        decimal decimal4 = decimal3 + 1;
        decimal decimal5 = decimal4 + 1;
        Console.WriteLine($"Level {level}");
        NestRecursionMethod(++level);
    } 

} 




2020年9月30日 星期三

Code First 建立 Entity Framework Core 應用專案

Code First 建立 Entity Framework Core 應用專案

在前一個文章 Console 專案與 EF Core 讀取已經存在的資料庫 ,說明了如何 DbContext 類別,來讀取後端資料庫內的紀錄;在這裡將要練習另外一種 Entity Framework Core 的架構,稱作 Code First ,也就是說,開發過程中,會先來使用 C# 類別定義出各種對應到後端資料庫內資料表的 Entity 類別,接著,透過這些 Entity 類別,產生出後端的資料,或者,直接進行存取。

對於建立和設定模型的相關資訊,可以參考這裡 建立和設定模型

請按照底下的步驟來進行操作

建立練習專案

  • 打開 Visual Studio 2019
  • 點選 [建立新的專案] 按鈕
  • 在 [建立新專案] 對話窗內,選擇 [主控台應用程式 (.NET Core)] 專案樣板
  • 在 [設定新的專案] 對話窗內,於 [專案名稱] 欄位內輸入 efCodeFirst
  • 點選 [建立] 按鈕,以便開始建立這個專案

加入 Entity Framework Core 要使用到的 NuGet 套件

  • 滑鼠右擊專案內的 [相依性] 節點
  • 選擇 [管理 NuGet 套件]
  • 點選 [瀏覽] 標籤分頁頁次
  • 在 [搜尋] 文字輸入盒內,輸入 [Microsoft.EntityFrameworkCore.SqlServer]
  • 點選 [安裝] 按鈕以便安裝這個套件
  • 在 [搜尋] 文字輸入盒內,輸入 [Microsoft.EntityFrameworkCore.Tools]
  • 點選 [安裝] 按鈕以便安裝這個套件

建立 Entity Framework 要用到的 Entity 模型相關類別

  • 在專案下,建立 [Models] 目錄

  • 在 [Models] 目錄下,建立 [Department] 類別,其程式碼如下

    這裡宣告這個 Department Entity 內,有兩個欄位,分別是 Id 與 Name;另外,他與 Course 這個 Entity 具有一對多的關聯,也就是說,一個 Department 會有多個 Course 紀錄

public class Department
{
    public Department()
    {
        Courses = new HashSet<Course>();
    }
    public int Id { get; set; }
    public string Name { get; set; }
    public virtual ICollection<Course> Courses { get; set; }
}
  • 在 [Models] 目錄下,建立 [Course] 類別,其程式碼如下

    這裡宣告這個 Course Entity 內,有兩個欄位,分別是 Id 與 Name,並且也宣告一個 Foreign Key 外鍵 DepartmentId 與一個導航屬性 Department,這裡表示了 Course 這個 Entity 將會對應到一個 Department Entity上;另外,他與 StudentGrade 這個 Entity 具有一對多的關聯,也就是說,一個 Course 會有多個 StudentGrade 紀錄。因為通常在學校內,一個課程內,將會有多個學生的考試成績紀錄。

public class Course
{
    public Course()
    {
        StudentGrades = new HashSet<StudentGrade>();
    }
    public int Id { get; set; }
    public string Name { get; set; }
    public int DepartmentId { get; set; }
    public virtual Department Department { get; set; }
    public virtual ICollection<StudentGrade> StudentGrades { get; set; }
}
  • 在 [Models] 目錄下,建立 [Student] 類別,其程式碼如下

    這裡宣告這個 Student Entity 內,有兩個欄位,分別是 Id 與 Name,並且也宣告一個導航屬性 Department,這裡表示了 Address 這個 Entity 將會對應到一個 StudentAddress Entity上;另外,他與 StudentGrades 這個 Entity 具有一對多的關聯,也就是說,一個 Student 會有多個 StudentGrade 紀錄。因為通常在學校內,一個學生內,將會有多個課程的考試成績紀錄。

    從這裡將會推論,學生 Student 與 課程 Course 這兩個類別,將會呈現了多對多的關係,而StudentGrade這個 Entity,則是扮演著這兩個 Entity 之間的一對多的關係。

public class Student
{
    public Student()
    {
        StudentGrades = new HashSet<StudentGrade>();
    }
    public int Id { get; set; }
    public string Name { get; set; }
 
    public virtual StudentAddress Address { get; set; }
    public virtual ICollection<StudentGrade> StudentGrades { get; set; }
}
  • 在 [Models] 目錄下,建立 [StudentAddress] 類別,其程式碼如下

    這裡宣告這個 StudentAddress Entity 內,有五個欄位,分別是 Id 、 Address 、 City 、 State 、 Country,並且也宣告一個 Foreign Key 外鍵 StudentId 與一個導航屬性 Student ,這裡表示了 StudentAddress 這個 Entity 將會與 Student Entity 呈現了一對一的關係。

public class StudentAddress
{
    public int Id { get; set; }
    public string Address { get; set; }
    public string City { get; set; }
    public string State { get; set; }
    public string Country { get; set; }
 
    public int StudentId { get; set; }
    public virtual Student Student { get; set; }
}

建立資料庫架構的 DbContent 類別

  • 在 [Models] 目錄下,建立 [DataContext] 類別,其程式碼如下

    這裡建立一個 DataContext 類別,該類別繼承了 DbContext 這個類別;在這個 DataContext 內,使用 DbSet 類別,宣告了三個屬性,說明了這個資料庫內共有三個資料表,有兩個欄位,分別是 Id 與 Name;另外,他與 Course 這個 Entity 具有一對多的關聯,也就是說,一個 Department 會有多個 Course 紀錄

public class DataContext : DbContext
{
    public virtual DbSet<Student> Student { get; set; }
    public virtual DbSet<StudentGrade> StudentGrade { get; set; }
    public virtual DbSet<StudentAddress> StudentAddress { get; set; }
    public virtual DbSet<Course> Course { get; set; }
    public virtual DbSet<Department> Department { get; set; }

    public DataContext()
    {
    }

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

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        if (!optionsBuilder.IsConfigured)
        {
            optionsBuilder.UseSqlServer("Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=SchoolCodeFirst");
        }
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
    }

}

現在 Entity Model 相關資料已經建立完成

建立資料庫

這裡將會有兩種方式,一種是透過命令列下達指令,另外一種是在程式碼中呼叫 API,首先,先確認該電腦上的 [(localdb)\MSSQLLocalDB] 內,沒有 SchoolCodeFirst 這個資料庫

(localdb)\MSSQLLocalDB

使用 PowerShell 來建立資料庫

  • 切換到 [套件管理器主控台] 視窗

    若沒有看到 [套件管理器主控台] 視窗,點選功能表 [工具] > [NuGet 套件管理員] > [套件管理器主控台]

  • 在 [套件管理器主控台] 輸入底下內容

Add-Migration InitialCreate
Update-Database
  • 若看到底下的訊息顯示出來之後,就表示該資料庫已經建立在 [(localdb)\MSSQLLocalDB]

(localdb)\MSSQLLocalDB

每個封裝均由其擁有者提供授權給您。NuGet 對於協力廠商封裝不負任何責任,也不提供相關的任何授權。某些封裝可能包含須由其他授權控管的相依項目。請遵循封裝來源 (摘要) URL 決定有無任何相依項目。

套件管理員主控台主機版本 5.7.0.6726

輸入 'get-help NuGet' 可查看所有可用的 NuGet 命令。

PM> Add-Migration InitialCreate
Build started...
Build succeeded.
To undo this action, use Remove-Migration.
PM> Update-Database
Build started...
Build succeeded.
Applying migration '20200926142800_InitialCreate'.
Done.
PM> 

使用 SQL Server Management Studio (SSMS) 產生 ERD

  • 安裝好 SQL Server Management Studio (SSMS) 之後,請打開這個應用程式

  • 首先會看到 [連線至伺服器] 對話窗

  • 請在 [伺服器名稱] 內,輸入 (localdb)\.

  • 最後,點選 [連線] 按鈕

  • 成功連線之後,將會顯示 [物件總管] 視窗

  • 請展開 [物件總管] 視窗內的 [(localdb.)] > [資料庫] > [School] 節點

  • 滑鼠右擊 [資料庫圖表] 節點,從彈出功能表選取 [新增資料庫圖表] 選項

  • 第一次將會出現 [此資料庫沒有使用資料庫圖表所需的一或多個支援物件。您要建立它們嗎?] 訊息

  • 點選 [是] 按鈕

  • 此時將會出現 [加入資料表] 對話窗

  • 請將全部資料表都選取起來

    想要全部選取,可以先點選第一個資料表 (Course),接著按下 [Shift] 按鍵,點選最後一個資料表(StudentGrade)

  • 最後,點選 [加入] 按鈕

  • 若這些資料表沒有正常排列顯示,請在空白處,使用滑鼠右擊,選擇 [排列資料表],這樣就會看到這個資料庫所以資料表之間的關聯 ERD,哪些是 一對一關係、一對多關係、多對一關係、多對多關係

  • 透過這裡產生的 ERD,來確認這裡使用 Code First 方式,所建立起來的資料庫架構,是否違當初所設計的內容。

使用 API 來建立資料庫

  • 打開專案內的 [Program.cs] 這個檔案
  • 輸入底下程式碼
class Program
{
    static void Main(string[] args)
    {
        var context = new DataContext();
        context.Database.EnsureDeleted();
        context.Database.EnsureCreated();
    }
}

首先將會看到建立起一個 DataContext 類別的 context 物件,透過這個物件便可以操作資料庫行為;接下來呼叫了 context.Database.EnsureDeleted(); API,若此時資料庫系統中有這裡定義的資料庫,會將該資料庫先刪除,接著呼叫 context.Database.EnsureCreated(); API,這裡將會開始建立這個資料庫。

 




2020年9月28日 星期一

Console 專案與 EF Core 讀取已經存在的資料庫

 

Console 專案與 EF Core 讀取已經存在的資料庫

在前一個文章 Entity Framework Core - 反向工程 建立 Entity Model建立 Entity Model ,說明了如何使用反向工程與 Scaffold-DbContext 指令來產生出 Entity Framework Core 需要的 Entity & DbContext 相關類別。

因為有了這些 Entity 定義類別,便可以進行資料庫的操作,在這裡,將會來練習如何開始使用 Entity Framework Core 來讀取資料庫,這裡將會顯示 Person 資料表內的紀錄清單,並且請按照 LastName , FirstName 來排序。

請按照底下的步驟來進行操作

建立練習專案

  • 打開 Visual Studio 2019
  • 點選 [建立新的專案] 按鈕
  • 在 [建立新專案] 對話窗內,選擇 [主控台應用程式 (.NET Core)] 專案樣板
  • 在 [設定新的專案] 對話窗內,於 [專案名稱] 欄位內輸入 DBEntityFrameworkCore
  • 點選 [建立] 按鈕,以便開始建立這個專案

加入 Entity Framework Core 要使用到的 NuGet 套件

  • 滑鼠右擊專案內的 [相依性] 節點
  • 選擇 [管理 NuGet 套件]
  • 點選 [瀏覽] 標籤分頁頁次
  • 在 [搜尋] 文字輸入盒內,輸入 [Microsoft.EntityFrameworkCore.SqlServer]
  • 點選 [安裝] 按鈕以便安裝這個套件
  • 在 [搜尋] 文字輸入盒內,輸入 [Microsoft.EntityFrameworkCore.Tools]
  • 點選 [安裝] 按鈕以便安裝這個套件

使用反向工程來產生 Entity Framework 要用到的 Entity 模型相關類別

  • 切換到 [套件管理器主控台] 視窗

    若沒有看到 [套件管理器主控台] 視窗,點選功能表 [工具] > [NuGet 套件管理員] > [套件管理器主控台]

  • 在 [套件管理器主控台] 輸入底下內容

    因為都在同一個專案內,所以,這裡可以省略 StartupProject & Project 這兩個參數,因此,底下的指令會更為精簡

Scaffold-DbContext "Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=School" Microsoft.EntityFrameworkCore.SqlServer -OutputDir Models -f

現在 Entity Model 相關資料已經建立完成

由於在 [Models] 資料夾內,建立了一個 [SchoolContext] 類別,這個類別將會指向 SQL Server 上的 [School] 資料庫,也就是說,想要對這個資料庫進行任何操作:新增、讀取、修改、刪除,都可以透過這個 [SchoolContext] 類別來做到;而想要知道有那些 Entity 物件對應到資料庫的那些資料表內,請查看 [SchoolContext] 類別的 DbSet 屬性,在這裡將會有這些 Entity 可以存取 Course , CourseInstructor , Department , OfficeAssignment , OnsiteCourse , Outline , Person , StudentGrade ,同樣的,這些名稱也會出現在該資料庫內的資料表。

using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata;

namespace EFCoreReverseEngineering.Models
{
    public partial class SchoolContext : DbContext
    {
        ...
        public virtual DbSet<Course> Course { get; set; }
        public virtual DbSet<CourseInstructor> CourseInstructor { get; set; }
        public virtual DbSet<Department> Department { get; set; }
        public virtual DbSet<OfficeAssignment> OfficeAssignment { get; set; }
        public virtual DbSet<OnsiteCourse> OnsiteCourse { get; set; }
        public virtual DbSet<Outline> Outline { get; set; }
        public virtual DbSet<Person> Person { get; set; }
        public virtual DbSet<StudentGrade> StudentGrade { get; set; }
        ...
    }
}

請打開這個 [Program.cs] 檔案,完成底下的程式碼

using DBEntityFrameworkCore.Model;
using Microsoft.EntityFrameworkCore;
using System;
using System.Linq;
using System.Threading.Tasks;

namespace DBEntityFrameworkCore
{
    class Program
    {
        static async Task Main(string[] args)
        {
            SchoolContext context = new SchoolContext();
            var people = await context.Person
                .OrderBy(x=>x.LastName)
                .ThenBy(x=>x.FirstName)
                .ToListAsync();
            foreach (var item in people)
            {
                Console.WriteLine($"人員:{item.LastName} {item.FirstName}");
            }
        }
    }
}

從上面的程式碼中,可以看到首先建立了一個 SchoolContext 型別的 context 物件,這個物件就代表了遠端的資料庫,當想要讀取 Person 資料表內的紀錄,只需要使用 context.Person 這樣的方式,就可以取得 Person 資料表內的紀錄,這樣的用法對於 C# 開發者並不陌生,因為,就把 Person 這個屬性,當作是一個集合 Collection 類型的物件,因為是集合類型,所以在 Person 這個屬性(DbSet)內,就會擁有了多筆的 Person 型別的物件。

另外,對於要使用 Entity Framework Core 來對資料庫紀錄操作的時候,通常會搭配著 LINQ 功能來進行查詢,對於開發者而言,是不需學習 SQL 語言,就可以存取資料庫了。

因此,因為這裡使用了這樣的敘述,將會把 Person 資料表內的所有紀錄,按照 LastName 先做排序,接著按照 FirstName 來排序,最後顯示在螢幕上。

var people = await context.Person
    .OrderBy(x=>x.LastName)
    .ThenBy(x=>x.FirstName)
    .ToListAsync();

這裡將會是輸出結果

人員:Abercrombie Kim
人員:Alexander Carson
人員:Alonso Meredith
人員:Anand Arturo
人員:Barzdukas Gytis
人員:Browning Meredith
人員:Bryant Carson
人員:Carlson Robyn
人員:Fakhouri Fadi
人員:Gao Erica
人員:Griffin Rachel
人員:Harui Roger
人員:Holt Roger
人員:Jai Damien
人員:Justice Peggy
人員:Kapoor Candace
人員:Li Yan
人員:Lopez Sophia
人員:Martin Randall
人員:Morgan Isaiah
人員:Norman Laura
人員:Olivotto Nino
人員:Powell Carson
人員:Rogers Cody
人員:Serrano Stacy
人員:Shan Alicia
人員:Stewart Jasmine
人員:Suarez Robyn
人員:Tang Wayne
人員:Van Houten Roger
人員:Walker Alexandra
人員:White Anthony
人員:Xu Kristen
人員:Zheng Roger