顯示具有 EntityFrameworkCore 標籤的文章。 顯示所有文章
顯示具有 EntityFrameworkCore 標籤的文章。 顯示所有文章

2022年7月17日 星期日

C# : 在開發方案中,加入自訂的 NuGet 套件來源,無需透過 工具 > 選項 > NuGet 來設定

在開發方案中,加入自訂的 NuGet 套件來源,無需透過 工具 > 選項 > NuGet 來設定

在上一篇文章 C# : 將 EF Core 的資料模型類別庫,打包成為 NuGet 套件,並且上傳到公開 NuGet 伺服器上 中,說明到如何自己設計一個類別庫專案,並且將其打包成為一個 NuGet 套件,接著,將其發佈到私有的 NuGet 伺服器上,最後,透過在 Visual Studio 上加入這個私有的 NuGet 套件來源,成功的安裝這些套件到專案內來進行開發。

有些時候,在進行專案開發需要使用到一些 NuGet 套件,然而,有些時候需要參考到非 https://www.nuget.org/ 官方網站的套件,例如,許多套件是自己團隊或者公司開發的(或者,像是參加我的課程,也會用到我自行開發的 NuGet 套件,方便進行開發練習),並且發佈到其他非 NuGet 的網站上,此時,若要使用這些套件,或者要打開這類專案,都需要事先在 Visual Studio 上進行宣告與設定,否則,將無法建置此類型的專案,因為,無法將這些套件從網路下載下來。

在進行此篇文章動手練習之前

  • 請先打開 Visual Studio 2022

  • 點選功能表 [工具] > [選項]

  • 在 [選項] 對話窗中,展開 [NuGet 套件管理員] > [套件來源] 節點

  • 確認此時對話窗的右方套件來源清單中,是安裝 Visual Studio 2022 預設的選項,沒有其他自行額外追加的項目,若有這些額外新增的項目,請先刪除掉

    Visual Stdio 工具 選項 nuget

建立測試用的主控台應用程式專案

  • 打開 Visual Studio 2022

  • 點選右下方的 [建立新的專案] 按鈕

  • 選擇一個 [主控台應用程式] 的專案範本

  • 點選右下方的 [下一步] 按鈕

  • 在 [設定新的專案] 對話窗內,在 [專案名稱] 欄位中,輸入 CustomNuGet

  • 點選右下方的 [下一步] 按鈕

  • 在 [其他資訊] 對話窗中

  • 取消 [Do not use top-level statements] 這個 checkbox 檢查盒的勾選

  • 點選右下方的 [建立] 按鈕

  • 當這個專案建立完成後

  • 滑鼠右擊 [解決方案] 節點

  • 點選 [加入] > [新增項目]

  • 在 [新增項目 - 方案項目] 對話窗內

  • 在中間清單區域,選擇 [XML] 項目

  • 在下方名稱欄位內,輸入 NuGet.config

  • 點選右下方 [新增] 按鈕

  • 當 [NuGet.config] 編輯視窗出現之後,便可以將底下的宣告內容輸入到這個檔案內

<?xml version="1.0" encoding="utf-8"?>
<configuration>
	<packageSources>
		<add key="Vulcan 教學課程 NuGet 套件" 
        value="https://www.myget.org/F/course-lab/api/v3/index.json" />
	</packageSources>
</configuration>

為了要讓這些新加入的內容生效,建議你將這個專案關閉起來,又或者關閉 Visual Studio 2022`,重新開啟 Visual Studio 2022 與 這個練習專案

驗證可以使用專案特定的 NuGet 套件來源

專案重新開啟之後,請依序進行底下操作,確認剛剛的設定可以正確完成無誤。

在這裡將會要透過剛剛新加入的 [Vulcan 教學課程 NuGet 套件] 套件來源中的一個 Entity Framework Core 課程練習用的套件,這個套件提供了 Entity Framework Core 反向工程的模型與 Code First 代碼優先的模型,有了這些模型便可以快速、方便的進行 Entity Framework Core 課程中的各項練習。

  • 滑鼠右擊該專案的 [相依性] 節點

  • 點選 [管理 NuGet 套件] 選項

  • 當 [NuGet: CustomNuGet] 視窗出現後

  • 點選該視窗右上方的 [套件來源] 右邊下拉選單控制項

  • 從下拉選單清單中,選擇 [Vulcan 教學課程 NuGet 套件] 這個項目

  • 點選該視窗左上方的 [瀏覽] 標籤頁次

  • 沒意外的話,將會看到下面截圖的內容,在這個 NuGet 套件來源中,存在著底下的項目

  • 請選擇 [EFCore.Course] 這個套件,並且安裝起來

  • 底下是安裝這個套件的相關輸出文字

正在還原 C:\Vulcan\Projects\CustomNuGet\CustomNuGet\CustomNuGet.csproj 的封裝...
  GET https://www.myget.org/F/course-lab/api/v3/flatcontainer/efcore.course/index.json
  GET https://api.nuget.org/v3-flatcontainer/efcore.course/index.json
  OK https://www.myget.org/F/course-lab/api/v3/flatcontainer/efcore.course/index.json 295 毫秒
  GET https://www.myget.org/F/course-lab/api/v3/flatcontainer/efcore.course/1.0.0/efcore.course.1.0.0.nupkg
  NotFound https://api.nuget.org/v3-flatcontainer/efcore.course/index.json 810 毫秒
  OK https://www.myget.org/F/course-lab/api/v3/flatcontainer/efcore.course/1.0.0/efcore.course.1.0.0.nupkg 1268 毫秒
已從具有內容雜湊 UYMB5nZ2H2vdU53P7L/orsrojzPkdK6oYTDZltk3g/MpCmLhZeRw1Y6MCU5t/djYixEs91HZTXkCsogdi3PN2w== 的 https://www.myget.org/F/course-lab/api/v3/index.json 安裝 EFCore.Course 1.0.0。
正在安裝 NuGet 套件 EFCore.Course 1.0.0。
正在產生 MSBuild 檔案 C:\Vulcan\Projects\CustomNuGet\CustomNuGet\obj\CustomNuGet.csproj.nuget.g.props。
正在將資產檔案寫入磁碟。路徑: C:\Vulcan\Projects\CustomNuGet\CustomNuGet\obj\project.assets.json
已成功將 'EFCore.Course 1.0.0' 安裝到 CustomNuGet
已成功將 'Microsoft.CSharp 4.5.0' 安裝到 CustomNuGet
已成功將 'Microsoft.Data.SqlClient 2.1.4' 安裝到 CustomNuGet
已成功將 'Microsoft.Data.SqlClient.SNI.runtime 2.1.1' 安裝到 CustomNuGet
已成功將 'Microsoft.EntityFrameworkCore 6.0.6' 安裝到 CustomNuGet
已成功將 'Microsoft.EntityFrameworkCore.Abstractions 6.0.6' 安裝到 CustomNuGet
已成功將 'Microsoft.EntityFrameworkCore.Analyzers 6.0.6' 安裝到 CustomNuGet
已成功將 'Microsoft.EntityFrameworkCore.Relational 6.0.6' 安裝到 CustomNuGet
已成功將 'Microsoft.EntityFrameworkCore.SqlServer 6.0.6' 安裝到 CustomNuGet
已成功將 'Microsoft.Extensions.Caching.Abstractions 6.0.0' 安裝到 CustomNuGet
已成功將 'Microsoft.Extensions.Caching.Memory 6.0.1' 安裝到 CustomNuGet
已成功將 'Microsoft.Extensions.Configuration.Abstractions 6.0.0' 安裝到 CustomNuGet
已成功將 'Microsoft.Extensions.DependencyInjection 6.0.0' 安裝到 CustomNuGet
已成功將 'Microsoft.Extensions.DependencyInjection.Abstractions 6.0.0' 安裝到 CustomNuGet
已成功將 'Microsoft.Extensions.Logging 6.0.0' 安裝到 CustomNuGet
已成功將 'Microsoft.Extensions.Logging.Abstractions 6.0.0' 安裝到 CustomNuGet
已成功將 'Microsoft.Extensions.Options 6.0.0' 安裝到 CustomNuGet
已成功將 'Microsoft.Extensions.Primitives 6.0.0' 安裝到 CustomNuGet
已成功將 'Microsoft.Identity.Client 4.21.1' 安裝到 CustomNuGet
已成功將 'Microsoft.IdentityModel.JsonWebTokens 6.8.0' 安裝到 CustomNuGet
已成功將 'Microsoft.IdentityModel.Logging 6.8.0' 安裝到 CustomNuGet
已成功將 'Microsoft.IdentityModel.Protocols 6.8.0' 安裝到 CustomNuGet
已成功將 'Microsoft.IdentityModel.Protocols.OpenIdConnect 6.8.0' 安裝到 CustomNuGet
已成功將 'Microsoft.IdentityModel.Tokens 6.8.0' 安裝到 CustomNuGet
已成功將 'Microsoft.NETCore.Platforms 3.1.0' 安裝到 CustomNuGet
已成功將 'Microsoft.Win32.Registry 4.7.0' 安裝到 CustomNuGet
已成功將 'Microsoft.Win32.SystemEvents 4.7.0' 安裝到 CustomNuGet
已成功將 'System.Collections.Immutable 6.0.0' 安裝到 CustomNuGet
已成功將 'System.Configuration.ConfigurationManager 4.7.0' 安裝到 CustomNuGet
已成功將 'System.Diagnostics.DiagnosticSource 6.0.0' 安裝到 CustomNuGet
已成功將 'System.Drawing.Common 4.7.0' 安裝到 CustomNuGet
已成功將 'System.IdentityModel.Tokens.Jwt 6.8.0' 安裝到 CustomNuGet
已成功將 'System.Runtime.Caching 4.7.0' 安裝到 CustomNuGet
已成功將 'System.Runtime.CompilerServices.Unsafe 6.0.0' 安裝到 CustomNuGet
已成功將 'System.Security.AccessControl 4.7.0' 安裝到 CustomNuGet
已成功將 'System.Security.Cryptography.Cng 4.5.0' 安裝到 CustomNuGet
已成功將 'System.Security.Cryptography.ProtectedData 4.7.0' 安裝到 CustomNuGet
已成功將 'System.Security.Permissions 4.7.0' 安裝到 CustomNuGet
已成功將 'System.Security.Principal.Windows 4.7.0' 安裝到 CustomNuGet
已成功將 'System.Text.Encoding.CodePages 4.7.0' 安裝到 CustomNuGet
已成功將 'System.Windows.Extensions 4.7.0' 安裝到 CustomNuGet
執行 NuGet 動作花費了 196 毫秒
經過時間: 00:00:03.3675770
========== 已完成 ==========

經過時間: 00:00:00.0296857
========== 已完成 ==========
  • 現在,這個主控台專案內,已經有可以存取後端資料庫的 Entity Framework Core Model 模型了

  • 打開 [Program.cs] 檔案,修改成為如下程式碼

using DBReverse;
using Microsoft.EntityFrameworkCore;

namespace ConsoleApp4
{
    internal class Program
    {
        static async Task Main(string[] args)
        {
            SchoolContext context = new SchoolContext();
            var people = await context.People
                .OrderBy(x => x.LastName)
                .ThenBy(x => x.FirstName)
                .Where(x => x.LastName == "Li")
                .ToListAsync();

            var foo = context.People
                .OrderBy(x => x.LastName);
            foo = foo.ThenBy(z => z.FirstName);
            var bar = foo.Where(x => x.LastName == "Li");
            var bar1 = bar.ToList();
            foreach (var item in people)
            {
                Console.WriteLine($"人員:{item.LastName} {item.FirstName}");
            }
        }
    }
}
  • 執行這個專案,並且查看結果是否為

人員:Li Yan 





2022年7月15日 星期五

C# : 將 EF Core 的資料模型類別庫,打包成為 NuGet 套件,並且上傳到公開 NuGet 伺服器上

 

C# : 將 EF Core 的資料模型類別庫,打包成為 NuGet 套件,並且上傳到公開 NuGet 伺服器上

在這篇文章,將會延續上一篇 EF Core : 使用資料庫反向工程,取得 EF Core 的資料模型 的開發結果,準備要將已經開發好的一個 Entity Framework Core 模型類別庫,打包成為 NuGet 套件,不過,在這裡將不會把這個 NuGet 套件上傳到 www.nuget.org 網站上,而是會上傳到另外一個方便管理的 MyGet 伺服器上

將類別庫專案設定可以產生 NuGet 套件

  • 再度開啟上篇文章所建立的 [DBReverse] 類別庫專案

  • 滑鼠右擊 [DBReverse] 專案節點

  • 從彈出功能表中,點選 [屬性] 選項

  • 此時,將會看到該專案的 [屬性] 視窗顯示在螢幕上

  • 請點選 [屬性] 視窗左方的清單選項 [套件] > [一般]

    Visual Studio 專案套件屬性

  • 請勾選 [在建置時產生 NuGet 套件] 下方的 Checkbox 檢查盒。

  • 對於下方的其他屬性設定值,可以依照自己的需要來做調整

  • 底下將會是這個類別庫專案的設定內容

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>net6.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <Authors>Vulcan Lee</Authors>
    <Product>EF Core 動手實作課程</Product>
    <GeneratePackageOnBuild>True</GeneratePackageOnBuild>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="6.0.7" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.7">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>
  </ItemGroup>

</Project>
  • 切換到 Release 模式下,建置這個類別庫專案

  • 現在,可以從 Visual Studio 2022 中,看到 [bin] > [Release] 資料夾下有個 [DBReverse.1.0.0.nupkg] 檔案產生出來

上ˇ傳到 MyGet 伺服器上

  • 打開 https://www.myget.org/ 網站,並且登入到這個網站內

  • 在首頁上,將會看到右方有底下的畫面

    MyGet Feed

  • 點選 [NEW FEED +] 這個按鈕

  • 當出現了 [Create your MyGet feed] 畫面後

  • 在 [Your feed URL] 欄位內輸入 efcore-reverse-engineering

  • 在 [Your feed description] 欄位中輸入 使用 Entity Framework Core 反向工程建立的模型與DbContext類別庫

  • 捲動該網頁到最下方,點選 [CREARE FEED] 按鈕

  • 當出現 [efcore-reverse-engineering - Packages] 網頁畫面

  • 請點選 [ADD PACKAGE] > [NuGet Package] 按鈕

  • 當 [Add Package] 對話窗出現的時候,切換到 [From an uploaded package] 標籤頁次

  • 點選 [選擇檔案] 按鈕,找到剛剛產生的 NuGet 檔案 - [DBReverse.1.0.0.nupkg]

  • 最後點選右下方的 [ADD] 綠色按鈕,上傳這個 NuGet 套件檔案

  • 點選左邊的 [FEED DETAILS] 連結

  • 找到 [Your NuGet V3 feed URL (Visual Studio 2015+)] 欄位

  • 將該欄位的內容值複製到剪貼簿內,此時的欄位值為 https://www.myget.org/F/efcore-reverse-engineering/api/v3/index.json

開始引用這個 MyGet 上的公開套件

  • 打開 Visual Studio 2022

  • 點選右下方的 [建立新的專案] 按鈕

  • 選擇一個 [主控台應用程式] 的專案範本

  • 點選右下方的 [下一步] 按鈕

  • 在 [設定新的專案] 對話窗內,使用預設值即可

  • 點選右下方的 [下一步] 按鈕

  • 在 [其他資訊] 對話窗中

  • 取消 [Do not use top-level statements] 這個 checkbox 檢查盒的勾選

  • 點選右下方的 [建立] 按鈕

  • 當這個專案建立完成後,接下來要設定飲用者個 NuGet 套件

  • 點選功能表 [工具] > [選項]

  • 此時, [選項] 對話窗將會出現

  • 點選左邊清單的 [NuGet 套件管理員] > [套件來源]

  • 點選右上方的綠色加號按鈕

  • 此時,中間的 [套件來源] 清單中將會多了一筆紀錄

  • 請在下方的 [名稱] 欄位輸入 EF Core 反向工程產生的模型

  • 請在下方的 [來源] 欄位輸入 https://www.myget.org/F/efcore-reverse-engineering/api/v3/index.json

  • 點選右下方的 [更新] 按鈕

  • 點選右下方的 [確定] 按鈕,完成加入一個 NuGet 來源設定

  • 滑鼠右擊該專案內的 [相依性] 節點

  • 從彈出功能表中,點選 [管理 NuGet 套件] 選項

  • 當 NuGet 套件管理視窗出現之後

  • 在該視窗的右上方,將會看到 [套件來源] 文字

  • 點選該文字右方的下拉選單,選擇 [EF Core 反向工程產生的模型] 這個 NuGet 套件來源

  • 現在可以點選這個 NuGet 套件管理視窗左上方的 [瀏覽] 標籤頁次

    若沒有看到下圖畫面,請再度切換 [套件來源] 為 [EF Core 反向工程產生的模型]

  • 在現在的視窗內,將會看到 [DBReverse] 套件出現

  • 點選這個套件,並且安裝到這個專案內

  • 底下是輸出文字內容

正在還原 C:\Vulcan\Projects\ConsoleApp4\ConsoleApp4\ConsoleApp4.csproj 的封裝...
  GET https://api.nuget.org/v3-flatcontainer/dbreverse/index.json
  NotFound https://api.nuget.org/v3-flatcontainer/dbreverse/index.json 218 毫秒
  GET https://www.myget.org/F/course-lab/api/v3/flatcontainer/dbreverse/index.json
  NotFound https://www.myget.org/F/course-lab/api/v3/flatcontainer/dbreverse/index.json 263 毫秒
  GET https://www.myget.org/F/efcore-reverse-engineering/api/v3/flatcontainer/dbreverse/index.json
  OK https://www.myget.org/F/efcore-reverse-engineering/api/v3/flatcontainer/dbreverse/index.json 256 毫秒
  GET https://www.myget.org/F/efcore-reverse-engineering/api/v3/flatcontainer/dbreverse/1.0.0/dbreverse.1.0.0.nupkg
  OK https://www.myget.org/F/efcore-reverse-engineering/api/v3/flatcontainer/dbreverse/1.0.0/dbreverse.1.0.0.nupkg 1230 毫秒
已從具有內容雜湊 skICCYIMQu3qIpAssDu4OdxIfgUwjM8Q9B/WJeNr2D22466yO/C9flV/R01aYnWYNQskpn7POfVwI1XZfkIlYA== 的 https://www.myget.org/F/efcore-reverse-engineering/api/v3/index.json 安裝 DBReverse 1.0.0。
正在安裝 NuGet 套件 DBReverse 1.0.0。
正在產生 MSBuild 檔案 C:\Vulcan\Projects\ConsoleApp4\ConsoleApp4\obj\ConsoleApp4.csproj.nuget.g.props。
正在將資產檔案寫入磁碟。路徑: C:\Vulcan\Projects\ConsoleApp4\ConsoleApp4\obj\project.assets.json
已成功將 'DBReverse 1.0.0' 安裝到 ConsoleApp4
已成功將 'Microsoft.CSharp 4.5.0' 安裝到 ConsoleApp4
已成功將 'Microsoft.Data.SqlClient 2.1.4' 安裝到 ConsoleApp4
已成功將 'Microsoft.Data.SqlClient.SNI.runtime 2.1.1' 安裝到 ConsoleApp4
已成功將 'Microsoft.EntityFrameworkCore 6.0.7' 安裝到 ConsoleApp4
已成功將 'Microsoft.EntityFrameworkCore.Abstractions 6.0.7' 安裝到 ConsoleApp4
已成功將 'Microsoft.EntityFrameworkCore.Analyzers 6.0.7' 安裝到 ConsoleApp4
已成功將 'Microsoft.EntityFrameworkCore.Relational 6.0.7' 安裝到 ConsoleApp4
已成功將 'Microsoft.EntityFrameworkCore.SqlServer 6.0.7' 安裝到 ConsoleApp4
已成功將 'Microsoft.Extensions.Caching.Abstractions 6.0.0' 安裝到 ConsoleApp4
已成功將 'Microsoft.Extensions.Caching.Memory 6.0.1' 安裝到 ConsoleApp4
已成功將 'Microsoft.Extensions.Configuration.Abstractions 6.0.0' 安裝到 ConsoleApp4
已成功將 'Microsoft.Extensions.DependencyInjection 6.0.0' 安裝到 ConsoleApp4
已成功將 'Microsoft.Extensions.DependencyInjection.Abstractions 6.0.0' 安裝到 ConsoleApp4
已成功將 'Microsoft.Extensions.Logging 6.0.0' 安裝到 ConsoleApp4
已成功將 'Microsoft.Extensions.Logging.Abstractions 6.0.0' 安裝到 ConsoleApp4
已成功將 'Microsoft.Extensions.Options 6.0.0' 安裝到 ConsoleApp4
已成功將 'Microsoft.Extensions.Primitives 6.0.0' 安裝到 ConsoleApp4
已成功將 'Microsoft.Identity.Client 4.21.1' 安裝到 ConsoleApp4
已成功將 'Microsoft.IdentityModel.JsonWebTokens 6.8.0' 安裝到 ConsoleApp4
已成功將 'Microsoft.IdentityModel.Logging 6.8.0' 安裝到 ConsoleApp4
已成功將 'Microsoft.IdentityModel.Protocols 6.8.0' 安裝到 ConsoleApp4
已成功將 'Microsoft.IdentityModel.Protocols.OpenIdConnect 6.8.0' 安裝到 ConsoleApp4
已成功將 'Microsoft.IdentityModel.Tokens 6.8.0' 安裝到 ConsoleApp4
已成功將 'Microsoft.NETCore.Platforms 3.1.0' 安裝到 ConsoleApp4
已成功將 'Microsoft.Win32.Registry 4.7.0' 安裝到 ConsoleApp4
已成功將 'Microsoft.Win32.SystemEvents 4.7.0' 安裝到 ConsoleApp4
已成功將 'System.Collections.Immutable 6.0.0' 安裝到 ConsoleApp4
已成功將 'System.Configuration.ConfigurationManager 4.7.0' 安裝到 ConsoleApp4
已成功將 'System.Diagnostics.DiagnosticSource 6.0.0' 安裝到 ConsoleApp4
已成功將 'System.Drawing.Common 4.7.0' 安裝到 ConsoleApp4
已成功將 'System.IdentityModel.Tokens.Jwt 6.8.0' 安裝到 ConsoleApp4
已成功將 'System.Runtime.Caching 4.7.0' 安裝到 ConsoleApp4
已成功將 'System.Runtime.CompilerServices.Unsafe 6.0.0' 安裝到 ConsoleApp4
已成功將 'System.Security.AccessControl 4.7.0' 安裝到 ConsoleApp4
已成功將 'System.Security.Cryptography.Cng 4.5.0' 安裝到 ConsoleApp4
已成功將 'System.Security.Cryptography.ProtectedData 4.7.0' 安裝到 ConsoleApp4
已成功將 'System.Security.Permissions 4.7.0' 安裝到 ConsoleApp4
已成功將 'System.Security.Principal.Windows 4.7.0' 安裝到 ConsoleApp4
已成功將 'System.Text.Encoding.CodePages 4.7.0' 安裝到 ConsoleApp4
已成功將 'System.Windows.Extensions 4.7.0' 安裝到 ConsoleApp4
執行 NuGet 動作花費了 247 毫秒
經過時間: 00:00:04.0392165
========== 已完成 ==========

經過時間: 00:00:00.0572992
========== 已完成 ==========
  • 打開 [Program.cs] 檔案,修改成為如下程式碼
using DBReverse;
using Microsoft.EntityFrameworkCore;

namespace ConsoleApp4
{
    internal class Program
    {
        static async Task Main(string[] args)
        {
            SchoolContext context = new SchoolContext();
            var people = await context.People
                .OrderBy(x => x.LastName)
                .ThenBy(x => x.FirstName)
                .Where(x => x.LastName == "Li")
                .ToListAsync();

            var foo = context.People
                .OrderBy(x => x.LastName);
            foo = foo.ThenBy(z => z.FirstName);
            var bar = foo.Where(x => x.LastName == "Li");
            var bar1 = bar.ToList();
            foreach (var item in people)
            {
                Console.WriteLine($"人員:{item.LastName} {item.FirstName}");
            }
        }
    }
}
  • 執行這個專案,並且查看結果是否為
人員:Li Yan







2022年7月13日 星期三

EF Core : 使用資料庫反向工程,取得 EF Core 的資料模型

EF Core : 使用資料庫反向工程,取得 EF Core 的資料模型

在使用 Entity Framework Core 進行專案開發的時候,需要先建立好 Entity Framework Core 需要使用的 Model 模型,這樣才能夠與後端的資料庫進行記錄的資料存取需求,而模型是由實體類別和表示與資料庫之會話的內容物件所組成。

一般來說, Entity Framework Core 提供了底下的三種方法來建立要開發用到模型:

  • 透過 Entity Framework Core 提供的工具命令,讀取現有的資料庫綱要 Schema 資訊來產生 EF Core 需要用到模型。
  • 自己手做,將模型手動編寫成符合資料庫的程式碼,這樣子的做法十分刻苦,而且經常會容易出錯,不建議使用。
  • 使用 C# 程式語言進行設計出許多 POCO (Plain Old CLR Object) 類別與 EF Core 會用到的模型之後,透過 EF 移轉 Migration 工具的協助,根據剛剛設計出來的模型資訊,分析並且建立資料庫。 當模型變更時,移轉可讓資料庫同步更新。

在這篇文章中,將會說明第一種的作法,那就是透過 Entity Framework Core 提供的工具命令,讀取現有的資料庫綱要 Schema 資訊來產生 EF Core 需要用到模型的過程與做法。

建立一個測試用的資料庫

為了要練習如何使用資料庫反向工程方式,來建立 EF Core 會用到的模型,在這裡將會建立出底下 ER 模型的資料庫出來,並且該資料庫內也會存在著許多範例紀錄

反向工程練習用資料庫模型

  • 請使用瀏覽器開啟 https://github.com/vulcanlee/Entity-Framework-Core-Getting-Started/blob/main/Database/SchemaAndData.sql 這個網址

  • 在該網頁的最右方中間地方(請參考下圖),會看到一個 [Copy] 複製內容按鈕

  • 請點選這個按鈕,把這裡 SQL 指令複製到剪貼簿內

  • 開啟 Visual Studio 2022 開發工具

  • 請點選右下角的 [不使用程式碼繼續] 的藍色文字

    Visual Studio 2022

  • 現在將進入到 Visual Studio 2022 開發工具內

  • 從功能表中,點選 [檢視] > [SQL Server 物件總管]

    檢視 > SQL Server 物件總管

  • 此時,將會看到 [SQL Server 物件總管] 這個視窗出現在螢幕上

  • 在這個 [SQL Server 物件總管] 視窗內

  • 展開 [SQL Server] 節點

  • 將會看到 [(localdb)\MSSQLLocalDB...] 這個 SQL Server Express LocalDB 這個節點

  • 使用滑鼠右擊這個節點

  • 從彈出功能表中,點選 [新增查詢] 這個選項

  • 此時,將會看到 [SQLQuery1.sql] 視窗出現在螢幕上

  • 將剛剛複製到剪貼簿內的 SQL 指令,貼到這個視窗內

  • 最後,在 [SQLQuery1.sql] 視窗左上方,將會看到一個綠色三角形(請參考上面螢幕截圖紅色箭頭指向地方)

  • 請點選這個綠色三角形按鈕,以便開始執行這個 SQL 指令

  • 很快的,[School] 資料庫已經建立完成了,並且相關資料表內也都有紀錄存在

  • 滑鼠再度右擊這個 [(localdb)\MSSQLLocalDB...] 節點

  • 從彈出功能表中點選 [重新整理] 選項

  • 此時,從 [SQL Server 物件總管] 視窗中,將會看到剛剛建立好的 [School] 資料庫與相關資料表物件

建立 EF Core 需要用到的 School 資料庫模型

為了要能夠建立起 EF Core 能夠用到的模型,可以建立一個 主控台專案 或者 一個 .NET 類別庫;因為,下一篇文章將會說明如何將一個類別庫,打包成為 NuGet 套件,並且上傳到 MyGet 伺服器上,以便可以日後重複使用,所以,在此,將會選擇建立一個 .NET 類別庫的方式

  • 打開 Visual Studio 2022 開發工具

  • 從 Visual Studio 2022 啟動視窗右下方,找到並點選 [建立新新的專案] 這個按鈕

  • 此時,將會看到 [建立新專案] 對話窗出現在螢幕上

  • 請在該對話窗的上方,將會看到三個下拉選單

  • 在最左邊的 [所有語言] 下拉選單中,點取與選擇 [C#] 這個項目

  • 在最右邊的 [所有專案類型] 下拉選單中,點取與選擇 [程式庫] 這個項目

  • 在中間的清單區域,第一個項目將會是 [類別庫] 專案,用於建立以 .NET 或 .NET Standard 為目標的類別庫

  • 請點選這個項目

  • 點選右下角的 [下一步] 按鈕

  • 在 [設定新的專案] 對話窗中

  • 找到 [專案名稱] 欄位,在此輸入 DBReverse 作為這個類別庫專案名稱

  • 點選右下角的 [下一步] 按鈕

  • 看到 [其他資訊] 對話窗出現後,可以選擇預設選項 [.NET 6 (長期支援)]

  • 點選右下角的 [建立] 按鈕

  • 一旦這個專案建立成功後,在 [方案總管] 中找到並且刪除 [Class1.cs] 檔案

  • 滑鼠右擊 [DBReverse] 專案內的 [相依性] 節點

  • 從彈出功能表中選擇 [管理 NuGet 套件]

  • 搜尋並且安裝 [Microsoft.EntityFrameworkCore.SqlServer] 套件

  • 搜尋並且安裝 [Microsoft.EntityFrameworkCore.Tools] 套件

  • 點選功能表 [工具] > [NuGet 套件管理員] > [套件管理器主控台]

  • 請在 [套件管理器主控台] 視窗內,輸入底下指令

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

    套件管理器主控台

  • 底下是執行後的輸出文字

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

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

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

PM> Scaffold-DbContext "Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=School" Microsoft.EntityFrameworkCore.SqlServer
Build started...
Build succeeded.
To protect potentially sensitive information in your connection string, you should move it out of source code. You can avoid scaffolding the connection string by using the Name= syntax to read it from configuration - see https://go.microsoft.com/fwlink/?linkid=2131148. For more guidance on storing connection strings, see http://go.microsoft.com/fwlink/?LinkId=723263.
PM> 

檢視反向工程的執行結果

  • 查看 [方案總管] 將會看到如下圖的畫面

    方案總管

  • 根據最前面所提到的這個 School 資料庫關聯關係圖

    反向工程練習用資料庫模型

  • 可以看的出來,所有在資料庫內的資料表,都有在 [方案總管] 內出現

  • 每個資料表都會對應到一個 C# 類別

  • 例如 對於資料庫上的 [Person] 這個資料表,將會自動產生出一個 [Person.cs] 檔案來對應

  • 底下將會是這個 Person 資料表的 SQL 指令

-- Create the Person table.
IF NOT EXISTS (SELECT * FROM sys.objects 
        WHERE object_id = OBJECT_ID(N'[dbo].[Person]') 
        AND type in (N'U'))
BEGIN
CREATE TABLE [dbo].[Person](
    [PersonID] [int] IDENTITY(1,1) NOT NULL,
    [LastName] [nvarchar](50) NOT NULL,
    [FirstName] [nvarchar](50) NOT NULL,
    [HireDate] [datetime] NULL,
    [EnrollmentDate] [datetime] NULL,
 CONSTRAINT [PK_School.Student] PRIMARY KEY CLUSTERED 
(
    [PersonID] ASC
)WITH (IGNORE_DUP_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
END
GO
  • 底下將會是這個 Person.cs 檔案的 C# 程式碼
public partial class Person
{
    public Person()
    {
        StudentGrades = new HashSet<StudentGrade>();
        Courses = new HashSet<Course>();
    }
    public int PersonId { get; set; }
    public string LastName { get; set; } = null!;
    public string FirstName { get; set; } = null!;
    public DateTime? HireDate { get; set; }
    public DateTime? EnrollmentDate { get; set; }
    public virtual OfficeAssignment OfficeAssignment { get; set; } = null!;
    public virtual ICollection<StudentGrade> StudentGrades { get; set; }
    public virtual ICollection<Course> Courses { get; set; }
}
  • 最後則是最重要的檔案 [SchoolContext.cs]
  • 這是一個繼承 DbContext 類別,這個 DbContext 將會提供與資料庫系統之間的對話,可以用於查詢與儲存記錄從 C# 物件內到資料庫的資料表紀錄裡,原則上,DbContext 是個 Unit Of Work 工作單位 與 Repository 存放酷的設計模式組合而成的。
  • SchoolContext.cs 的程式碼如下所示
using System;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata;

namespace DBReverse
{
    public partial class SchoolContext : DbContext
    {
        public SchoolContext()
        {
        }

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

        public virtual DbSet<Course> Courses { get; set; } = null!;
        public virtual DbSet<Department> Departments { get; set; } = null!;
        public virtual DbSet<OfficeAssignment> OfficeAssignments { get; set; } = null!;
        public virtual DbSet<OnsiteCourse> OnsiteCourses { get; set; } = null!;
        public virtual DbSet<Outline> Outlines { get; set; } = null!;
        public virtual DbSet<Person> People { get; set; } = null!;
        public virtual DbSet<StudentGrade> StudentGrades { get; set; } = null!;

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            if (!optionsBuilder.IsConfigured)
            {
#warning To protect potentially sensitive information in your connection string, you should move it out of source code. You can avoid scaffolding the connection string by using the Name= syntax to read it from configuration - see https://go.microsoft.com/fwlink/?linkid=2131148. For more guidance on storing connection strings, see http://go.microsoft.com/fwlink/?LinkId=723263.
                optionsBuilder.UseSqlServer("Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=School");
            }
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Course>(entity =>
            {
                entity.ToTable("Course");

                entity.Property(e => e.CourseId)
                    .ValueGeneratedNever()
                    .HasColumnName("CourseID");

                entity.Property(e => e.DepartmentId).HasColumnName("DepartmentID");

                entity.Property(e => e.Title).HasMaxLength(100);

                entity.HasOne(d => d.Department)
                    .WithMany(p => p.Courses)
                    .HasForeignKey(d => d.DepartmentId)
                    .OnDelete(DeleteBehavior.ClientSetNull)
                    .HasConstraintName("FK_Course_Department");

                entity.HasMany(d => d.People)
                    .WithMany(p => p.Courses)
                    .UsingEntity<Dictionary<string, object>>(
                        "CourseInstructor",
                        l => l.HasOne<Person>().WithMany().HasForeignKey("PersonId").OnDelete(DeleteBehavior.ClientSetNull).HasConstraintName("FK_CourseInstructor_Person"),
                        r => r.HasOne<Course>().WithMany().HasForeignKey("CourseId").OnDelete(DeleteBehavior.ClientSetNull).HasConstraintName("FK_CourseInstructor_Course"),
                        j =>
                        {
                            j.HasKey("CourseId", "PersonId");

                            j.ToTable("CourseInstructor");

                            j.IndexerProperty<int>("CourseId").HasColumnName("CourseID");

                            j.IndexerProperty<int>("PersonId").HasColumnName("PersonID");
                        });
            });

            modelBuilder.Entity<Department>(entity =>
            {
                entity.ToTable("Department");

                entity.Property(e => e.DepartmentId)
                    .ValueGeneratedNever()
                    .HasColumnName("DepartmentID");

                entity.Property(e => e.Budget).HasColumnType("money");

                entity.Property(e => e.Name).HasMaxLength(50);

                entity.Property(e => e.StartDate).HasColumnType("datetime");
            });

            modelBuilder.Entity<OfficeAssignment>(entity =>
            {
                entity.HasKey(e => e.InstructorId);

                entity.ToTable("OfficeAssignment");

                entity.Property(e => e.InstructorId)
                    .ValueGeneratedNever()
                    .HasColumnName("InstructorID");

                entity.Property(e => e.Location).HasMaxLength(50);

                entity.Property(e => e.Timestamp)
                    .IsRowVersion()
                    .IsConcurrencyToken();

                entity.HasOne(d => d.Instructor)
                    .WithOne(p => p.OfficeAssignment)
                    .HasForeignKey<OfficeAssignment>(d => d.InstructorId)
                    .OnDelete(DeleteBehavior.ClientSetNull)
                    .HasConstraintName("FK_OfficeAssignment_Person");
            });

            modelBuilder.Entity<OnsiteCourse>(entity =>
            {
                entity.HasKey(e => e.CourseId);

                entity.ToTable("OnsiteCourse");

                entity.Property(e => e.CourseId)
                    .ValueGeneratedNever()
                    .HasColumnName("CourseID");

                entity.Property(e => e.Days).HasMaxLength(50);

                entity.Property(e => e.Location).HasMaxLength(50);

                entity.Property(e => e.Time).HasColumnType("smalldatetime");

                entity.HasOne(d => d.Course)
                    .WithOne(p => p.OnsiteCourse)
                    .HasForeignKey<OnsiteCourse>(d => d.CourseId)
                    .OnDelete(DeleteBehavior.ClientSetNull)
                    .HasConstraintName("FK_OnsiteCourse_Course");
            });

            modelBuilder.Entity<Outline>(entity =>
            {
                entity.ToTable("Outline");

                entity.Property(e => e.OutlineId).HasColumnName("OutlineID");

                entity.Property(e => e.CourseId).HasColumnName("CourseID");

                entity.Property(e => e.Title).HasMaxLength(100);

                entity.HasOne(d => d.Course)
                    .WithMany(p => p.Outlines)
                    .HasForeignKey(d => d.CourseId)
                    .OnDelete(DeleteBehavior.ClientSetNull)
                    .HasConstraintName("FK_Outline_Course");
            });

            modelBuilder.Entity<Person>(entity =>
            {
                entity.ToTable("Person");

                entity.Property(e => e.PersonId).HasColumnName("PersonID");

                entity.Property(e => e.EnrollmentDate).HasColumnType("datetime");

                entity.Property(e => e.FirstName).HasMaxLength(50);

                entity.Property(e => e.HireDate).HasColumnType("datetime");

                entity.Property(e => e.LastName).HasMaxLength(50);
            });

            modelBuilder.Entity<StudentGrade>(entity =>
            {
                entity.HasKey(e => e.EnrollmentId);

                entity.ToTable("StudentGrade");

                entity.Property(e => e.EnrollmentId).HasColumnName("EnrollmentID");

                entity.Property(e => e.CourseId).HasColumnName("CourseID");

                entity.Property(e => e.Grade).HasColumnType("decimal(3, 2)");

                entity.Property(e => e.StudentId).HasColumnName("StudentID");

                entity.HasOne(d => d.Course)
                    .WithMany(p => p.StudentGrades)
                    .HasForeignKey(d => d.CourseId)
                    .OnDelete(DeleteBehavior.ClientSetNull)
                    .HasConstraintName("FK_StudentGrade_Course");

                entity.HasOne(d => d.Student)
                    .WithMany(p => p.StudentGrades)
                    .HasForeignKey(d => d.StudentId)
                    .OnDelete(DeleteBehavior.ClientSetNull)
                    .HasConstraintName("FK_StudentGrade_Student");
            });

            OnModelCreatingPartial(modelBuilder);
        }

        partial void OnModelCreatingPartial(ModelBuilder modelBuilder);
    } 

}