LINQ không chỉ dùng để rút trích hay truy vấn dữ liệu mà còn là công cụ mạnh để chuyển đổi dữ liệu. Bằng cách dùng LINQ, bạn có thể sử dụng 1 tập dữ liệu nguồn làm đầu vào và thay đổi tập này theo nhiều cách để tạo ra các tập dữ liệu mới (đầu ra). Thậm chí là bạn có thay đổi các phần tử của tập dữ liệu nguồn bằng cách sắp xếp hoặc gom nhóm. Tuy nhiên, chức năng mạnh nhất của LINQ vẫn là khả năng tạo ra các dạng tập dữ liệu mới và được thực hiện bằng mệnh đề select. Một số ví dụ mà LINQ có thể thực hiện được trong việc chuyển đổi dữ liệu là:
• Trộn 2 tập dữ liệu đầu vào thành 1 tập đầu ra với dạng mới
• Tạo tập dữ liệu đầu ra chỉ chứa 1 hoặc vài thuộc tính của mỗi phần tử trong tập dữ liệu nguồn
• Tạo tập dữ liệu đầu ra với các phần tử chứa các kết quả thực thi trên tập dữ liệu nguồn
• Tạo tập dữ liệu đầu ra theo 1 định dạng khác. Ví dụ, chuyển đổi dữ liệu từ các dòng SQL hay các tập tin văn bản thành XML.
Chúng ta có thể nhìn lại mô hình kiến trúc LINQ để thấy bức tranh tổng thể, theo đó LINQ có chuyển đổi thành rất nhiều kiểu nguồn dữ liệu và ngược lại như LINQ to Objects, LINQ to DataSet, LINQ to SQL, LINQ to Entities, LINQ to XML.
Trên đây chỉ là 1 vài ví dụ mà LINQ có thể dùng để chuyển đổi dữ liệu. Tất nhiên là những chuyển đổi có thể kết hợp theo nhiều cách trong cùng 1 truy vấn. Hơn nữa, tập dữ liệu đầu ra lại còn có thể sử dụng là tập dữ liệu đầu vào của 1 truy vấn mới.
4.1. Kết hợp nhiều tập dữ liệu đầu vào thành 1 tập dữ liệu đầu ra
Bạn có thể dùng truy vấn LINQ để tạo một tập dữ liệu đầu ra chứa các phần tử tử nhiều tập dữ liệu đầu vào. Ví dụ sau mô tả cách kết hợp 2 cấu trúc dữ liệu với cùng 1 nguyên lý mà có thể áp dụng cho kết hợp dữ liệu từ XML, SQL hay các nguồn DataSet. Giả sử chúng ta có 2 lớp như sau:
class Student
{
public string Name { get; set; }
public int ID { get; set; }
public string City { get; set; }
}
class Teacher
{
public string Name { get; set; }
public int ID { get; set; }
public string City { get; set; }
}
Lớp Student và lớp Teacher chứa các thuộc tính giống nhau: Name, ID và City. Trong hàm Main chúng ta tạo 2 danh sách kiểu List chứa danh sách Teacher và danh sách Student với các dữ liệu được gán trực tiếp trong mã nguồn như sau:
class DataTransformations
{
static void Main()
{
// Tạo danh sách sinh viên có kiểu List, với từng đối tượng kiểu Student
List students = new List()
{
new Student {Name="Svetlana", ID=111, City="Đà Lạt"},
new Student {Name="Claire", ID=112, City="Nha Trang"},
new Student {Name="Sven", ID=113, City="Hà Nội"}
};
// Tạo danh sách giáo viên cũng có kiểu List, với từng đối tượng kiểu Teacher
List teachers = new List()
{
new Teacher {Name="Ann", ID=945, City = "Dammio"},
new Teacher {Name="Alex", ID=956, City = "Hà Nội"},
new Teacher {Name="Michiyo", ID=972, City = "Nha Trang"}
};
// Tạo câu truy vấn
var peopleInNhaTrang = (from student in students
where student.City == "Nha Trang"
select student.Name)
.Concat(from teacher in teachers
where teacher.City == "Nha Trang"
select teacher.Name);
Console.WriteLine("Các sinh viên và giáo viên sống ở Nha Trang:");
// Thực hiện truy vấn
foreach (var person in peopleInNhaTrang)
{
Console.WriteLine(person);
}
Console.WriteLine("Nhất bất ký nút nào để thoát");
Console.ReadKey();
}
}
Trong ví dụ trên, chúng ta xem lại cách tạo câu truy vấn như sau:
var peopleInNhaTrang = (from student in students
where student.City == "Nha Trang"
select student.Name)
.Concat(from teacher in teachers
where teacher.City == "Nha Trang"
select teacher.Name);
Chúng ta thấy rằng để gom 2 tập dữ liệu trong LINQ, ở ví dụ dùng phương thức Concat(…) (Concatenate – động từ có nghĩa là gom lại với nhau), phương thức này dùng để gom 2 tập dữ liệu tương đương với nhau. Ngoài ra có rất nhiều cách kết hợp 2 tập dữ liệu như Aggregate, Union, Join, … chúng ta sẽ tìm hiểu thêm trong các bài tiếp theo. Theo đó, trong ví dụ sẽ cho ra kết quả tên giáo viên và sinh viên ở Nha Trang. Kết quả là: Claire và Michiyo.
Để hiểu rõ hơn về phương thức Concat(…) chúng ta xét đến ví dụ sau:
class Program
{
static void Main()
{
List<string> list1 = new List<string>();
list1.Add("xin"); list1.Add("chào");
List<string> list2 = new List<string>();
list2.Add("Linq"); list2.Add("!");
var result = list1.Concat(list2);
List<string> resultList = result.ToList();
foreach (var entry in resultList)
{
Console.WriteLine(entry);
}
}
}
Trong ví dụ trên, chúng ta có 2 danh sách list1 và list2 với mỗi phần tử là kiểu string, sau đó chúng ta dùng 1 biến var result để trộn danh sách 1 với danh sách 2 bằng phương thức Concat(…) và xuất ra kết quả như sau:
xin
chào
Linq
!
4.2. Chọn tập con của mỗi phần tử nguồn
Đây là phương pháp thứ 2 của LINQ để tạo tập dữ liệu đầu ra. Có 2 cách chính để chọn tập dữ liệu con của mỗi phần tử ở tập dữ liệu nguồn:
a. Chọn 1 thành viên của mỗi phần tử nguồn, sử dụng toán tử chấm (.)
Xét đến ví dụ sau, chúng ta chỉ chọn thành phố (City) sinh sống của các khách hàng để làm tập dữ liệu đầu ra, chúng ta có thể dùng cust.City như trong ví dụ.
var query = from cust in Customers
select cust.City;
b. Để tạo các phần tử chứa nhiều hơn 1 thuộc tính từ phần tử nguồn, chúng ta có thể dùng 1 đối tượng tự đặt tên hoặc để kiểu khuyến danh.
Trong ví dụ sau, chúng ta để 1 kiểu khuyến danh với 2 thuộc tính Name và City.
var query = from cust in Customer
select new { Name = cust.Name, City = cust.City };
4.3. Chuyển đổi các đối tượng thành XML
Các truy vấn LINQ cho phép chuyển đổi dữ liệu giữa các cấu trúc đối tượng, cơ sở dữ liệu SQL, ADO.NET Dataset và XML hay các tài liệu với nhau. Trong ví dụ sau, chúng ta sẽ tìm hiểu cách chuyển đổi các cấu trúc đối tượng thành các phần tử XML.
class XMLTransform
{
static void Main()
{
// tạo nguồn dữ liệu bằng cách dùng 1 tập hợp khởi tạo
// Lớp Student tương tự phần trên
List<Student> students = new List<Student>()
{
new Student {Name="Svetlana", ID=111},
new Student {Name="Claire", ID=112}
};
// Tạo câu truy vấn
var studentsToXML = new XElement("Root",
from student in students
select new XElement("student",
new XElement("Name", student.Name),
new XElement("ID", student.ID)
) // kết thúc "student"
); // kết thúc "Root"
// Thực thi câu truy vấn.
Console.WriteLine(studentsToXML);
// Giữ cửa số console vẫn mở trong chế độ debug - dammio.com
Console.WriteLine("Nhấn bất cứ phím nào để thoát");
Console.ReadKey();
}
}
Chúng ta xem lại câu truy vấn trong ví dụ thì nhận thấy rằng các nút trong XML mô tả bằng lớp XElement, theo đó chúng ta có thể phân cấp nút bằng cách tạo nút trong nút lồng nhau và gán giá trị cho các nút này. Ví dụ trên cho ra kết quả như sau.
<Root>
<student>
<Name>Svetlana</Name>
<ID>111</ID>
</student>
<student>
<Name>Claire</Name>
<ID>112</ID>
</student>
</Root>
4.4. Thực thi các tương tác trên dữ liệu nguồn
Tập dữ liệu đầu ra có thể không chứa bất kỳ phần tử hay thuộc tính nào của tập dữ liệu đầu vào, thay vào đó nó có thể chứa các giá trị dựa trên kết quả tính toán theo các giá trị đầu vào. Chúng ta theo dõi ví dụ sau đây.
class FormatQuery
{
static void Main()
{
// Nguồn dữ liệu
double[] listNumber = { 1, 2, 3 };
// Query.
IEnumerable<string> query =
from number in listNumber
select String.Format("Square = {0}", number * number);
// Thực thi truy vấn
foreach (string s in query)
Console.WriteLine(s);
// Giữ cho cửa sổ console mở trong chế độ debug
Console.WriteLine("Nhấn bất kỳ phím nào để thoát.");
Console.ReadKey();
}
}
/* Kết quả:
Square = 1
Square = 4
Square = 9
*/
Trong ví dụ trên, chúng ta có thể định dạng dữ liệu đầu ra hoàn toàn mới so với tập dữ liệu đầu vào. Chúng ta có danh sách đầu vào là các số 1, 2, 3 và tập dữ liệu đầu ra là nhãn tính bình phương của các số này viết trên từng dòng.