一、引言
在使用ORM框架時,一個表有一個主鍵是必須的,如果沒有主鍵,就沒有辦法來唯一的更新一條記錄。在Sql Server數據庫和Mysql數據庫設置自增長的主鍵是一件很輕松的事情,如果在Oracle數據庫中設置自增長的主鍵是比較繁瑣的。本文不討論數據庫里單表的自增長問題,探討的是多表自增長唯一Id的設計。
如果各位看官遇到這個多表自增長唯一Id的這個需求,會怎么處理呢?
二、GUID的介紹
關於自增長主鍵的問題,有些人可能會想到.Net中的GUID,先對這個GUID進行測試。

public void GuidTest() { string guid = Guid.NewGuid().ToString(); Console.WriteLine(guid); Console.WriteLine(guid.Length); }
下面是輸出結果:
c8eb1c81-eafa-423b-a1bf-15fd5df829c4
36
可以看到這個GUID是一個字符串,長度36位,還真夠長的,估計在用的時候,如果想減少位數,可以把橫杠-去掉,少了4位,還剩32位,不過還是挺長的。
以我的觀察來看,現在如果誰把數據庫的主鍵設計成這個GUID,還真的是個二貨:)
原因有三,1)太長了,在數據庫里占空間
2)排序不方便
3)檢索時字符串的效率不如整數
本來對GUID只想一筆而過,看來寫得太多了。
三、單機版自增長Id實現
針對多表下的自增長的唯一的ID,首先想到的就是時間,把這個時間格式化成整形的數字,就可以解決問題。為了減少位數,把日期的年的部分20去掉,毫秒數取兩位,共15位。
為了防止重復,程序每次生成,如果當前生成的Id大於當前時間下的Id,會保存在在xml里,下次程序啟動,會檢查xml里保存的Id和當前時間下的Id,兩者取其大。這樣做是為了,防止程序重啟,生成的Id重復。當然,為了多線程的安全,順手lock。代碼如下:

public class IdGenerator { private static string idXml = "id.xml"; private static long current = 0; private static object obj = new object(); private static string format = "yyMMddHHmmssfff"; //15位 static IdGenerator() { var xDoc = XDocument.Load(idXml); long last = 0; long.TryParse(xDoc.Root.Value, out last); //每次啟動檢查最后生成的Id是否大於當前時間下的Id long now = long.Parse(DateTime.Now.ToString(format)); if (last > now) { current = last; } else { current = now; } } public static long GetId() { lock (obj) { current += 1; //如果當前的生成的Id大於當前時間下的Id,就要保存下來 if (current > long.Parse(DateTime.Now.ToString(format))) { var xDoc = XDocument.Load(idXml); xDoc.Root.Value = current.ToString(); xDoc.Save(idXml); } return current; } } }
四、分布式下的自增長Id實現
在現實環境下,項目中可能有多個網站或多個應用程序使用這個自增長的Id,這樣就要考慮分布式的情況,這里實現分布式使用WCF技術,WCF實現在本地和服務器通過契約來通信,抽象之后,調用服務器代碼就像調用本地代碼。
首先定義契約:

namespace WCFServiceTest { [ServiceContract] public interface IIdGenContract { [OperationContract] long GetIdByWCF(); } }
該接口里有一個方法GetIdByWCF,這個方法需要服務端來實現。

namespace WCFServiceTest { [ServiceBehavior(IncludeExceptionDetailInFaults = true, InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Multiple, MaxItemsInObjectGraph = Int32.MaxValue)] public class IdGenService : IIdGenContract { public static IdGenService Instance = new IdGenService(); public long GetIdByWCF() { return IdGenerateHelper.IdGenerator.GetId(); } } }
下一步就是開啟這個服務

namespace WCFServiceTest { class Program { static void Main(string[] args) { ServiceHost host = new ServiceHost(IdGenService.Instance); host.Open(); Console.WriteLine(typeof(IdGenService) + " Opened"); Console.WriteLine("服務地址:" + host.Description.Endpoints[0].ListenUri); Console.Read(); } } }
這里面需要配置文件,如果是網站就是web.config,桌面程序就是app.config,配置文件的詳細會在下面的demo代碼里。
服務端完成了,下面就是編寫客戶端的代碼來調用該服務了。客戶端通過契約(接口)來和服務端進行通信,也要實現這個接口。

namespace ConsoleApplication2 { public class IdService : ClientBase<IIdGenContract>, IIdGenContract { public long GetIdByWCF() { return base.Channel.GetIdByWCF(); } } }
接下來就是測試代碼了

namespace ConsoleApplication2 { /// <summary> /// 通過調用WCF服務的方式生成自增長Id /// </summary> class Program { static void Main(string[] args) { long id = new ClientIdService().GetIdByWCF(); Console.WriteLine(id); Console.Read(); } } }
四、結束
一切按計划執行,輸出一個唯一的Id,本文完整Demo下載。