Pages

2015-07-29

軟件授權序號發行

以前覺得軟件授權用序號方式很容易被破解,因此就實作硬體ID+網路啟動的方式,但站在User的角度其實輸入註冊序號是比較簡單的,至少不需要連網。一般簡單註冊序號的實作方式,就是將用戶資訊+到期時間加密寫成二進位檔。

加密內容方式應該能隨DateTime變化而內容不同,以防止被推算竄改,加密方式可以加上隨機byte陣列Salt(鹽)混淆即可。

License KeyGen

關於License授權物件的寫作,多數人大概會優先使用.NET二進化序列化技術,不過我不喜歡這類帶有強型別的序列化行為(因為序列化的前面Bytes都是描述類別不會變化,很好推算加密私鑰),最主要也因為別的程式語言不容易針對檔案作反序列化。

因此我使用「Packet封包結構與解析」文件的格式來包裝License物件,因為它有很好的擴充性,也是C/C++很好理解處理的模式。

當然,.NET軟件上的檢查註冊機制,很容易被反組譯給註解掉IL執行指令,這部分可以在程式裏加上很多空的函式來混淆別人的分析,授權檢查不要只回傳Boolean值就了事,而是在多處關鍵處去授權物件內的值作簡易計算比對,即使發現授權檢查被竄改也不要立即彈視窗警告,使用一個%機率出現怪現象即可。

最後,防守到一個程度被破解就算了,千萬不要為了授權檢查而混淆程式執行效能,做出本末倒置的事才好。畢竟,軟體上的授權檢查,都是防君子不防小人的。

2015-07-22

C# General Tree結構

.NET Framework有Stack, Queue, List, Directory等資料結構,但很少看到Tree,主要是Tree樹的形成各有其用途,很難針對專一用途實作比較通用的Library物件。在一般AP程式裏,Tree結構是代表資料階層性,往往需要在DB->UI裏使用Recursive遞迴函式去形成Tree控件結構。

為了增加轉換效率及判斷整個Tree節點的屬性(如是否有子點),我會在DB與UI控件間使用到Tree結構的物件。以前作法是直接拿UI TreeView控件來當記錄物件,但該TreeView難免比較佔記憶體,因此就興起自己寫作一個很單純的Tree資料結構的念頭。

以Tree資料物件來說,只需要TreeNode<T>及其父子點TreeNodeCollection<T>兩個類別,即可形成一個完整的Tree結構。對任一個TreeNode節點來說,要有父節點及上下的群點,以利整顆樹的節點搜尋。

Tree Structure

Tree CodeSnippet

研究過程裏,發現TreeNode節點的Depth深度值的演算法挺特別的,把它節錄出來。

public int Depth
{
get
{
// ------------------------------
// Recursive algorithm.
// ------------------------------
return (this.ParentNode == null ? -1 : this.ParentNode.Depth) + 1;

// ------------------------------
// General algorithm
// ------------------------------
//int depth = 0;
//TreeNode node = this;
//while (node.ParentNode != null)
//{
// node = node.ParentNode;
// depth++;
//}

//return depth;
}
}

2015-07-18

阿里巴巴1688的收貨地址

阿里巴巴1688網站相較淘寶而言,是屬於批發大量的購物站,其價格都更有競爭力。它的「收貨地址」維護頁面,在登入後台很不容易找到,因此作下備忘筆記。

1688 Address

2015-07-15

EF的多筆批次修改及刪除

EF v6.1.3對於執行多筆批次修改的SQL命令(UPDATE Table1 SET ItemType=2 WHERE ItemId > 5),還是得一筆一筆執行,網上很多套Update Extensions可供使用,比較有名的就是「EntityFramework.Extended」:

PM> Install-Package EntityFramework.Extended

在多筆修改部分,它使用INNER JOIN方式來綁定ID修改欄位,雖然不及原生SQL語法簡潔,但還能接受,而且它能強制更新指定欄位,比起用EF內建的Attach(entity)方式更方便,強制更新指定欄位,不需再注意Old/New Values差異可能造成的BUG地雷,。
using EntityFramework.Extensions;

var listId = new List { 1, 2, 3 };
dbContext.Entries.Where(x => listId.Contains(oldEntry.LogId)).Update(
expr => new CMS_Log {
LogType = "123",
LogLevel = 0
}
);
dbContext.SaveChanges();

SQL Update


在多筆刪除部分,EF v6已支援內建RemoveRange(entities)來多筆刪除,也可以使用外掛的Extension Update()更是簡潔。

using EntityFramework.Extensions;

dbContext.Entries
.Where(x => x.LogId > 15 && x.LogId < 20)
.Delete();
dbContext.SaveChanges();

SQL Delete


外掛Extension函式產生的SQL都有點冗長,但在不使用原生SQL Text下,就將就用吧! 總比起EF得下N筆SQL指令的方式更具高效率了。此外,EF BulkInsert大量新增操作的效能提升,又是另一話題了。

2015-07-08

WebForm更新MasterPage的推薦方式

ASP.NET下的MasterPage一般用來作前端UI框架的,真正的ContentPage才是主要寫該頁程序的地方。這兩種Page在CodeBehind的方式,長得都一樣,因此初學者很容易在各自的頁面CS裏,寫獨立的DB連線及程序(Page_Load),這樣的作法除了多浪費一倍頁面物件所需建立的時間及效能損耗下,Master與Content也不容易作互動。

頁面的執行Trigger都是決定在ContentPage裏,因此要改MasterPage上的控制項方式為:

if (this.Master != null)
{
Literal menu = this.Master.FindControl("MyControldId") as Literal;
if (menu != null)
{
menu.Text = "ABC";
}
}

這些控制項本身就在MasterPage有始初化操作,又被刷值了一次,N個控制項得FindControl()集合N次,十分冗雜又效能差的。其實MasterPage.cs裏不要去連資料庫,只需要寫個更新UI的Method函式即可:

public void Menu_UpdateText(string text, Entity items)
{
this.lblPageTitle.Text = "MasterTitle";
this.ListView1.DataSource = items;
this.ListView1.DataBind();
}
在ContentPage.cs裏,使用以下方式呼叫MasterPage內的函式進行刷新:
if (this.Master != null)
{
var masterPage = this.Master as MasterPageType;;
if (masterPage != null)
{
masterPage.Menu_UpdateText("ABC", dbItems);;
}
}
如此方式,即可既簡單又高效率地在MasterPage與ContentPage間繫結資料項了,一切由ContentPage來控制。

2015-07-04

EF的異動偵測DetectChanges機制

EF執行資料庫Update指令時,由於在UI上都會挾帶PK ItemId, 因此我們常用Attach(entity)方式來取代得多下一道Select指令的問題:

// 一般作法,得多一道DB Select指令
var entity = dbContext.Entities.Single(x => x.ItemId = 1);
entity.ItemText = "abc";
entity.ItemKey = 0;
dbContext.SaveChanges();

// Attach方式,不會下一道DB Select指令
EntityType entity = new EntityType();
entity.ItemId = 1;
dbContext.Entities.Attach(entity); // EntityState = Unchanged

entity.ItemText = "abc";
entity.ItemKey = 0;

//dbContext.Entry(entity).State = EntityState.Modified; // 此行不需寫,因為它會讓DB修改所有欄位值,而非指定的欄位,這也是一個隱藏Bug地雷
dbContext.Configuration.ValidateOnSaveEnabled = false; // 因為Entity有些欄位必填,若不避開會有Validate錯誤
//dbContext.ChangeTracker.DetectChanges(); // 不需撰寫此行,因為dbContext.Configuration.AutoDetectChangesEnabled = true (Default)會自動呼叫
dbContext.SaveChanges();

首先令人覺得好奇的是,只是修改一個很單純POCO Entity的屬性值,為何EF會知道異動了? 原來當你Attach(entity)後,EF透過DbSet宣告時virtual關鍵字,暗地把原本的POCO類別ref位址指向內部DynamicProxy類別,這Proxy為每一個屬性Property作了延伸處理,讓內部ChangingTracker記錄了每個屬性的Old, New值及IsModified狀態。可查看一下EF PropertyEntry源碼宣告,就知大概的追蹤異動處理手法。


因此,當一個Property改值時,它會比對新舊值,一旦不一樣就會把EntityState狀態由Unchanged變為Modified,並在SaveChanges()之前自動呼叫DetectChanges()來搜集每個Properties的新舊值變動狀況。在這樣的原則下,上述用Attach(entity)省下DB Select指令的寫法,就藏著一個可怕的Bug,原來只要Property的值沒有異動到,DB指令裏該欄位就不會修改到,這點是筆者尚未明白EF偵測異動原理前遇到的血淚地雷經驗。

// 調整如下,才能確保要修改的欄位值會被DB更新
EntityType entity = new EntityType();
entity.ItemId = 1;
entity.ItemKey = -1; // Entity int類型屬性,預設是0,所以要指定不可能的值造成異動
dbContext.Entities.Attach(entity); // EntityState = Unchanged

entity.ItemText = "abc"; // 字串通常不需要在Attach()前先調值,因為string預設是null,而UI取值通常是String.Empty
entity.ItemKey = 0; // 若事先沒調值,當有UI把某值改為0時,DB執行時並不會修改到此欄位,造成Bug

dbContext.Entry(entity).Property(x => x.ItemKey).IsModified = true; // 可以直接使用這方式強制某欄位要更新,只是查詢集合耗效能而己

dbContext.Configuration.ValidateOnSaveEnabled = false; // 因為Entity有些欄位必填,若不避開會有Validate錯誤
dbContext.SaveChanges();

由於EF預設下都會自動偵測異動(dbContext.Configuration.AutoDetectChangesEnabled=true),尤其在以下操作時都會自動呼叫DetectChanges()比對所有的entry集合的每一個屬性Properties的新舊值,若在大量操作時難免效能不佳,因此特殊情況時,會讓dbContext.Configuration.AutoDetectChangesEnabled=fase,完成複雜的操作,才只作一道dbContext.ChangeTracker.DetectChanges()比對異動。



  • DbSet.Find
  • DbSet.Local
  • DbSet.Remove
  • DbSet.Add
  • DbSet.Attach
  • DbContext.SaveChanges
  • DbContext.GetValidationErrors
  • DbContext.Entry
  • DbChangeTracker.Entries

EntityState是EF執行DB指令重要的判斷依據,不了解底層原理就直接使用Attach(entity),其實隱藏了很多可能Bug錯誤。不過,或許大多數EF使用者不在乎修改資料時多下一道DB Select指令,也就不會遇到這樣的問題了。EF內部行為已為大多數DB操作行為作了合理推斷,不過每個開發者追求效能的心態層次不一樣,想自由穿梭其間,就得多閱讀理論基礎就是了。