星期三, 4月 09, 2025

[SSRS] Image - 刪除內嵌圖檔

SSRS Image 控件內嵌模式會把圖檔放進報表檔案內,報表檔案大小會增加,因此當內嵌圖檔有更新時都會注意把舊圖檔刪除,但每次都忘記去哪進行刪除,都直覺把報表檔案打開刪除舊圖檔相關內容

SSRS IDE

慣性在屬性視窗內尋找刪除按鈕,但其實是在報表資料 => 影像內進行刪除   

報表檔案

報表本身就是 xml 檔案,直接打開檔案搜尋 EmbeddedImages 關鍵字,就可以找到圖檔相關資訊,通常都在報表最下方位置

星期日, 3月 30, 2025

[EFCore] Cache

同事遇上 EF cache 造成結果不如預期, 寫份簡易範例來記錄並介紹

C# 完整 Code

根據該筆記 [EFCore] LogTo 輸出 Linq 產生的 TSQL 語法,藉此來觀察是否發生資料庫存取,而輸出結果會稍微整理,以方便閱讀為主
using EFCoreCache.Models;
using Microsoft.EntityFrameworkCore;

namespace EFCoreCache
{
    internal class Program
    {
        static void Main(string[] args)
        {
            AdventureWorks2022DbContext dbContext = new();

            Console.WriteLine("----- Query1:第一次存取 Entity");

            Console.WriteLine($"存取前 Person Entity Cache Count:{dbContext.Person.Local.Count}");
            var query1 = dbContext.Person.SingleOrDefault(p => p.BusinessEntityID == 1);
            Console.WriteLine($"存取後 Person Entity Cache Count:{dbContext.Person.Local.Count}");
            Print(query1);

            Console.WriteLine("----- Query2:理解 Cache 行為");

            // 修正 Cache 內資料
            query1.LastName = "LastNameFromCache";

            // 直接更新資料庫內資料
            dbContext.Person
                   .Where(w => w.BusinessEntityID == 1)
                   .ExecuteUpdate(e =>
                   e.SetProperty(sod => sod.LastName, "LastNameFromDB"));

            var query2 = dbContext.Person.SingleOrDefault(p => p.BusinessEntityID == 1);
            Print(query2);

            Console.WriteLine("----- Query3:AsNoTracking()");
            var query3 = dbContext.Person.AsNoTracking().SingleOrDefault(p => p.BusinessEntityID == 1);
            Print(query3);

            Console.WriteLine("----- Query4:Find()");
            var query4 = dbContext.Person.Find(1);
            Print(query4);
        }

        private static void Print(Person p)
        {
            Console.WriteLine($"內容輸出:{p.LastName}");
        }
    }
}
後續會分別解釋每個 Query

Query1:第一次存取 Entity 

使用 Single 進行存取,目前該 entity 沒有 cache,所以會先放進 cache 內,再把 entity 往 AP 丟
Console.WriteLine($"存取前 Person Entity Cache Count:{dbContext.Person.Local.Count}");
var query1 = dbContext.Person.SingleOrDefault(p => p.BusinessEntityID == 1);
Console.WriteLine($"存取後 Person Entity Cache Count:{dbContext.Person.Local.Count}");
Print(query1);
存取前 Person Entity Cache Count:0

SELECT TOP(2) * FROM [Person].[Person] AS [p] WHERE [p].[BusinessEntityID] = 1

存取後 Person Entity Cache Count:1

內容輸出:Sanchez

Query2:理解 Cache 行為

更新 Query1 產生的 entity 資料,同時也更新資料庫內資料,藉此觀察使用 Single() 存取相同 entity 行為
// 修正 Cache 內資料
query1.LastName = "LastNameFromCache";

// 直接更新資料庫內資料
dbContext.Person
    .Where(w => w.BusinessEntityID == 1)
    .ExecuteUpdate(e =>
       e.SetProperty(p => p.LastName, "LastNameFromDB"));

var query2 = dbContext.Person.SingleOrDefault(p => p.BusinessEntityID == 1);
Print(query2);
使用 Single 存取相同的 entity,EFCore 還是會對資料庫進存取,但該資料目前已經在 cache 內,所以會把 cache 內 entity 往 AP 丟,內容輸出為 LastNameFromCache,在官方文章內有相關說明

Queries are always executed against the database even if the entities returned in the result already exist in the context.

How Queries Work - The life of a query

3. For each item in the result set

a. If the query is a tracking query, EF checks if the data represents an entity already in the change tracker for the context instance

  • If so, the existing entity is returned
  • If not, a new entity is created, change tracking is set up, and the new entity is returned

b.If the query is a no-tracking query, then a new entity is always created and returned

另外 ExecuteUpdate 是不會有 cache 的,詳見 [EFCore] ExecuteUpdate 和 ExecuteDelete

UPDATE Person.Person SET LastName = 'LastNameFromDB' WHERE BusinessEntityID = 1

SELECT TOP(2) * FROM [Person].[Person] AS [p] WHERE [p].[BusinessEntityID] = 1

內容輸出:LastNameFromCache

Query3:AsNoTracking()

使用 AsNoTracking() 就可以跳過 cache,直接存取資料庫並回傳
var query3 = dbContext.Person.AsNoTracking().SingleOrDefault(p => p.BusinessEntityID == 1);
Print(query3);
SELECT TOP(2) * FROM [Person].[Person] AS [p] WHERE [p].[BusinessEntityID] = 1

內容輸出:LastNameFromDB

Query4:Find()

當 cache 內有該 entity 時,使用 Find() 會直接回傳 cache 內 entity,不會有資料庫存取行為,沒有 TSQL 輸出,但 cache 內沒有該 entity 的話,才會去資料庫存取
var query4 = dbContext.Person.Find(1);
Print(query4);
內容輸出:LastNameFromCache
以上範例雖然是以 EFCore 9 為記錄內容,但在 EF 6.5.1 也是相同行為

星期三, 3月 19, 2025

[EFCore] 陰影屬性

通常 EF Entity Property 會對應 Table Column,但透過陰影屬性 (Shadow Property) 可以讓該 EF Entity 沒有該 Property,但卻還是可以對應 Table Column。 適用場景在於 Table 常設的 CreateTime、ModifiedDate 欄位或是資料軟刪除欄位

Shadow Property 設定

在 DbContext 內的 OnModelCreating 中透過 Fluent API 來進行設定
public partial class AdventureWorks2022DbContext : DbContext
{
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Person>()
            .Property<DateTime>("ModifiedDate");
    }
}

C# Code

namespace EFCoreShadowProperty
{
    internal class Program
    {
        static void Main(string[] args)
        {
            string shadowPropertyName = "ModifiedDate";

            var dbContext = new AdventureWorks2022DbContext();

            Console.WriteLine("---------- 在 OrderBy 中使用 EF.Property 來抓取 Shadow Property");
            var person = dbContext
                .Person
                .OrderBy(p => EF.Property<DateTime>(p, shadowPropertyName))
                .FirstOrDefault();

            // 取值並顯示
            var modifyDate = dbContext.Entry(person).Property(shadowPropertyName).CurrentValue;
            Console.WriteLine($"---------- 修改時間最早的資料:{person.LastName}-{person.FirstName}-{modifyDate}");

            Console.WriteLine($"---------- 設定 Shadow Property 值並儲存");
            // 設定值並儲存
            DateTime now = DateTime.Now;
            dbContext.Entry(person).Property(shadowPropertyName).CurrentValue = now;
            dbContext.SaveChanges();

            Console.WriteLine($"---------- 在 Where 中使用 EF.Property 來抓取 Shadow Property");
            var updatePerson = dbContext
                .Person
                .Where(p => EF.Property<DateTime>(p, shadowPropertyName) == now)
                .ToList();
        }
    }
}

輸出結果觀察


設定 [EFCore] LogTo 來觀察 Shadow Property 所產生的 TSQL,以下有把輸出資訊簡化,方便閱讀
---------- 在 OrderBy 中使用 EF.Property 來抓取 Shadow Property

SELECT TOP(1) 欄位名稱
FROM [Person].[Person] AS [p]
ORDER BY [p].[ModifiedDate]

---------- 修改時間最早的資料:Tamburello-Roberto-2007/11/4 上午 12:00:00

---------- 設定 Shadow Property 值並儲存

SET IMPLICIT_TRANSACTIONS OFF;
SET NOCOUNT ON;
UPDATE [Person].[Person] SET [ModifiedDate] = @p0
WHERE [BusinessEntityID] = @p1;
SELECT @@ROWCOUNT;

---------- 在 Where 中使用 EF.Property 來抓取 Shadow Property

SELECT 欄位名稱
FROM [Person].[Person] AS [p]
WHERE [p].[ModifiedDate] = @__now_0

星期六, 3月 15, 2025

[Azure] Notifications - Pull Request

在 Azure DevOps 上設定 PR 通知,當 PR 建立時可以發信通知該 Project 相關成員

Project Setting => Notifications => New subscription

Code (Git) => A pull request is created or updated

subscription 相關設定,分別為
  • Description:修改為方便辨識名稱
  • Deliver to:選擇 Member of Project Team,預設是根據 Role
  • Filter:A specific team project:選擇特定專案
Filter criteria 為發信條件,紀錄兩種觸發方式
  • Event Type = Pull request created
  • Status changes to Completed
兩者差異在於發出訊息內容,Pull request created 內會包含 Reviewer、檔案變更、Commits 資訊,Status changes to Completed 只有 Reviewer

下圖為官方文章 - Create an email subscription 發信條件

星期五, 3月 14, 2025

[Azure] Contact email

 Azure DevOps 上登錄信箱即為聯絡信箱,但可以在 User Setting => Profile => Contact email 內去進行修改

星期四, 3月 13, 2025

[Outlook] 轉寄

在 Outlook 內設定,要把收到信件轉寄到指定信箱去

設定 => 郵件 => 轉寄 => 啟用轉寄並輸入信箱,這樣寄到 Outlook 信箱的信件就會轉寄到指定信箱,另外也可以透過設定規則,符合規則才進行轉寄

星期二, 3月 11, 2025

[EFCore] LogTo

根據官方文章 Simple Logging 內容,要在 Console 上顯示 Linq to Entity 產生的 TSQL 語法。

只要在 DbContext.OnConfiguring() 內進行 LogTo 設定,語法如下
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        => optionsBuilder.UseSqlServer(連線字串)
        .LogTo(Console.WriteLine , LogLevel.Information);
        // 或
        .LogTo(message => Console.WriteLine(message) , LogLevel.Information);
萬一沒有指定 LogLevel.Infomation 會輸出過多訊息,變得不容易找到 TSQL 語法
info: 2025/3/11 08:41:31.472 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command) Executed DbCommand (12ms) [Parameters=[], CommandType='Text', CommandTimeout='30'] SELECT [p].[BusinessEntityID], [p].[AdditionalContactInfo], [p].[Demographics], [p].[EmailPromotion], [p].[FirstName], [p].[LastName], [p].[MiddleName], [p].[ModifiedDate], [p].[NameStyle], [p].[PersonType], [p].[Suffix], [p].[Title], [p].[rowguid] FROM [Person].[Person] AS [p]
文章內還其他輸出模式、顯示機密資料和篩選資訊相關可以參考。

星期一, 3月 10, 2025

[C#] String.Insert()

閱讀 C# 字串相關函數時,突然發現有 Insert() 可以對字串進行文字插入,根據 官方文章 用 LinqPad 筆記一下
void Main()
{
	string animal_fox = "fox";
	string animal_dog = "dog";
	string target = $"The {animal_fox} jumps over the {animal_dog}";

	string strInsert = target.Insert(target.IndexOf(animal_fox), "bold ");
	target.Dump("Insert() 不會更改原本字串內容");
	strInsert.Dump("Insert() 會返回一個新字串");
	
	strInsert = strInsert.Insert(strInsert.IndexOf(animal_dog), "lazy ");
	strInsert.Dump("對原字串進行 insert");
	
	strInsert = strInsert.Insert(strInsert.Length, ".");
	strInsert.Dump("當 StartIndex 參數等同於字串長度,會直接把內容附加字串最後面");
}


星期日, 3月 09, 2025

[EF] StartsWith 轉 TSQL 語法

看見 EFCore 範例使用 C# StartsWith 轉出的 TSQL 是 LIKE '前綴文字%',但在 EF 時代印象中轉出的 TSQL 會使用 substring() 才是,驗證看看
using System.Linq;

namespace EFLike
{
    class Program
    {
        static void Main(string[] args)
        {
            var dbContext = new AdventureWorks2022DbContext();
            var data = dbContext.Person
                .Where(p => p.LastName.StartsWith("A"))
                .ToList();
        }
    }
}
SQL Profile 側錄結果

從 EF 時代就誤會到 EFCore

星期一, 3月 03, 2025

[VS] 格式化 - 忽略宣告陳述式中的空格

在 Line 群組看人家討論時,常常會看見宣告變數時,會使用空白排版,沒想到現在 VS2022 內有這選項,預設是沒有啟用,開啟路徑為工具 => 選項 => 文字編輯器 => C# => 程式碼樣式 => 格式化 => 間距 => 忽略宣告陳述式中的空格

星期六, 2月 15, 2025

[RV] 執行操作時發生錯誤

同事有設計一款非同步進度條,當該功能需要長時間操作時,可以讓使用者意識到系統正在跑,而不是系統當掉,ReportViewer Render 報表時常常需要點時間,所以把 ReportViewer 當成 callback 塞進去時,效果都如預期,但是當按下 ReportViewer 列印按鈕時,會拋出該錯誤訊息,在該 stackoverflow 討論內也有人遇上相同情況,最後只好先不要進度條功能,維持現況囉
正常點選列印按鈕會出現印表機選項視窗
進度條效果非同步 Code
public async Task PrograssBarShow(Action action, int second = 60)
{
    await Task.Factory.StartNew(() =>
    {
        _prograssBar.Show(this, second);

        action();

        _prograssBar.Close();
    });
}

星期五, 2月 14, 2025

[SSRS] DataSource 重覆,造成無法開啟資料集功能

開發 SSRS 報表時,要新增資料集時,VS 突然拋出錯誤訊息,訊息如下
詳細錯誤訊息
如需叫用 Just-In-Time (JIT) 偵錯的詳細資料, 請參閱本訊息結尾處 (而非這個對話方塊) 的資訊。

************** 例外狀況文字 ************** 

System.NullReferenceException: 並未將物件參考設定為物件的執行個體。 
於 Microsoft.ReportDesigner.Design.GenericObjectDataSource.InitializeVB(ISchemaType schemaType) 於 Microsoft.ReportDesigner.Design.DesignUtil.AddTablesFromSchema(ISchemaType schemaType, Type parentType, String propertyName, IDictionary objectDataSources, IServiceProvider serviceProvider) 於 Microsoft.ReportDesigner.Design.DesignUtil.GetVBObjectDataSources(XmlSchema[] schemaDataSources, IServiceProvider serviceProvider) 於 Microsoft.ReportDesigner.Data.Local.DataSetSynchronizer.<GetProjectGenericObjectDataSources>d__11.MoveNext() 於 Microsoft.ReportDesigner.Data.Local.DataSetSynchronizer.<GetSynchableDataSources>d__7.MoveNext() 於 Microsoft.ReportDesigner.Data.Local.Dialogs.Pages.DataSetGeneralPage.LoadDataSources() 於 Microsoft.ReportDesigner.Data.Local.Dialogs.Pages.DataSetGeneralPage.LoadData() 於 Microsoft.ReportDesigner.Data.Local.Dialogs.DataSetDialog.OnLoad(EventArgs e) 於 System.Windows.Forms.Form.OnCreateControl() 於 System.Windows.Forms.Control.CreateControl(Boolean fIgnoreVisible) 於 System.Windows.Forms.Control.CreateControl() 於 System.Windows.Forms.Control.WmShowWindow(Message& m) 於 System.Windows.Forms.Control.WndProc(Message& m) 於 System.Windows.Forms.NativeWindow.Callback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)

簡單測試後發現新開 Project 或是現有其他 Project 去新增報表或修改現有報表都可以正常開啟資料集 GUI 畫面,只有該 Project 上會拋出該錯誤訊息,同事在版控上發現某個 commit 開始就有該現象,還剛好是我 commit 的,從該 commit change 沒有發現問題,完全沒有頭緒。

洗澡時突然想到,不會是報表更名造成的吧,查看資料夾內實際檔案發現,該報表使用的 DataSource 有兩個,實際檔案名稱雖然有差異,但各個 DataSource 內容只有檔案名稱大小寫差異,把更名前的 DataSource 刪除後,資料集 GUI 就完全恢復正常。
重覆測試後確定報表名稱更名,並不會直接影響 DataSource,也確認在報表內刪除 DataSource 並不會直接刪除實體 DataSource 檔案,直接刪除 Project 內的 DataSource 才會刪除實體 DataSource 檔案,猜測是該 DataSource 可能因為版控關係,該檔案不在 Project 內,又新增同名 DataSource 造成。

該 commit 是一個月前,期間也經歷三張報表開發,沒想到就突然異常。

星期二, 2月 11, 2025

[CCNA] IPv6 - 靜態路由

參考該篇 [CCNA] 靜態路由 內容,但 IP 設定改以 IPv6 為主

網路拓樸圖

IPv6 設定

設定前必須先執行下列語法,參考 Cisco ipv6 unicast-routing 說明
ipv6 unicast-routing
To enable the forwarding of IPv6 unicast datagrams, use the ipv6 unicast-routing command in global configuration mode. To disable the forwarding of IPv6 unicast datagrams, use the no form of this command.

顯示 ipv6 網路卡設定
Router1 依上述步驟進行設定,一定要開啟 ipv6 unicast-routing

設定靜態路由
Router1 也必須依上述語法設定往 Route0 的靜態路由,語法如下
ipv6 route 2001:1:1:10::/64 2001:1:1:100::1

Client 端設設定 IPv6
Ping 測試

星期一, 1月 13, 2025

GodexRT 730X - 特定區域空白

使用者回報告知,標籤輸出內容都不完整,而且是特定區域無法正常顯示而已,標籤如下

打開標籤機發現感熱部分竟然有一層膠,看起來是碳帶黏在熱感上,該位置剛好跟標籤紙無法顯示位置相同,用指甲把膠刮下來後,列印就會恢復正常


星期五, 1月 03, 2025

[CCNA] Port Security

使用 mac address 來確定該電腦是否能夠存取 switch port,該筆記內容是以只允許一個 mac address 連線來記錄

網路拓樸圖
Port Security 設定

設定 switch IP 方便進行測試
 

設定 fa0/1 為 Port Security 時,要先切換至 access 模式,要不然會拋出下圖錯誤訊息

Port Security 設定步驟
  • 設定 access 模式
  • 允許記錄一個 mac address
  • 使用 sticky 模式紀錄 mac address
  • violation 模式為 shutdown 為預設值,以該筆記來說即為紀錄第二個 mac address 時會把 Port 關閉

使用 show port security interface 來查看 Port Security 相關設定,以下圖來說,上方為設定完成後,還沒有任何連線狀態,下方為已記錄一個 mac address


show port-security 語法的 Last Source Address:Vlan 資料分別為紀錄的 mac address 和 vlan 號碼,從 PC0 截圖可以看到 mac address 是一致的

Port Security 測試

因為 switch 已經紀錄 PC0 mac address,當 Laptop0 ping siwtch 時,超過設定只允許記錄一個 mac address,該 Port 會立即 shutdown,如要在 CLI 內重啟該 Port,要先下 shutdown 後 no shutdown,直接下 no shutdown 是沒有作用的


show port-security 顯示已開啟 Port Security 的相關資訊

關閉 Port Security

使用 no switchport port-security 來關閉,關閉後必須重啟該 Port
關閉後兩台設備就同時可以通啦


確認 Port Security 已經關閉