Trong bài trước khi giới thiệu về “Object-Relational Mapping, Entity Class, Association và DataContext”, tôi đã làm một ví dụ nhỏ tạo entity class và truy vấn dữ liệu trên database Northwind. Hôm nay tôi sẽ làm một ví dụ tương tự nhưng hoàn chỉnh hơn để bạn hiểu rõ cách tạo và sửa đổi các entity class khi cần thiết, bao gồm ví dụ về One-To-Many Relationship.
Giới thiệuTrong 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:
Lớp NorthwindDataContextKhi 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: [DatabaseAttribute(Name = "northwind")] public partial class NorthwindDataContext : DataContext { public NorthwindDataContext(string connection) : base(connection) { } public Table<Category> Categories { get { return this.GetTable<Category>(); } } public Table<Products> Products { get { return this.GetTable<Products>(); } } } 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 ProductLớ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: [Table(Name = "Products")] public partial class Product { private int _ProductID; private string _ProductName; private System.Nullable<int> _CategoryID; private EntityRef<Category> _Category; public Product() { this._Category = default(EntityRef<Category>); } // ... } 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: [Table(Name = "Products")] public partial class Product { // ... [Column(Storage = "_ProductID", AutoSync = AutoSync.OnInsert, DbType = "Int NOT NULL IDENTITY", IsPrimaryKey = true, IsDbGenerated = true)] public int ProductID { get { return this._ProductID; } set { if ((this._ProductID != value)) this._ProductID = value; } } [Column(Storage = "_ProductName", DbType = "NVarChar(40) NOT NULL", CanBeNull = false)] public string ProductName { get { return this._ProductName; } set { if ((this._ProductName != value)) this._ProductName = value; } } [Column(Storage = "_CategoryID", DbType = "Int")] public System.Nullable<int> CategoryID { get { return this._CategoryID; } set { if ((this._CategoryID != value)) { if (this._Category.HasLoadedOrAssignedValue) { throw new ForeignKeyReferenceAlreadyHasValueException(); } this._CategoryID = value; } } } // ... } 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: [Association(Name = "FK_Products_Categories", ThisKey = "CategoryID", IsForeignKey = true)] public Category Category { get { return this._Category.Entity; } set { Category previousValue = this._Category.Entity; if (((previousValue != value) || (this._Category.HasLoadedOrAssignedValue == false))) { if ((previousValue != null)) { this._Category.Entity = null; previousValue.Products.Remove(this); } this._Category.Entity = value; if ((value != null)) { value.Products.Add(this); this._CategoryID = value.CategoryID; } else { this._CategoryID = default(Nullable<int>); } } } } 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ú ý:
Lớp CategoryTươ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): [Table(Name = "Categories")] public partial class Category { private int _CategoryID; private string _CategoryName; private EntitySet<Product> _Products; public Category() { Action<Product> attachProducts = new Action<Product>((p) => p.Category = this); Action<Product> detachProducts = new Action<Product>((p) => p.Category = null); this._Products = new EntitySet<Product>(new Action<Product>(Attach_Products), detachProducts); } [Column(Storage = "_CategoryID", AutoSync = AutoSync.OnInsert, DbType = "Int NOT NULL IDENTITY", IsPrimaryKey = true, IsDbGenerated = true)] public int CategoryID { get { return this._CategoryID; } set { if ((this._CategoryID != value)) this._CategoryID = value; } } [Column(Storage = "_CategoryName", DbType = "NVarChar(15) NOT NULL", CanBeNull = false)] public string CategoryName { get { return this._CategoryName; } set { if ((this._CategoryName != value)) this._CategoryName = value; } } [Association(Name = "FK_Products_Categories", Storage = "_Products", OtherKey = "CategoryID", DeleteRule = "NO ACTION")] public EntitySet<Product> Products { get { return this._Products; } set { this._Products.Assign(value); } } } 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ỉnhLớp Northwnd.cs: using System; using System.Data.Linq; using System.Data.Linq.Mapping; namespace Northwnd { [DatabaseAttribute(Name = "northwind")] public partial class NorthwindDataContext : DataContext { public NorthwindDataContext(string connection) : base(connection) { } public Table<Category> Categories { get { return this.GetTable<Category>(); } } public Table<Product> Products { get { return this.GetTable<Product>(); } } } [Table(Name = "Categories")] public partial class Category { private int _CategoryID; private string _CategoryName; private EntitySet<Product> _Products; public Category() { Action<Product> attachProduct = new Action<Product>((p) => p.Category = this); Action<Product> detachProduct = new Action<Product>((p) => p.Category = null); this._Products = new EntitySet<Product>(attachProduct, detachProduct); } [Column(Storage = "_CategoryID", AutoSync = AutoSync.OnInsert, DbType = "Int NOT NULL IDENTITY", IsPrimaryKey = true, IsDbGenerated = true)] public int CategoryID { get { return this._CategoryID; } set { if ((this._CategoryID != value)) this._CategoryID = value; } } [Column(Storage = "_CategoryName", DbType = "NVarChar(15) NOT NULL", CanBeNull = false)] public string CategoryName { get { return this._CategoryName; } set { if ((this._CategoryName != value)) this._CategoryName = value; } } [Association(Name = "FK_Products_Categories", Storage = "_Products", OtherKey = "CategoryID", DeleteRule = "NO ACTION")] public EntitySet<Product> Products { get { return this._Products; } set { this._Products.Assign(value); } } } [Table(Name = "Products")] public partial class Product { private int _ProductID; private string _ProductName; private System.Nullable<int> _CategoryID; private EntityRef<Category> _Category; public Product() { this._Category = default(EntityRef<Category>); } [Column(Storage = "_ProductID", AutoSync = AutoSync.OnInsert, DbType = "Int NOT NULL IDENTITY", IsPrimaryKey = true, IsDbGenerated = true)] public int ProductID { get { return this._ProductID; } set { if ((this._ProductID != value)) this._ProductID = value; } } [Column(Storage = "_ProductName", DbType = "NVarChar(40) NOT NULL", CanBeNull = false)] public string ProductName { get { return this._ProductName; } set { if ((this._ProductName != value)) this._ProductName = value; } } [Column(Storage = "_CategoryID", DbType = "Int")] public System.Nullable<int> CategoryID { get { return this._CategoryID; } set { if ((this._CategoryID != value)) { if (this._Category.HasLoadedOrAssignedValue) { throw new ForeignKeyReferenceAlreadyHasValueException(); } this._CategoryID = value; } } } [Association(Name = "FK_Products_Categories", ThisKey = "CategoryID", IsForeignKey = true)] public Category Category { get { return this._Category.Entity; } set { Category previousValue = this._Category.Entity; if (((previousValue != value) || (this._Category.HasLoadedOrAssignedValue == false))) { if ((previousValue != null)) { this._Category.Entity = null; previousValue.Products.Remove(this); } this._Category.Entity = value; if ((value != null)) { value.Products.Add(this); this._CategoryID = value.CategoryID; } else { this._CategoryID = default(Nullable<int>); } } } } } } 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: using System; using System.Linq; using System.Data.Linq; using Northwnd; class Program { static void Main() { NorthwindDataContext db = new NorthwindDataContext("C:\\SampleDB\\Northwnd.mdf"); Table<Category> categories = db.Categories(); var query = from c in categories where c.CategoryName.StartsWith("M") select c; Console.WriteLine("Category:"); foreach (var cat in query) { Console.WriteLine(cat.CategoryID + " | " + cat.CategoryName); Console.WriteLine("Products:\n\t{0,-4} | {1,-25} | {2}\n","ID","Name","CategoryID"); foreach (var p in cat.Products) Console.WriteLine("\t{0,-4} | {1,-25} | {2}",p.ProductID,p.ProductName,p.CategoryID); } Console.Read(); } } 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ậnTrong 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. Related articles
|
LINQ to SQL – Entity Class: Mapping Database, Table và Relationship
Ý kiến bạn đọc
Tin tức khác
LINQ – Group Join và Outer Join (Left Join, Right Join)
- 19/3/2012
LINQ – Sử dụng Group By
- 19/3/2012
LINQ – Ví dụ về Multiple Inner Join (C#)
- 19/3/2012
LINQ – Sử dụng Inner Join (C#)
- 19/3/2012
LINQPad – Hướng dẫn tạo class và chạy C# Program
- 19/3/2012
Hướng dẫn cơ bản sử dụng LINQPad
- 19/3/2012
LINQ to SQL – Các khái niệm cơ bản: Object-Relational Mapping, Entity Class, Association và DataContext
- 19/3/2012
LINQ – Từ Lambda Expression đến LINQ
- 19/3/2012
DLR– Dynamic Programming: Một wrapper của string với DynamicObject và reflection
- 16/3/2012
LINQ cơ bản– Tổng quan về kiến trúc LINQ to XML
- 14/3/2012
Tin tiêu điểm
-
LINQ – Sử dụng Inner Join (C#) (44,486)
-
LINQ-Phần 1: Truy vấn dữ liệu. (33,625)
-
LINQ to SQL – Các khái niệm cơ bản: Object-Relational Mapping, Entity Class, Association và DataContext (10,914)
-
LINQ – Ví dụ về Multiple Inner Join (C#) (9,781)
-
LINQPad – Hướng dẫn tạo class và chạy C# Program (9,718)
-
LINQ-Phần 3: Thực thi dữ liệu với Transaction (9,183)
-
Hướng dẫn cơ bản sử dụng LINQPad (8,955)
-
LINQ – Group Join và Outer Join (Left Join, Right Join) (8,791)
-
LINQ-Phần 2: Thưc thi dữ liệu. (7,288)
-
LINQ-Phần 4: Linq nâng cao (7,048)
Gallery
Text Links
Thiết kế logo chuyên nghiệp Insky
DAFABET
W88 w88b.com/dang-ky-tai-khoan-w88
W88
ca do bong da online
DAFABET
W88 w88b.com/dang-ky-tai-khoan-w88
W88
ca do bong da online
Tags
asp.net
JavaScript
Lập trình
Cơ sở dữ liệu
jquery
Csharp
Ajax
Thủ thuật
JavaScript
menu
Sql Server
Lập trình C#
WebService
stty
Sql
Phân trang
Rewrite
Mã hoá
Backup
Thủ thuật lập trình
Store procedure
Accordion
Validation
Store
Upload
Slide
jQueryPlugin
StoreProcedure
Regular Expression
Regex
android
Quick and snow
HTML5
WPF
WCF
Copyright © 2011 - 2012 vietshare.vn
by
phamkhuong102@gmail.com doanhkisi2315@gmail.com. All rights reserved.