非常抱歉,由於需要發表其他文章的緣故,我只能忍著不修正文中一小部分錯誤,以及增加一些有助免於誤解的內容。這裡特別說明一下,本文不是要討論緩存機制的好壞,更不是要討論如何緩存對象。而是說DAL/BLL上面對DataContext的處理。另外一個需要注意的地方,是修改了一個錯誤,原來大部分都寫成IQueryable了,實際上應該是除了最後一個之外,都是IEnumerable。原因是什麼需要大家想一下。此外,也需要大家注意的事,我所提出來的緩存,並不是直接利用Linq2Sql的代碼來緩存,而是指是否便於緩存。這部分的內容,會在文章後面補充說明。
Linq to Sql 用的人也應該有些吧,我在cnblogs上面看老趙寫的那幾篇文章(請看08年9月左右的文章),感覺也很有深度,有不少啟發。因此我也打算寫一點我自己的實踐經驗,希望也能同樣給大家一些有用的啟發吧。
我首先想要問一下大家,Linq to Sql有哪些很特別的地方?這個問題的答案肯定五花八門,我說一下我看到的一些問題吧。
首先,Linq to Sql的基礎之一是DataContext,而另外一個基礎,則是通過映射產生的實體類,以及這些實體類的Table<>對象。這個不是廢話嘛!我想很多人都應該知道這個最基本的知識,不過卻不見得有多少人真正注意到,或者認真思考一下這裡面的“機關”。不知道“機關”在哪裡,那麼就不可能寫出合適的代碼。比如說,在某個頁面裡面(N層結構沒有給弄好的情況下),或者在某個業務邏輯裡面(有N層結構),你的Linq to Sql的代碼是否是長這樣的?
using (MyDataContext db = new MyDataContext)
{
var q = from product in db.ProductInfos
where product.Price > 100
select product;
DoSomethingWithProducts(q.ToList());
}
“對啊,就是長這樣的,有什麼問題嗎?”當然有問題啦,否則我也不寫這個隨筆了。不知道大家有沒有想過這麼一個問題,什麼叫做Context?Context就是上下文,上下文的意思就是,依賴於這個上下文的對象,必須存活在這個上下文裡面。脫離了這個上下文,那些對象就會出現錯誤。事實上也確實如此:在上面的例子裡面,從ProductInfos中得到的q.ToList(),裡面的每一個元素都依賴於MyDataContext。換句話說MyDataContext如果被注銷了,q.ToList()生成的對象也就會“部分功能失效”。
“失效就失效好了,反正該做的工作已經做完了,q.ToList()也已經利用完了。”不錯,在上面的例子裡面,不會發生什麼錯誤。不過這麼寫的話,會比較難使用的。為什麼這麼說?我舉一個具體的例子:這個網站需要用戶登錄,而所有的業務邏輯幾乎都依賴於當前用戶。如果說,我們使用上面的using模式,那麼我估計你的代碼不外乎是如下兩種情況:
1、每一次需要當前用戶的地方,你都需要從數據庫讀取;或者
2、你把當前用戶保存為全局變量了,但是你發現currentUser.CompanyInfo因為上下文已經拋棄了,因此是無法使用的,業務層不得不每一次都重新從數據庫讀取該用戶所屬公司的數據。
這兩種形式如下所示:
// 通過實體對象來存儲
// 注意!這個函數是不在BLL層的,而是更上層的某些處理,為了演示起來簡單,顯得是BLL層的東西。
public double GetCurrentBalanceByObject()
{
int userId;
int.TryParse(HttpContext.Current.User.Identity, out userId);
UserInfo user = GetUser(userId);
CompanyInfo company = GetCompanyByUser(user);
IEnumerable<TransactionInfo> transactions = GetTransactionsByCompany(company);
return transactions.Sum(item => item.Amount);
}
// 後面這幾個方法才是BLL層的東西,後面的例子也相同。
public UserInfo GetUser(int userId)
{
using(MyDataContext context = new MyDataContext)
{
return context.UserInfos.Where(item => item.UserId == userId).FirstOrDefault();
}
}
public CompanyInfo GetCompanyByUser(UserInfo user)
{
using(MyDataContext context = new MyDataContext)
{
return context.CompanyInfos.Where(item => item.UserId == user.Id).FirstOrDefault();
}
}
public IEnumerable<Transaction> GetTransactionsByCompany(CompanyInfo company)
{
using(MyDataContext context = new MyDataContext)
{
return context.TransactionInfos.Where(item => item.CompnayId == company.Id).ToList;
}
}
// 實際上很容易就退化為通過鍵值來存儲,因為在這種設計方式下面,
// 實際上根本沒有什麼必要去傳輸整個對象。
// 我們可以想象,這個時候很多的操作其實是依賴UserId和CompanyId的,
// 而我見過的“有趣”設計,是在Page_Load事件中,不管是否需要用到,
// 都會將HttpContext.Current.User.Identity以及
// GetCompanyByUserId(userId).CompanyId保存為當前頁面的全局變量。
// 其實這樣是違背了Linq的設計初衷的。
// 當然,我不是說不緩存最經常用的東西,該緩存還是要緩存的,只是,
// 下面的設計只會把問題變復雜。因為這裡只能緩存一個id值,內部還是要重復獲取User或者Company對象的。
// 下面就是一個只傳Id的做法:
public double GetCurrentBalanceByObject()
{
int userId;
int.TryParse(HttpContext.Current.User.Identity, out userId);
CompanyInfo company = GetCompanyByUserId(userId);
IEnumerable<TransactionInfo> transactions = GetTransactionsByCompanyId(company.CompanyId);
return transactions.Sum(item => item.Amount);
}
public CompanyInfo GetCompanyByUser(int userId)
{
using(MyDataContext context = new MyDataContext)
{
return context.CompanyInfos.Where(item => item.UserId == userId).FirstOrDefault();
}
}
public IEnumerable<Transaction> GetTransactionsByCompanyId(int companyId)
{
using(MyDataContext context = new MyDataContext)
{
return context.TransactionInfos.Where(item => item.CompnayId == companyId).ToList();
}
}