How Do I第一篇,難度系數50,定位為入門級。
上一篇對Linq to SQL做了一個大致的介紹,從這一篇起,將對要完成一項Linq to SQL的Case要做 的一些事情,主要從細節上做一些講解。不會很深入,但是卻又是必須的。為了 使講解不落於泛泛而談,我們首先來構建實例:
還是以博客園的系統為 例子,既然稱Linq to SQL為一個ORM框架,ORM,對象-關系 映射,既然O在前關 系在後,說明O為重,關系是根據O得來的,那麼我們就先新建一些Entity Object吧。在一個博客系統裡最常見的就是User,Blog,Post。
一個用戶 有且僅有一個博客,而一個博客可以有零篇或者多篇博客文章。根據這個描述我 們來建立Entity:
Step 1 建立實體對象
User
/**////
/// 用戶類
///
public class User
{
/**////
/// 用戶標識
///
public int Id { get; set; }
/**////
/// 該 用戶對應的博客,
/// 一個用戶有且僅有一個博客
///
public Blog Blog { get; set; }
/**////
/// 用戶名
///
public string UserName { get; set; }
/**////
/// 密 碼
///
public string Password { get; set; }
/**////
/// 昵稱
///
public string NickName { get; set; }
/**////
/// 用戶離開時間
///
public DateTime LeaveTime { get; set; }
}
Blog
/**////
/// 博客類
///
public class Blog
{
/**////
/// 博客標識
///
public int Id { get; set; }
/**////
/// 用 戶標識,和用戶相關聯
///
public int UserId { get; set; }
/**////
/// 博客的中文名
///
public string Name { get; set; }
/**////
/// 創建時間
///
public DateTime CreateDate { get; set; }
/**////
/// 一個博客有零篇或多篇文章
///
public IList<Post> Posts { get; set; }
}
Code
/**////
/// 博客文章類
///
public class Post
{
/**////
/// 文章Id
///
public int Id { get; set; }
/**////
/// 文章從屬的博客
///
public int BlogId { get; set; }
/**////
/// 標題
///
public string Title { get; set; }
/**////
/// 內容
///
public string Body { get; set; }
/**////
/// 發表時 間
///
public DateTime CreateDate { get; set; }
}
我們先不要談論這個設計是否合理,也不要談論 這三個實體是貧血模型還是充血模型。
對象建立好了,我們來建設數據 庫表吧:users表,blogs表,posts表(為什麼表都用復數?從Rails裡學過來的, ActiveRecord模式裡,表裡每條記錄都對應著一個對象,所以表用復數表示,這 是一種約定)
Step 2:創建數據庫表
Users表:
Blogs表:
Posts表:
我們注意到,用戶類裡的LeaveTime離開時間在數據庫裡並沒有對應的 字段,因為這個是用來臨時記路用戶離開離開博客園的時間的,這樣可以用來做 一些在線統計,所以無需持久化。
Step 3:建立映射
對象也有了, 關系也有了,那剩下的是什麼?對,就是映射,我們怎樣將對象映射到數據庫表 上?
我們從最簡單的Post開始吧:
///
/// 博客文章類
/// 這個類和數據庫裡的posts表對應,由於
/// 類名和表名不一樣,所以需要顯式的指明Table特性的Name屬性
/// 如果是一致的則無需指明了
///
[Table(Name="posts")]
public class Post
{
///
/// 文章Id
/// 這個對應posts表裡的postid
/// 這個字段還是一個主鍵,所以Column特性的IsPrimaryKey屬性為true
///
[Column(Name="postid",IsPrimaryKey=true)]
public int Id { get; set; }
///
/// 文章從屬的博客
/// 這個對應著表裡的blogid,名字一樣所以只需要加個Column特性就可以了
/// 下面幾個也是一樣的意思
///
[Column]
public int BlogId { get; set; }
///
/// 標題
///
[Column]
public string Title { get; set; }
///
/// 內容
///
[Column]
public string Body { get; set; }
///
/// 發表時間
///
[Column]
public DateTime CreateDate { get; set; }
}
仔細看看注釋裡面的說明,很簡單吧,在類 上面加Table特性,屬性上加Column特性。
映射建完了,我們可以施加一些 操作了。
在上一篇文章裡介紹了,Linq to SQL的入口點是DataContext 類,這個類主要做這麼幾件事情:
將我們用C#寫的這個查詢翻譯成SQL語 句,當然也並不是他全權負責翻譯工作。
執行查詢
連接的管理, 這樣我們就不用寫啥Connection了啊,也不用擔心數據庫的連接和關閉的問題。
Step 4:執行查詢
那我們就首先實例化一個DataContext類吧(為 了好測試,使用一個控制台程序):
DataContext dbContext = new DataContext(ConfigurationManager.ConnectionStrings["CnBlogs"].ConnectionString);
從這裡可以看出, DataContext需要一個連接字符串,在DataContext裡,我們打交道最多的就是 GetTable()方法,這裡的TEntity就是我們上面的那個Post了,帶有映射的實體 ,這個方法返回一個Table對象:
Table<TEntity> posts = dbContext.GetTable<Post>();
實際上,Table實現了IEnumerable接口,那這裡實際上是返回了一個 IEnumerable系列,那我們可以對posts進行遍歷了:
foreach(var post in posts)
Console.WriteLine(post.Title);
實際上,如果光返回一個Table,而不在上面施加 任何的操作,遍歷的時候是返回整個表的數據,在上一篇我還提到,為了跟蹤, 我們最好將dbContext.Log給顯示出來。如果你添加了下面這行代碼:
dbContext.Log=Console.Out;
(對於這行代碼,如果你使用的是 控制台程序,那麼輸出就會顯示在命令窗口裡,如果你用的是WinForm程序調試 ,那麼輸出會顯示在VS的output窗口裡)
那你將會從控制台裡得到下面的東 東:
這就是Linq為你生成的SQL語句,是不在是比你寫的還標准啊,呵呵。
當然,我們常常做的肯定不是像上面這樣的全查詢,我們還要附加一些條件 的。
上一篇還提到過:由於Table實現了IEnumerable接口,那麼所有的 查詢表達式都可以在這裡使用了,這樣你先前在Linq to Objects裡學到東西現 在又有了新的用途了:
比如我們可以對BlogId做篩選:
var posts = from post in dbContext.GetTable<Post>()
where post.BlogId == 2
select post;
(PS :我在寫Linq語法的時候,因為Linq的風格太像SQL語句了,所以一些地方比如 ==我老寫成=,而&&經常寫成”and”,希望大家不要煩我這 樣的低級錯誤,呵呵)
看看這次生成的SQL語句:
可以看到生成的SQL語句對blogid做了篩選,而且值得慶祝的是,Linq to SQL為我們生成的SQL語句還使用的是參數,她並不是僅僅將數值和語句組合 在一起。關於使用參數而不是拼湊起來的SQL語句的好處在這裡啰嗦兩句,主要 有兩點:
這樣可以有效的SQL注入攻擊,這個hack手段曾經讓很多網站吃 盡了苦頭。
使用參數可以利用Sql Server對SQL語句的緩存和預編譯作用 ,因為使用參數的SQL語句可能多次用到,而拼湊起來的SQL語句卻和特定的查詢 有關。
這樣的篩選還不能體現出Linq的智能了,請再看這個:
var posts = from post in dbContext.GetTable<Post>()
where post.Title.StartsWith("y")
select post;
面對這樣的一個Linq查詢,Linq to SQL居然知道將它翻譯成LIKE子句 ,從上圖倒數第二行最後中括號裡面的東西可以看出,Linq to SQL還知道將傳 入的參數設置為y%,我不得不驚歎:太強大了。
但是不是所有的C#的方 法都可以使用呢?答案是否定的。比如:
var posts = from post in dbContext.GetTable<Post>()
where post.Title.StartsWith("y")
select post.CreateDate.ToString("yyyy-MM-dd");
我想對最後查出來的文章的創建時間做個格式化,居然報錯了,說不支持這種 “翻譯”。那看來每次我們做查詢的時候是要先try一下。
上 面只是做了篩選的實例,你還可以去嘗試一下排序和分組,這個和Linq to Objects裡的用法是一樣的,你可以到前面的文章裡復習一下。
Step 5: 插入對象
本系列的文章到現在為止都是在介紹查詢,難道Linq to SQL只 能做查詢麼?不能向數據庫添加數據?,肯定不是的:
Table<Post> posts = dbContext.GetTable<Post>();
//先實例化一個新的要插入的對象
Post post = new Post();
post.BlogId = 2;
post.Title = "test";
post.Body = "test,test,test,test";
post.CreateDate = DateTime.Now;
//調用Table的InsertOnSubmit方法
posts.InsertOnSubmit(post);
//把改變提交到數據庫,這個時候才真正執行了
dbContext.SubmitChanges();
//提交修改後,你就可以查詢新插入的post的Id了
Console.WriteLine(post.Id);
生成的SQL語句:
一個簡單的不能再簡單的insert into語句,加上一條返回新插入記錄 的標識值的語句。
如果你按照本文所說的一步步往下來,在執行上面的 插入的時候肯定會碰到異常,這是因為,Id對應的數據表字段postid是該表的主 鍵,你不應該在插入的時候賦值,有人說我實例化對象的時候確實沒有給post的 Id屬性賦值啊,但是.net會在後台為我們將Id賦值為0,所以你要對映射對象做 一下修改:
[Column(Name="postid",IsPrimaryKey=true,IsDbGenerated=true)]
public int Id { get; set; }
不僅僅數據表的主鍵要加這個屬性, 如果對於那些在數據庫裡設置了默認值的,你並不像用程序插入的時候,你也得 使用,比如這裡的CreateDate。
Step 6:更新數據
可以插入肯定 就可以更新了,下面就來看看如何更新呢:
做更新的時候,你首先得從 數據庫查詢出該對象,然後對該對象的屬性進行修改,最後更新到數據庫:
var posts = from post in dbContext.GetTable<Post>()
where post.BlogId == 2
select post;
foreach (var post in posts)
post.BlogId = 5;
dbContext.SubmitChanges();
Linq to SQL也會自動的幫你生成Update語句了。
有 了插入,更新,查詢就差一個Delete CURD就全了,對於delete更簡單了,你只 要調用Table的DeleteOnSubmit方法就行了,這裡就不再詳述。
後記
這篇文章主要關注How Do I上面,對一個簡單的單表CURD做一個比較全 面的介紹,下一篇會更深入一些,將會涉及到多表的連接,兩個表之間的關系怎 樣反應到映射上來,還將探討一下Linq to SQL中的延遲計算的問題。本篇定位 為入門級,只期望給一些初學者或者未接觸過Linq的朋友一些提示。