Chatbox

Các bạn vui lòng dùng từ ngữ lịch sự và có văn hóa,sử dụng Tiếng Việt có dấu chuẩn. Chúc các bạn vui vẻ!
05/01/2012 10:01 # 1
khanhdtq
Cấp độ: 2 - Kỹ năng: 2

Kinh nghiệm: 4/20 (20%)
Kĩ năng: 6/20 (30%)
Ngày gia nhập: 27/09/2010
Bài gởi: 14
Được cảm ơn: 16
LINQ to SQL


Giới thiệu

Trong ví dụ này tôi sẽ tạo các Entity class cho database Northwind, table Categories và Products. Mối quan hệ giữa hai bảng này được minh họa như hình sau, cùng các cột mà tôi sẽ sử dụng:

Bạn cũng đừng quên thêm tham chiếu đến thư viện System.Data.Linq và hai khai báo namespace sau:

using System.Data.Linq;

using System.Data.Linq.Mapping;

Lớp NorthwindDataContext

Khi tạo lớp này bạn có thể không cần đến từ DataContext trong phần tên lớp, tuy nhiên tôi muốn giữ lại để giúp phân biệt dễ dàng hơn giữa entity class cho database và cho các table.

Ta sử dụng attibute [DatabaseAttribute] và thuộc tính Name để tạo một entity class đại diện cho database Northwind, và tất nhiên lớp này phải kế thừa từ DataContext:

01 [DatabaseAttribute(Name = "northwind")]
02 public partial class NorthwindDataContext : DataContext
03 {
04     public NorthwindDataContext(string connection)
05         : base(connection)
06     {
07     }
08  
09     public Table<Category> Categories
10     {
11         get { return this.GetTable<Category>(); }
12     }
13  
14     public Table<Products> Products
15     {
16         get { return this.GetTable<Products>(); }
17     }
18 }

Constructor của entity class nhận một vào chuỗi kết nối, connection, ta gọi trực tiếp constructor của lớp cha (DataContext) với tham số là connection này để tạo kết nối.

Hai phương thức còn lại là Categories() và Products() chỉ đơn giản là cho phép lấy trực tiếp các table có tên tương ứng với phương thức, bằng cách  gọi phương thức GetTable<TEntity>() của DataContext. Giả sử bạn có 10 table trong database và cần sử dụng chúng, bạn sẽ tạo 10 tên phương thức để trả về mỗi table với tên tương ứng.

Lớp Product

Lớp này đại diện cho một dòng dữ liệu của table Products, cũng có thể coi là lớp đại diện cho table Products trong database theo nguyên tắc ánh xạ ORM (Object-Relational Mapping).

Trong ví dụ này tôi chỉ dùng ba cột là ProductID, ProductName và CategoryID, mỗi cột ứng với một private field. Tuy nhiên như vậy chưa đủ, vì Product có mối quan hệ cha-con với Category nên ta cần một tham chiếu đến đối tượng Category để có thể truy xuất trực tiếp đến nó. Đối tượng tham chiếu này sẽ có kiểu là EntityRef<TEntity> với tên _Category.

Trong constructor của Product ta sẽ khởi tạo giá trị mặc định cho đối tượng _Category này với từ khóa default:

01 [Table(Name = "Products")]
02 public partial class Product
03 {
04     private int _ProductID;
05  
06     private string _ProductName;
07  
08     private System.Nullable<int> _CategoryID;
09  
10     private EntityRef<Category> _Category;
11  
12     public Product()
13     {
14         this._Category = default(EntityRef<Category>);
15     }
16  
17     // ...
18 }

Trong đoạn mã trên bạn có thể thấy field _CategoryID được khai báo với kiểu System.Nullable<int>, điều này cho phép _CategoryID có thể được gán giá trị null (một giá trị mà int không thể có được). Điều này là do trong cột CategoryID trong table Products được thiết lập Allow Nulls là true. Nếu như bạn không cho phép null, ta chỉ cần khai báo với kiểu int như _ProductID.

Tiếp đến là tạo các property tương ứng cho các cột tương ứng là ProductID, ProductName và CategoryID:

01 [Table(Name = "Products")]
02 public partial class Product
03 {
04     // ...
05  
06     [Column(Storage = "_ProductID", AutoSync = AutoSync.OnInsert, DbType = "Int NOT NULL IDENTITY", IsPrimaryKey = true, IsDbGenerated = true)]
07     public int ProductID
08     {
09         get { return this._ProductID; }
10         set
11         {
12             if ((this._ProductID != value))
13                 this._ProductID = value;
14         }
15     }
16  
17     [Column(Storage = "_ProductName", DbType = "NVarChar(40) NOT NULL", CanBeNull = false)]
18     public string ProductName
19     {
20         get { return this._ProductName; }
21         set
22         {
23             if ((this._ProductName != value))
24                 this._ProductName = value;
25         }
26     }
27  
28     [Column(Storage = "_CategoryID", DbType = "Int")]
29     public System.Nullable<int> CategoryID
30     {
31         get { return this._CategoryID; }
32         set
33         {
34             if ((this._CategoryID != value))
35             {
36                 if (this._Category.HasLoadedOrAssignedValue)
37                 {
38                     throw new ForeignKeyReferenceAlreadyHasValueException();
39                 }
40                 this._CategoryID = value;
41             }
42         }
43     }
44  
45     // ...
46 }

Các property này không có gì đặc biệt ngoại trừ một điểm khi gán giá trị cho CategoryID. Giá trị của _CategoryID phải khớp với đối tượng _Category. Chính vì vậy ta cần phải kiểm tra xem _Category đã có giá trị chưa bằng property HasLoadedOrAssignedValue của EntityRef<TEntity> trước khi thay đổi giá trị của _CategoryID.

Mối quan hệ của Product và Category được thể hiện bởi một property với [AssociationAttribute]. Việc thay đổi giá trị của property này cần được kiểm tra kĩ càng và phải đảm bảo _CategoryID cũng phải được thay đổi theo. Ngoài ra, bởi vì bên entity class Category (sẽ trình bày trong phần kế tiếp) cũng sẽ có một collection chứa các đối tượng Product. Ta phải loại bỏ đối tượng Product ra khỏi tập hợp đó nếu như “cha” (Category) của nó được thay đổi:

01 [Association(Name = "FK_Products_Categories", ThisKey = "CategoryID", IsForeignKey = true)]
02 public Category Category
03 {
04     get { return this._Category.Entity; }
05     set
06     {
07         Category previousValue = this._Category.Entity;
08         if (((previousValue != value)
09                     || (this._Category.HasLoadedOrAssignedValue == false)))
10         {
11             if ((previousValue != null))
12             {
13                 this._Category.Entity = null;
14                 previousValue.Products.Remove(this);
15             }
16             this._Category.Entity = value;
17             if ((value != null))
18             {
19                 value.Products.Add(this);
20                 this._CategoryID = value.CategoryID;
21             }
22             else
23             {
24                 this._CategoryID = default(Nullable<int>);
25             }
26         }
27     }
28 }

Các thuộc tính của [ColumnAttribute] dựa vào tên gọi của chúng bạn cũng có thể đoán ra được, tuy nhiên còn một vài thuộc tính bạn cần chú ý:

Name Type Description
AutoSync (enum) AutoSyncBao gồm:Default, Always, Never, OnInsert, OnUpdate Chỉ ra việc lấy giá trị cho property sau lệnh Insert hoặc Update.Ví dụ như các cột ID sẽ được database tự động gán giá trị, việc dùng attribute này sẽ giúp đồng bộ dữ liệu của cột này trong database với property tương ứng sau khi Insert.
IsDbGenerated Boolean Xác định cột có được database tự động sinh ra không (như primary key).
Storage String Thuộc tính này xác định tên của field lưu trữ giá trị cho property. Nhờ đó, LINQ có thể lấy giá trị trực tiếp từ field thay vì thông qua property.

 

Lớp Category

Tương tự như lớp Product, trong ví dụ này ta chỉ sử dụng hai cột là CategoryID, CategoryName, mỗi cột tương ứng với một private field và một private field khác chứa tập hợp các Product có liên hệ với Category hiện tại. Entity class của table cha (Categories) sẽ chứa một collection EntitySet<TEntity> các thể hiện entity class của table con (Products):

01 [Table(Name = "Categories")]
02 public partial class Category
03 {
04     private int _CategoryID;
05  
06     private string _CategoryName;
07  
08     private EntitySet<Product> _Products;
09  
10     public Category()
11     {
12         Action<Product> attachProducts = new Action<Product>((p) => p.Category = this);
13         Action<Product> detachProducts = new Action<Product>((p) => p.Category = null);
14         this._Products = new EntitySet<Product>(new Action<Product>(Attach_Products), detachProducts);
15     }
16  
17     [Column(Storage = "_CategoryID", AutoSync = AutoSync.OnInsert, DbType = "Int NOT NULL IDENTITY", IsPrimaryKey = true, IsDbGenerated = true)]
18     public int CategoryID
19     {
20         get { return this._CategoryID; }
21         set
22         {
23             if ((this._CategoryID != value))
24                 this._CategoryID = value;
25         }
26     }
27  
28     [Column(Storage = "_CategoryName", DbType = "NVarChar(15) NOT NULL", CanBeNull = false)]
29     public string CategoryName
30     {
31         get { return this._CategoryName; }
32         set
33         {
34             if ((this._CategoryName != value))
35                 this._CategoryName = value;
36         }
37     }
38  
39     [Association(Name = "FK_Products_Categories", Storage = "_Products", OtherKey = "CategoryID", DeleteRule = "NO ACTION")]
40     public EntitySet<Product> Products
41     {
42         get { return this._Products; }
43         set { this._Products.Assign(value); }
44     }
45 }

Constructor của lớp này tạo ra hai delegate System.Action<in T> là attachProducts và detachProducts để truyền vào làm tham số của constructor EntitySet<Product>(). Mỗi lần collection EntitySet<Product>, _Products,  được gán hay chèn giá trị, delegate attachProduct sẽ được kích hoạt để gán tham chiếu đến đối tượng Category hiện tại. Tương tự như vậy, khi bạn xóa các đối tượng Product ra khỏi collection này, delegate detachProduct sẽ được kích hoạt để gán tham chiếu Category của đối tượng đó thành null.

Bạn có thể thấy phương thức Assign() được sử dụng trong property Products của lớp này. Ngoài lý do để delegate được kích hoạt ra, phương thức này còn tạo ra một bản sao của giá trị được gán.

Mã nguồn hoàn chỉnh

Lớp Northwnd.cs:

001 using System;
002 using System.Data.Linq;
003 using System.Data.Linq.Mapping;
004  
005 namespace Northwnd
006 {
007     [DatabaseAttribute(Name = "northwind")]
008     public partial class NorthwindDataContext : DataContext
009     {
010         public NorthwindDataContext(string connection)
011             : base(connection)
012         {
013         }
014  
015         public Table<Category> Categories
016         {
017             get { return this.GetTable<Category>(); }
018         }
019  
020         public Table<Product> Products
021         {
022             get { return this.GetTable<Product>(); }
023         }
024     }
025  
026     [Table(Name = "Categories")]
027     public partial class Category
028     {
029         private int _CategoryID;
030  
031         private string _CategoryName;
032  
033         private EntitySet<Product> _Products;
034  
035         public Category()
036         {
037             Action<Product> attachProduct = new Action<Product>((p) => p.Category = this);
038             Action<Product> detachProduct = new Action<Product>((p) => p.Category = null);
039             this._Products = new EntitySet<Product>(attachProduct, detachProduct);
040         }
041  
042         [Column(Storage = "_CategoryID", AutoSync = AutoSync.OnInsert, DbType = "Int NOT NULL IDENTITY", IsPrimaryKey = true, IsDbGenerated = true)]
043         public int CategoryID
044         {
045             get { return this._CategoryID; }
046             set
047             {
048                 if ((this._CategoryID != value))
049                     this._CategoryID = value;
050             }
051         }
052  
053         [Column(Storage = "_CategoryName", DbType = "NVarChar(15) NOT NULL", CanBeNull = false)]
054         public string CategoryName
055         {
056             get { return this._CategoryName; }
057             set
058             {
059                 if ((this._CategoryName != value))
060                     this._CategoryName = value;
061             }
062         }
063  
064         [Association(Name = "FK_Products_Categories", Storage = "_Products", OtherKey = "CategoryID", DeleteRule = "NO ACTION")]
065         public EntitySet<Product> Products
066         {
067             get { return this._Products; }
068             set { this._Products.Assign(value); }
069         }
070     }
071  
072     [Table(Name = "Products")]
073     public partial class Product
074     {
075         private int _ProductID;
076  
077         private string _ProductName;
078  
079         private System.Nullable<int> _CategoryID;
080  
081         private EntityRef<Category> _Category;
082  
083         public Product()
084         {
085             this._Category = default(EntityRef<Category>);
086         }
087  
088         [Column(Storage = "_ProductID", AutoSync = AutoSync.OnInsert, DbType = "Int NOT NULL IDENTITY", IsPrimaryKey = true, IsDbGenerated = true)]
089         public int ProductID
090         {
091             get
092             {
093  
094                 return this._ProductID;
095             }
096             set
097             {
098                 if ((this._ProductID != value))
099                     this._ProductID = value;
100             }
101         }
102  
103         [Column(Storage = "_ProductName", DbType = "NVarChar(40) NOT NULL", CanBeNull = false)]
104         public string ProductName
105         {
106             get { return this._ProductName; }
107             set
108             {
109                 if ((this._ProductName != value))
110                     this._ProductName = value;
111             }
112         }
113  
114         [Column(Storage = "_CategoryID", DbType = "Int")]
115         public System.Nullable<int> CategoryID
116         {
117             get { return this._CategoryID; }
118             set
119             {
120                 if ((this._CategoryID != value))
121                 {
122                     if (this._Category.HasLoadedOrAssignedValue)
123                     {
124                         throw new ForeignKeyReferenceAlreadyHasValueException();
125                     }
126                     this._CategoryID = value;
127                 }
128             }
129         }
130  
131         [Association(Name = "FK_Products_Categories", ThisKey = "CategoryID", IsForeignKey = true)]
132         public Category Category
133         {
134             get { return this._Category.Entity; }
135             set
136             {
137                 Category previousValue = this._Category.Entity;
138                 if (((previousValue != value)
139                             || (this._Category.HasLoadedOrAssignedValue == false)))
140                 {
141                     if ((previousValue != null))
142                     {
143                         this._Category.Entity = null;
144                         previousValue.Products.Remove(this);
145                     }
146                     this._Category.Entity = value;
147                     if ((value != null))
148                     {
149                         value.Products.Add(this);
150                         this._CategoryID = value.CategoryID;
151                     }
152                     else
153                     {
154                         this._CategoryID = default(Nullable<int>);
155                     }
156                 }
157             }
158         }
159     }
160 }

Kiểm tra với lớp Program.cs, đoạn mã trong Main() sẽ lấy ra dòng dữ liệu trong bảng Categories có CategoryName bắt đầu bằng “M”, sau đó in ra tất cả các dòng trong bảng Products có liên hệ với Category này:

01 using System;
02 using System.Linq;
03 using System.Data.Linq;
04 using Northwnd;
05  
06 class Program
07 {
08     static void Main()
09     {
10         NorthwindDataContext db = new NorthwindDataContext("C:\\SampleDB\\Northwnd.mdf");
11  
12         Table<Category> categories = db.Categories();
13         var query = from c in categories where c.CategoryName.StartsWith("M") select c;
14         Console.WriteLine("Category:");
15         foreach (var cat in query)
16         {
17             Console.WriteLine(cat.CategoryID + " | " + cat.CategoryName);
18             Console.WriteLine("Products:\n\t{0,-4} | {1,-25} | {2}\n","ID","Name","CategoryID");
19             foreach (var p in cat.Products)
20                 Console.WriteLine("\t{0,-4} | {1,-25} | {2}",p.ProductID,p.ProductName,p.CategoryID);
21         }
22         Console.Read();
23     }
24 }

Output:

Category:
6 | Meat/Poultry
Products:
        ID   | Name                      | CategoryID

        9    | Mishi Kobe Niku           | 6
        17   | Alice Mutton              | 6
        29   | Thüringer Rostbratwurst   | 6
        53   | Perth Pasties             | 6
        54   | Tourtière                 | 6
        55   | Pâté chinois              | 6

Kết luận

Trong khuôn khổ của bài viết tôi chỉ trình bày về cách tạo các entity class với các DatabaseAttribute, ColumnAttribute và AssociationAttribute. Mã nguồn của các entity class trên được tạo ra bằng công cụ SQLMetal và được tôi rút gọn để tiện trình bày.

// Vui lòng bổ sung nguồn bài viết nha khanhdtq




 
Các thành viên đã Thank khanhdtq vì Bài viết có ích:
07/01/2012 21:01 # 2
tindomobile
Cấp độ: 6 - Kỹ năng: 3

Kinh nghiệm: 53/60 (88%)
Kĩ năng: 7/30 (23%)
Ngày gia nhập: 06/01/2012
Bài gởi: 203
Được cảm ơn: 37
Phản hồi: LINQ to SQL


Còn mình thì có thử qua Rational Rose để lập các bảng, liên hệ và mô hình quan hệ, entity này kia, cũng good lắm, sinh code luôn nếu làm đúng chuẩn, có điều RR nó hơi thiên về Java và làm cũng phải kỹ lắm nên hơi khó. Có lẽ sẽ thử ngâm cứu LINQ coi sao dù dạo này có vài người chê nó rồi
Mà bài viết này chủ thớt nếu tự viết cũng nên ghi rõ luôn, không thì khi nguồn kẻo bị ném đá nữa đó


 điện thoại Samsung Galaxy S
Điện thoại Samsung, đối thủ nặng ký của điện thoại iPhone, kaka.

 
Copyright© Đại học Duy Tân 2010 - 2024