0. 說明
Linq to Sql,以下簡稱L2S。
以下文中所指的兩層和三層結構,分別如下圖所示:
準確的說,這里的分層并不是特別明確:
(1) 生成的DataContext(Linq t0 SQL Runtime)和Entity是放在一個文件中的,物理上不能切割開來;上圖只是展示邏輯上的結構。
(2) 拿上圖右邊的三層結構來說,鑒于第(1)點,UI層就可以跨越BusinessLogic層,直接訪問L2S層,這可能不是我們期望的。對于這個問題,可以有如下兩種辦法:A.建立自己的Entity層(DomainModel,UI與BL之間使用DomainModel,BL將DomainModel轉換成L2S的Entity后,再交給L2S Runtime進行數據庫更新); B.告訴UI層開發人員不要訪問L2S Runtime-,-
1. 生成DataContext和Entity
1.1 使用VS自帶的L2S對象關系設計器(O/R設計器)
如果程序很簡單只是拿來玩兒的,就少數幾個表,可以直接使用此法,將表/視圖/存儲過程從數據源中拖拽到設計器中即可。但如果系統中的表很多,拖著拖著就把自己給拖暈了……
1.2 使用SqlMeta工具
用法見MSDN。
sqlmetal /conn:"Data Source=server;Initial Catalog=database;Persist Security Info=True;User ID=sa;Password=password" /namespace:DataEntity /code:_Entity.cs /language:csharp /context:EntityDataContext /serialization:Unidirectional /views
1.3 手工建立實體定義或者XML映射
太多活要干,如果沒有特殊場景必須手工Code這堆東西的話,建議還是省省……
具體可以參考MSDN:
使用代碼編輯器自定義實體類 (LINQ to SQL)
2. 兩層應用下的CUD操作
兩層應用下的較為簡單,MSDN中有例子,借花獻佛:
2.1 新增
1: Order ord = new Order
2: { 3: OrderID = 12000,4: ShipCity = "Seattle",
5: OrderDate = DateTime.Now6: // …
7: }; 8: db.Orders.InsertOnSubmit(ord); 9: db.SubmitChanges();2.2 更新
1: //1.查詢需要修改的紀錄[Lazy Load]
2: IQueryable<Order> query =3: from ord in db.Orders
4: where ord.OrderID == 11000
5: select ord;6: //2.修改
7: foreach (Order ord in query)
8: {9: ord.ShipName = "Mariner";
10: ord.ShipVia = 2;11: // Insert any additional changes to column values.
12: }13: //3.保存
14: db.SubmitChanges();2.3 刪除
1: //1.查詢需要修改的紀錄[Lazy Load]
2: IQueryable<Order> query =3: from ord in db.Orders
4: where ord.OrderID == 11000
5: select ord;6: //2.修改
7: foreach (Order ord in query)
8: { 9: db.Orders.DeleteOnSubmit(ord); 10: }11: //3.提交更改
12: db.SubmitChanges();
3. 多層應用下的CUD操作
新增操作同上。
但修改和刪除操作則需要做調整。在文章起始位置的分層結構圖中可以看到,層之間是以Entity作為數據傳輸對象。由于Entity的狀態是有DataContext進行監控的,脫離了DataContext,則L2S無法知道對象是否做了更新、做了什么更新。譬如我的BusinessLogic被封裝在一個WCF應用中,在將Entity通過網絡序列化到客戶端時,這些Entity會與其DataContext分離;當Entity再從客戶端傳回到服務器端時,服務器端使用的是一個新的DataContext,繼續用上面的方法來操作的話,執行時會報錯。
關于對象狀態及跟蹤,可以參考MSDN:對象狀態與更改跟蹤 (LINQ to SQL)
一種解決辦法是:將上層傳過來的Entity重新Attach到新的DataContext中。
3.1 新增
新增不用Attach,直接按照2.1中同樣的操作方式即可。(同2.1)
3.2 刪除
1: public void DeleteOrder(Order order)
2: {3: NorthwindClasses1DataContext db = new NorthwindClasses1DataContext(connectionString);
4: 5: db.Orders.Attach(order, false); //重新Attach一次
6: db.Orders.DeleteOnSubmit(order);7: try
8: { 9: db.SubmitChanges(ConflictMode.ContinueOnConflict); 10: }11: catch (ChangeConflictException e)
12: {13: //處理更改沖突
14: } 15: }3.3 修改
Attach可以解決刪除問題,但并不能解決所有的更新問題。因為在L2S中,需要支持開放式并發檢查。L2S支持以下三種開放式并發方案中的更新:(有關開放式并發的更多信息,請參見MSDN:開放式并發概述 (LINQ to SQL))
(1).基于時間戳或 RowVersion 號的開放式并發。
(2).基于實體屬性子集的原始值的開放式并發。
(3).基于完整原始實體和已修改實體的開放式并發。
通俗的講,就是如下根據如下三中處理并發的方式:
(1). 數據庫表結構定義中包含時間戳(timestamp)字段:
由于時間戳的值惟一,每次更新記錄時其值會同時進行更新,因此只用根據主鍵+時間戳,就能判斷記錄是否被其他人更新過;如果沒有被更新過,則可以進行更新;如果被更新過,則引發ChangeConflictException異常。
時間戳字段對應的Entity定義中屬性的Attribute會長成這個樣子:[Column(Storage="_TimestampFieldName", AutoSync=AutoSync.Always, DbType="rowversion NOT NULL", CanBeNull=false, IsDbGenerated=true, IsVersion=true, UpdateCheck=UpdateCheck.Never)]。
此時可以按照3.1類似的方式來Attach修改后的Entity,并提交更改到數據庫:(可以用Sql Server的Profile來查看生成的TSQL)
1: public void UpdateOrder(Order order)
2: {3: NorthwindClasses1DataContext db = new NorthwindClasses1DataContext(connectionString);
4: 5: db.Orders.Attach(order, true);
6: try
7: { 8: db.SubmitChanges(); 9: }10: catch (ChangeConflictException e)
11: {12: //處理更改沖突
13: } 14: }
(2). 如果數據庫表結構定義中不包含時間戳(timestamp)字段:
如果繼續用上面(1)的方式來更新,編譯時不會報錯,但執行時會報錯。
此時L2S不知道該咋更新了,于是就需要人為地告訴L2S該更新啥,具體包含如下兩種方式
(a). 提供原始對象及更新后的屬性值
1: public void UpdateOrder(Order o, short? newValue1, short? newValue2)
2: {3: using (NorthwindClasses1DataContext db = new NorthwindClasses1DataContext(connectionString))
4: {5: db.Orders.Attach(o, false);//o必須是原始的沒有更改過的實體對象
6: 7: o.XXOO = newValue1; 8: o.OOXX = newValue2;9: try
10: { 11: db.SubmitChanges(); 12: }13: catch (ChangeConflictException e)
14: {15: // 處理更改沖突
16: } 17: } 18: }(b). 提供原始對象及更新后的對象
1: public void UpdateOrder(Order originalOrder, Order newOrder)
2: {3: using (NorthwindClasses1DataContext db = new NorthwindClasses1DataContext(connectionString))
4: {5: db.Orders.Attach(newOrder, originalOrder);//originalOrder必須是原始的沒有更改過的實體對象
6: 7: try
8: { 9: db.SubmitChanges(); 10: }11: catch (ChangeConflictException e)
12: {13: // 處理更改沖突
14: } 15: } 16: }第一種方式只需要對表增加TimeStamp類型的字段;而后面兩種更新方式都需要一些額外的代碼來進行處理;相比之下,還是第一種方式相對省力點兒。
如果表比較多,逐個表增加TimeStamp字段就比較討人厭了,于是有了下面的SQL,來為所有的表增加時間戳列:
1: DECLARE @Table Table(ID int identity(1,1), TableName nvarchar(100));
2: DECLARE @Index int, @TotalCount int;
3: DECLARE @TableName nvarchar(100);
4: 5: INSERT INTO @Table(TableName)6: SELECT TOP 100 PERCENT T.name FROM dbo.sysobjects T WHERE (T.xtype = 'u');
7: SET @TotalCount = @@RowCount;
8: 9: SET @Index = 1;
10: WHILE(@Index <= @TotalCount)
11: BEGIN12: SELECT @TableName = TableName FROM @Table WHERE ID=@Index;
13: 14: IF(NOT Exists(SELECT 1 FROM syscolumns WHERE id = object_id(@TableName) AND number = 0
15: AND type_name(xusertype) = 'timestamp'))
16: BEGIN17: EXEC('ALTER TABLE ' + @TableName + ' ADD [Timestamp] timestamp')
18: END
19: 20: SET @Index = @Index + 1;
21: END尤其需要注意的是:
在嘗試進行更新之前,不要將從數據庫中檢索數據作為一種獲取原始值的方式。
4. 批量更新、批量刪除
然而,上面的處理方式只是處理的最基本的CUD操作,而實際應用中的業務邏輯不會這么簡單。如果涉及到稍為復雜點兒的應用,譬如我要根據一個外部傳進來的條件,執行批量修改、批量刪除,該咋處理呢?
關于批量刪除和批量更新,老趙曾提出過自己的方案:擴展LINQ to SQL:使用Lambda Expression批量刪除數據,其思路就是實現一個Expression<Func<T, bool>>解析器,并將Expression解析為最終需要執行的TSQL。在老趙的同篇英文博客中,有人在回復中提出了另一個思路,對L2S生成的TSQL進行包裝,將其作為Update/Delete語句中的子查詢,這樣就可以直接復用L2S的查詢解析器,使用L2S解析器提供的全部功能了;接下來有人將這個思路進行了實現:Batch Updates and Deletes with LINQ to SQL。原文作者寫的非常詳細了,并提供了源代碼,可以下載回來學習或者直接復用。
1: string productName = new StringBuilder("test").Append("productName").ToString();
2: int productID = new Random().Next(10000000, Int32.MaxValue);
3: Expression<Func<Products, bool>> predicate = p => p.ProductName.Contains(productName);
4: Expression<Func<Products, Products>> evaluator = 5: p => new Products() { UnitPrice = p.UnitPrice + 1, ProductName = "Test" };
6: 7: UpdateBatch(predicate, evaluator); //批量更新
8: DeleteBatch(predicate); //批量刪除
9: MultiSelect(); //批量查詢
10: DeleteByPK(productID); //按主鍵刪除
11: 12: public void UpdateBatch(Expression<Func<Products, bool>> predicate,
13: Expression<Func<Products, Products>> evaluator) 14: {15: using (NorthWindDataContext context = new NorthWindDataContext())
16: { 17: context.Products.UpdateBatch(predicate, evaluator); 18: } 19: } 20: 21: public void DeleteBatch(Expression<Func<Products, bool>> predicate)
22: {23: using (NorthWindDataContext context = new NorthWindDataContext())
24: { 25: context.Products.DeleteBatch(predicate); 26: } 27: } 28: 29: public void MultiSelect()
30: {31: using (NorthWindDataContext context = new NorthWindDataContext())
32: {33: var query1 = (from P in context.Products select P).Take(2);
34: var query2 = (from S in context.Suppliers select S).Take(2);
35: IMultipleResults result = context.SelectMutlipleResults(query1, query2); 36: 37: List<Products> products = result.GetResult<Products>().ToList(); 38: List<Suppliers> suppliers = result.GetResult<Suppliers>().ToList(); 39: ObjectDumper.Write(products); 40: ObjectDumper.Write(suppliers); 41: } 42: } 43: 44: public void DeleteByPK(int productID)
45: {46: using (NorthWindDataContext context = new NorthWindDataContext())
47: { 48: context.Products.DeleteByPK(productID); 49: } 50: }具體生成的SQL,可以用SQL Server Profile來查看。
參考《MSDN》:
1.做出和提交數據更改 (LINQ to SQL)
2.N 層應用程序中的數據檢索和 CUD 操作 (LINQ to SQL)
3.開放式并發概述 (LINQ to SQL)
4.對象狀態與更改跟蹤 (LINQ to SQL)
浙公網安備 33010602011771號