程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> LINQ那些事兒(2)- 簡單對象的CRUD操作和Association的級聯操作

LINQ那些事兒(2)- 簡單對象的CRUD操作和Association的級聯操作

編輯:關於.NET

從(1)我們看到,當生成entity class定義時,entity class或xml mapping文件中都已經完整的包含了entity和關系數據庫的映射信息了,LINQ2SQL會根據這些信息來把CRUD操作轉化為SQL提交給數據庫,並且把數據庫的返回DataTable封裝成我們想要的對象。

所謂簡單對象,就是數據表定義中沒有Foreign-key的entity class,在操作這類對象時不會涉及級聯的操作。

簡單對象的CRUD操作,可參考MSDN:http://msdn.microsoft.com/zh-cn/library/bb399349.aspx

有一點很方便,在插入數據時,LINQ2SQL不但生成了Insert的SQL語句,而且還生成語句把ColumnAttribute標記 IsDbGenerated=true的數據列取回。這點當我們用數據庫生成的uniqueidentifier列或自增id做主鍵時尤其方便。

下面讓我們來一起討論級聯操作以及相關問題,為了方便示例我定義了兩張數據表Publishers和Books:

其中PublisherID和BookID都是RowGuid,而且默認值為newid(),以下代碼都是基於SqlMetal生成的xml mapping和entity class。

添加

下面代碼示例了在添加Publisher記錄時,同時添加兩個關聯Book記錄

1 var context = GenerateContext();
2 Publisher publisher = new Publisher { Name = "Microsoft" };
3 publisher.Books.Add(new Book { Title = "Expert F#" });
4 publisher.Books.Add(new Book { Title = "Beautiful code" });
5
6 context.Publishers.InsertOnSubmit(publisher);
7 context.SubmitChanges();

提交成功,關聯對象的添加就好像是集合操作。但是,好像缺了點什麼?我們好像沒有給Book.PublisherID賦值,作為外鍵沒有賦值為什麼會沒拋出異常呢?這都是生成代碼的功勞,我們看看Publisher.Books屬性的定義

1 private EntitySet<Book> _Books;
2
3 public Publisher()
4 {
5     this._Books = new EntitySet<Book>(
6     new Action<Book>(this.attach_Books), new Action<Book>(this.detach_Books));
7     OnCreated();
8 }

當向Books集合中添加元素時,會調用attach_Books讓Book.Publisher指向Publisher對象

1 private void attach_Books(Book entity)
2 {
3     this.SendPropertyChanging();
4     entity.Publisher = this;
5 }

而Book.Publisher的賦值又觸發事件使Book.PublisherId=Book.Publisher.PublisherID

01 public Publisher Publisher
02     {
03         get { … }
04         set
05         {
06             Publisher previousValue = this._Publisher.Entity;
07             if (((previousValue != value)
08                         || (this._Publisher.HasLoadedOrAssignedValue == false)))
09             {
10                 this.SendPropertyChanging();
11                 if ((previousValue != null))
12                 {
13                     this._Publisher.Entity = null;
14                     previousValue.Books.Remove(this);
15                 }
16                 this._Publisher.Entity = value;
17                 if ((value != null))
18                 {
19                     value.Books.Add(this);
20                     this._PublisherID = value.PublisherID;
21                 }
22                 else 
23                 {
24                     this._PublisherID = default(System.Guid);
25                 }
26                 this.SendPropertyChanged("Publisher");
27             }
28         }
29     }

更新

下面代碼示例了在更新Publisher記錄時,同時更新相關的Book記錄

1 Publisher publisher = context.Publishers.Where(
2 p => p.PublisherID == new Guid("ae825c5f-465d-4eb5-a2bb-cc1aeb5edb7d")).Single();
3 publisher.Name = "Updated Publisher";
4 var book = publisher.Books.First();
5 book.Title = "Updated book";
6
7 context.SubmitChanges();

這樣的操作我們稱之為什麼?級聯更新?事實上LINQ2SQL的實現裡不存在所謂的“級聯更新“。在生命周期內,每一個DataContext對象都維護著每一個查詢獲得的對象的引用,並且跟蹤對象的修改,所有發生了修改的對象,在調用 DataContext.SubmitChanges的時候,都會被保存到數據庫,這方面的內容在”LINQ那些事(6)“裡會詳細討論。所以,在這裡不是級聯更新,而是Publisher和Book對象都發生了更改,所以在調用SubmitChanges都被保存了。

涉及Association的更新,有時還會引發異常,我們來看下面這段代碼:

1 var context = GenerateContext();
2 Publisher publisher = context.Publishers.Where(
3 p => p.PublisherID == new Guid("ae825c5f-465d-4eb5-a2bb-cc1aeb5edb7d")).Single();
4 var book = publisher.Books.First();
5 publisher.Books.Remove(book);
6
7 context.SubmitChanges();

這段代碼的意圖是刪除Publisher對象相關的某Book對象,看起來是很漂亮的集合操作,但是運行時拋出異常:

“An attempt was made to remove a relationship between a Publisher and a Book. However, one of the relationship's foreign keys (Book.PublisherID) cannot be set to null.“

還記得我們在調用Publisher.Books.Add時候,EntitySet的會調用 attach_Books來修改Book.PublisherID嗎?在調用Publisher.Books.Remove的時候,EntitySet會調用detach_Books來讓Book.PublisherID = default(Guid)

而對於LINQ2SQL,只跟蹤到了Book.PublisherID屬性發生了更改,所以在調用 SubmitChanges會嘗試更新Book記錄而產生上述外鍵異常。解決這個問題的方法是:在Book.PublisherID的 ColumnAttribute定義中,把DeleteOnNull設為true。這樣當Context發現Book.PublisherID為空(default(Guid)相當於Guid.Empty)時,不是執行Update而是Delete操作。

刪除

根據msdn,目前版本的LINQ是不支持級聯刪除的,需要級聯刪除只能依賴數據庫的級聯刪除,否則也可以考慮下面的方式:

1 context.Books.DeleteAllOnSubmit(publisher.Books.AsEnumerable());
2 context.Publishers.DeleteOnSubmit(publisher);
3 context.SubmitChanges();

你不需要擔心事務的問題,context.SubmitChanges在執行時會自動創建本地數據庫事務,來保證操作的完整。關於事務的問題,我們在“LINQ那些事(3)”中會有詳細討論。

查詢

先看看下面這段代碼

01 var context = GenerateContext();
02 context.Log = Console.Out;
03
04 // 查詢publisher對象
05 Console.WriteLine("Querying publisher");
06 Publisher publisher = context.Publishers.Where(
07 p => p.PublisherID == new Guid("ae825c5f-465d-4eb5-a2bb-cc1aeb5edb7d")).Single();
08
09 Console.WriteLine("Querying books")
10 // 當調用publisher.Books.GetEnumerator()時,執行book對象的查詢 
11 foreach (Book book in publisher.Books)
12 {
13 Console.WriteLine(book.Title);
14 } 

在默認情況下,DataContext並不會加載Assocation對象(EntitySet<T> 或EntityRef<T>),當Assocation對象需要被訪問時才會執行數據庫查詢,這就是所謂的lazy-loading。 LINQ2SQL的Layz-loading的實現與IEnumerable<T>的deferred query execution是一樣的,有興趣可以看看EntitySet<T>.GetEnumerator或 EntityRef<T>.Entity.Getter代碼。

Lazy-loading的好處是避免了不必要的查詢,但是在某些場合確定Assocation對象都應該加載時,我們可以設置DataContext. LoadOption來指定Assocation對象的加載:

1 DataLoadOptions option = new DataLoadOptions();
2 option.LoadWith<Publisher>(p => p.Books);
3 context.LoadOptions = option;

總結:本節討論了如何使用LINQ2SQL來進行簡單對象和涉及Association的對象的CRUD操作。示例代碼段在只涉及一個DataContext對象或在單線程的情況下都可以正確運行,但是當涉及多個DataContext或並發訪問的情況的下會怎麼樣呢?這是我們接下來要討論的。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved