Bai11 truy vấn và xử lý dữ liệu

0 10 0
Bai11 truy vấn và xử lý dữ liệu

Đang tải... (xem toàn văn)

Thông tin tài liệu

Bai11 truy vấn và xử lý dữ liệu aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

HỌC PHẦN: LẬP TRÌNH NET Bài 11: Truy vấn xử lý liệu với Entity Framework Core LINQ (Language Integrated Query) 1.1 Truy vấn truy vấn làm gì? 1.2 Biểu thức truy vấn (query expression) gì? 1.3 Biến truy vấn (Query variable) 1.4 Xây dựng biểu thức truy vấn 1.5 Cú pháp truy vấn cú pháp phương thức 10 ORM EF 11 2.1 ORM (Object Relational Mapping) 11 2.2 EF – ENTITY FRAMEWORK 12 Entity Framework Core (EF Core) 13 Truy vấn liệu sử dụng EF Core 14 4.1 Thêm package cần thiết 15 4.2 Tạo model 19 Cập nhật sở liệu sử dụng EF core 26 5.1 Thêm liệu 26 5.2 Sửa liệu 27 5.3 Xóa liệu 27 Học kết hợp Trang HỌC PHẦN: LẬP TRÌNH NET Bài 11: Truy vấn xử lý liệu với Entity Framework Core - Mục tiêu: Bài học cung cấp kiến thức LINQ và EF Core để truy vấn cập nhật liệu - Yêu cầu: Sinh viên sử dụng LINQ EF Core để truy cập liệu SQL Server - Hình thức tổ chức dạy học: Lý thuyết, tự học - Thời gian: Lý thuyết( lớp: 3; online: 3) Tự học, tự nghiên cứu: 12 - Nội dung: + Nội dung học online: LINQ (Language Integrated Query) ORM EF Entity Framework Core (EF Core) + Nội dung học offline: Truy vấn liệu sử dụng EF Core Cập nhật sở liệu sử dụng EF core LINQ (Language Integrated Query) LINQ (ngơn ngữ truy vấn tích hợp) tên tập hợp cơng nghệ dựa việc tích hợp khả truy vấn trực tiếp vào ngôn ngữ C # Theo truyền thống, truy vấn liệu thể dạng chuỗi đơn giản mà không cần kiểm tra kiểu thời điểm biên dịch hỗ trợ IntelliSense Hơn nữa, bạn phải học ngôn ngữ truy vấn khác cho loại liệu nguồn: sở liệu SQL, tài liệu XML, dịch vụ Web khác nhau, v.v Với LINQ, truy vấn có cấu trúc lớp, gồm lớp, phương thức, kiện Bạn viết truy vấn tập hợp kiểu, cách sử dụng từ khóa ngơn ngữ tốn tử quen thuộc Họ công nghệ LINQ cung cấp trải nghiệm truy vấn quán cho đối tượng (LINQ to Object), sở liệu quan hệ (LINQ to SQL) XML (LINQ to XML) Đối với nhà phát triển, người viết truy vấn, phần "tích hợp ngơn ngữ" dễ thấy LINQ biểu thức truy vấn Các biểu thức truy vấn viết theo cú pháp khai báo truy vấn Bằng cách sử dụng cú pháp truy vấn, bạn thực thao tác lọc, xếp nhóm liệu với việc viết code Bạn sử dụng mẫu biểu thức truy vấn để truy vấn chuyển đổi liệu sở liệu SQL, ADO.NET Datasets, luồng tài liệu XML tập hợp NET Bạn viết truy vấn LINQ C # cho sở liệu SQL Server, tài liệu XML, Dataset ADO.NET tập hợp đối tượng hỗ trợ giao diện Học kết hợp Trang HỌC PHẦN: LẬP TRÌNH NET IEnumerable IEnumerable Hỗ trợ LINQ cung cấp bên thứ ba cho nhiều Web services triển khai sở liệu khác Ví dụ sau cho thấy hoạt động truy vấn hoàn chỉnh Trong LINQ, hoạt động truy vấn hoàn chỉnh bao gồm: tạo nguồn liệu, định nghĩa biểu thức truy vấn thực truy vấn câu lệnh foreach class LINQQueryExpressions { static void Main() { // Xác định nguồn liệu int[] scores = new int[] { 97, 92, 81, 60 }; // Định nghĩa biểu thức truy vấn IEnumerable scoreQuery = from score in scores where score > 80 select score; // Thực thi truy vấn foreach (int i in scoreQuery) { Console.Write(i + " "); } } } Kết quả: Output: 97 92 81 1.1 Truy vấn truy vấn làm gì? Truy vấn tập hợp dẫn mô tả liệu cần truy xuất từ nguồn liệu định, định dạng cách tổ chức liệu trả Nhìn chung, liệu nguồn tổ chức chuỗi phần tử loại Ví dụ, bảng sở liệu SQL chứa chuỗi dòng Trong file XML, có "chuỗi" phần tử XML (mặc dù phần tử tổ chức theo thứ bậc cấu trúc cây) Một tập hợp chứa chuỗi đối tượng nhớ Từ quan điểm ứng dụng, loại cấu trúc cụ thể liệu nguồn ban đầu không quan trọng Ứng dụng xem liệu nguồn tập hợp IEnumerable IQueryable Ví dụ: LINQ to XML, liệu nguồn hiển thị dạng IEnumerable Với chuỗi nguồn này, truy vấn thực ba điều sau:  Truy xuất tập hợp phần tử để tạo chuỗi mà không sửa đổi phần tử riêng biệt Sau đó, truy vấn xếp nhóm chuỗi trả Học kết hợp Trang HỌC PHẦN: LẬP TRÌNH NET theo nhiều cách khác nhau, ví dụ sau (giả sử scores mảng chiều, phần tử có kiểu int): IEnumerable highScoresQuery = from score in scores where score > 80 orderby score descending select score;  Lấy chuỗi phần tử ví dụ chuyển đổi chúng thành kiểu đối tượng Ví dụ: truy vấn truy xuất last name từ ghi khách hàng định nguồn liệu Hoặc truy xuất tồn ghi sau sử dụng để tạo loại đối tượng khác nhớ chí liệu XML trước sinh chuỗi kết cuối Ví dụ sau cho thấy phép chiếu từ số nguyên đến chuỗi Chú ý kiểu highScoresQuery IEnumerable highScoresQuery2 = from score in scores where score > 80 orderby score descending select $"The score is {score}";  Lấy giá trị đơn lẻ liệu nguồn, chẳng hạn như: + Số lượng phần tử phù hợp với điều kiện định + Phần tử có giá trị lớn nhỏ + Phần tử thỏa mãn điều kiện tổng giá trị cụ thể phần tử định Ví dụ: truy vấn sau trả số điểm lớn 80 từ mảng số nguyên Scores: int highScoreCount = (from score in scores where score > 80 select score) Count(); Trong ví dụ trên, lưu ý việc sử dụng dấu ngoặc đơn xung quanh biểu thức truy vấn trước gọi phương thức Count Bạn biểu diễn điều cách sử dụng biến để lưu trữ kết cụ thể Kỹ thuật dễ đọc giữ biến lưu trữ truy vấn tách biệt với truy vấn lưu kết IEnumerable highScoresQuery3 = from score in scores where score > 80 select score; int scoreCount = highScoresQuery3.Count(); Học kết hợp Trang HỌC PHẦN: LẬP TRÌNH NET 1.2 Biểu thức truy vấn (query expression) gì? Một biểu thức truy vấn truy vấn biểu diễn theo cú pháp truy vấn Nó giống biểu thức sử dụng ngữ cảnh biểu thức C # hợp lệ Một biểu thức truy vấn bao gồm tập mệnh đề viết theo cú pháp khai báo tương tự SQL XQuery Mỗi mệnh đề lại chứa nhiều biểu thức C # biểu thức biểu thức truy vấn chứa biểu thức truy vấn Một biểu thức truy vấn phải bắt đầu mệnh đề from phải kết thúc mệnh đề select group Giữa mệnh đề from mệnh đề select group cuối cùng, chứa nhiều mệnh đề tùy chọn sau: where, orderby, join, let chí thêm mệnh đề from Bạn sử dụng từ khóa into phép kết mệnh đề join group trở thành nguồn cho mệnh đề truy vấn bổ sung biểu thức truy vấn 1.3 Biến truy vấn (Query variable) Trong LINQ, biến truy vấn biến lưu trữ truy vấn thay kết truy vấn Cụ thể hơn, biến truy vấn kiểu enumerable, kiểu tạo chuỗi phần tử lặp câu lệnh foreach lời gọi trực tiếp đến phương thức IEnumerator.MoveNext Ví dụ sau cho thấy biểu thức truy vấn đơn giản với nguồn liệu, mệnh đề lọc liệu, mệnh đề xếp không chuyển đổi phần tử nguồn Mệnh đề select kết thúc truy vấn static void Main() { // Nguồn liệu int[] scores = { 90, 71, 82, 93, 75, 82 }; // Biểu thức truy vấn IEnumerable scoreQuery = //biến truy vấn from score in scores //mệnh đề from bắt buộc where score > 80 // mệnh đề where tùy chọn orderby score descending // mệnh đề orderby tùy chọn select score; //biểu thức truy vấn phải kết thúc select|group // Thực thi truy vấn để sinh kết foreach (int testScore in scoreQuery) { Console.WriteLine(testScore); } } Kết quả: Output: 93 90 82 82 - Học kết hợp Trang HỌC PHẦN: LẬP TRÌNH NET Trong ví dụ trên, scoreQuery biến truy vấn, đơi gọi truy vấn Biến truy vấn không lưu trữ liệu kết thực sự, liệu sinh vòng lặp foreach Và câu lệnh foreach thực thi, kết truy vấn không trả thông qua biến truy vấn scoreQuery Thay vào đó, chúng trả thơng qua biến lặp testScore Biến scoreQuery lặp lại vịng lặp foreach thứ hai Nó sinh kết tương tự miễn nguồn liệu khơng bị sửa đổi Một biến truy vấn lưu trữ truy vấn biểu diễn cú pháp truy vấn cú pháp phương thức kết hợp hai Trong ví dụ sau, queryMajorCities queryMajorCities2 biến truy vấn: //Cú pháp truy vấn IEnumerable queryMajorCities = from city in cities where city.Population > 100000 select city; // Cú pháp phương thức IEnumerable queryMajorCities2 = cities.Where(c => c.Population > 100000); Mặt khác, hai ví dụ sau cho thấy biến khơng phải biến truy vấn biến khởi tạo truy vấn Chúng biến truy vấn chúng lưu trữ kết quả: int highestScore = (from score in scores select score) Max(); // tách biểu thức IEnumerable scoreQuery = from score in scores select score; int highScore = scoreQuery.Max(); // đoạn code sau trả lại kết int highScore = scores.Max(); List largeCitiesList = (from country in countries from city in country.Cities where city.Population > 10000 select city) ToList(); // tách biểu thức IEnumerable largeCitiesQuery = from country in countries Học kết hợp Trang HỌC PHẦN: LẬP TRÌNH NET from city in country.Cities where city.Population > 10000 select city; List largeCitiesList2 = largeCitiesQuery.ToList(); Kiểu biến truy vấn tường minh ngầm định Ta sử dụng biến truy vấn có kiểu tường minh để thể mối quan hệ kiểu biến truy vấn mệnh đề select Tuy nhiên, bạn sử dụng từ khóa var để hướng dẫn trình biên dịch suy luận kiểu biến truy vấn (hoặc biến cục khác) thời điểm biên dịch Ví dụ: truy vấn hiển thị trước chủ đề thể cách sử dụng kiểu ngầm định: // Sử dụng var tùy chọn tất truy vấn // queryCities IEnumerable xác định var queryCities = from city in cities where city.Population > 100000 select city; kiểu 1.4 Xây dựng biểu thức truy vấn 1.4.1 Bắt đầu biểu thức truy vấn mệnh đề from Một biểu thức truy vấn phải bắt đầu mệnh đề from Nó nguồn liệu với biến phạm vi (range variable) Biến phạm vi đại diện cho phần tử liên tiếp chuỗi nguồn chuỗi nguồn duyệt qua Biến phạm vi xác định kiểu dựa kiểu phần tử nguồn liệu Trong ví dụ sau, countries mảng đối tượng Country, biến phạm vi country xác định kiểu Country Vì biến phạm vi xác định kiểu, bạn sử dụng dấu chấm (.) để truy cập thành viên có sẵn kiểu IEnumerable countryAreaQuery = from country in countries where country.Area > 500000 // km2 select country; Biến phạm vi nằm phạm vi sử dụng truy vấn thoát dấu chấm phẩy với mệnh đề continuation Một biểu thức truy vấn chứa nhiều mệnh đề from Sử dụng thêm mệnh đề from thân phần tử chuỗi nguồn tập hợp chứa tập hợp Ví dụ: giả sử bạn có tập hợp đối tượng country, đối tượng chứa tập hợp đối tượng City có tên Cities Để truy vấn đối tượng City Country, sử dụng hai mệnh đề from sau: Học kết hợp Trang HỌC PHẦN: LẬP TRÌNH NET IEnumerable cityQuery = from country in countries from city in country.Cities where city.Population > 10000 select city; 1.4.2 Kết thúc biểu thức truy vấn với mệnh đề select group Một biểu thức truy vấn phải kết thúc mệnh đề group mệnh đề select Mệnh đề group Sử dụng mệnh đề group để tạo chuỗi nhóm tổ chức khóa (key) mà bạn định Khóa loại liệu Ví dụ: truy vấn sau tạo chuỗi nhóm có chứa nhiều đối tượng Country khóa giá trị kiểu char có giá trị ký tự thuộc tính Name var queryCountryGroups = from country in countries group country by country.Name[0]; Mệnh đề select Sử dụng mệnh đề select để tạo tất loại chuỗi phần tử khác Một mệnh đề select đơn giản tạo chuỗi đối tượng kiểu đối tượng chứa nguồn liệu Trong ví dụ sau, nguồn liệu chứa đối tượng Country Mệnh đề orderby xếp phần tử theo thứ tự mệnh đề select tạo chuỗi đối tượng country xếp lại IEnumerable sortedQuery = from country in countries orderby country.Area select country; Mệnh đề select sử dụng để chuyển đổi liệu nguồn thành chuỗi kiểu Sự chuyển đổi đặt tên projection (phép chiếu) Trong ví dụ sau, mệnh đề select chiếu chuỗi đối tượng kiểu vô danh, kiểu chứa tập hợp trường phần tử gốc // var bắt buộc truy vấn sinh kiểu vô danh var queryNameAndPop = from country in countries select new { Name = country.Name, Pop = country.Population }; 1.4.3 Từ khóa into Bạn sử dụng từ khóa into mệnh đề select group để tạo định danh tạm thời lưu trữ truy vấn Thực việc bạn phải thực thao tác truy vấn bổ sung truy vấn sau thao tác nhóm chọn Học kết hợp Trang HỌC PHẦN: LẬP TRÌNH NET Trong ví dụ sau, quốc gia phân nhóm theo dân số khoảng 10 triệu Sau nhóm tạo, mệnh đề bổ sung lọc số nhóm sau xếp nhóm theo thứ tự tăng dần Để thực hoạt động bổ sung đó, cần có phần biểu diễn countryGroup // percentileQuery IEnumerable var percentileQuery = from country in countries let percentile = (int) country.Population / 10000000 group country by percentile into countryGroup where countryGroup.Key >= 20 orderby countryGroup.Key select countryGroup; // Nhóm IGrouping foreach (var grouping in percentileQuery) { Console.WriteLine(grouping.Key); foreach (var country in grouping) Console.WriteLine(country.Name + ":" + country.Population); } 1.4.4 Lọc, xếp kết nối Giữa mệnh đề bắt đầu from mệnh đề kết thúc select group, tất mệnh đề khác (where, join, orderby, from, let) tùy chọn Bất kỳ mệnh đề tùy chọn sử dụng nhiều lần thân truy vấn Mệnh đề where Sử dụng mệnh đề where để lọc phần tử từ liệu nguồn dựa nhiều điều kiện Ví dụ mệnh đề where có điều kiện IEnumerable queryCityPop = from city in cities where city.Population < 200000 && city.Population > 100000 select city; Mệnh đề orderby Sử dụng mệnh đề orderby để xếp kết theo thứ tự tăng dần giảm dần Bạn định thứ tự xếp Ví dụ sau thực xếp đối tượng country cách sử dụng thuộc tính Area Sau đó, thực xếp cách sử dụng thuộc tính Population IEnumerable querySortedCountries = from country in countries orderby country.Area, country.Population descending select country; Từ khóa ascending tùy chọn; thứ tự xếp mặc định Học kết hợp Trang HỌC PHẦN: LẬP TRÌNH NET Mệnh đề join Sử dụng mệnh đề join để liên kết và/hoặc kết hợp phần tử từ nguồn liệu với phần tử từ nguồn liệu khác dựa so sánh khóa định phần tử Trong LINQ, phép nối thực chuỗi đối tượng mà phần tử chúng có kiểu khác Sau bạn kết nối hai chuỗi, bạn phải sử dụng câu lệnh select group để định phần tử lưu chuỗi kết Bạn sử dụng kiểu vơ danh để kết hợp thuộc tính từ phần tử liên kết thành loại cho chuỗi kết Ví dụ sau liên kết đối tượng prod có thuộc tính Category khớp với category mảng chuỗi categories Các product có Category khơng khớp với chuỗi categories loại Câu lệnh select chiếu kiểu có thuộc tính lấy từ cat prod var categoryQuery = from cat in categories join prod in products on cat equals prod.Category select new { Category = cat, Name = prod.Name }; Bạn thực kết nối nhóm cách lưu trữ kết hoạt động kết nối vào biến tạm cách sử dụng từ khóa into 1.4.5 Mệnh đề let Sử dụng mệnh đề let để lưu trữ kết biểu thức, lời gọi phương thức, biến phạm vi Trong ví dụ sau, biến firstName lưu trữ phần tử mảng chuỗi trả Split string[] names = { "Svetlana Omelchenko", "Claire O'Donnell", "Sven Mortensen", "Cesar Garcia" }; IEnumerable queryFirstNames = from name in names let firstName = name.Split(' ')[0] select firstName; foreach (string s in queryFirstNames) Console.Write(s + " "); Kết quả: Output: Svetlana Claire Sven Cesar 1.5 Cú pháp truy vấn cú pháp phương thức Có hai cách để viết truy vấn LINQ cú pháp truy vấn (query syntax) cú pháp phương thức (method syntax) Học kết hợp Trang 10 HỌC PHẦN: LẬP TRÌNH NET Cú pháp truy vấn: cú pháp tương tự truy vấn SQL, có mệnh đề select, from, where, join truy vấn viết code C# VB.NET theo cú pháp sau: from in product.Price == 400); Phương thức mở rộng Biểu thức lambda Cú pháp pha trộn: thực tế số phương thức LINQ khơng hỗ cú pháp truy vấn, ta sử dụng cách viết pha trộn hai loại cú pháp ORM EF 2.1 ORM (Object Relational Mapping) Khi làm việc với hệ thống hướng đối tượng, có khơng khớp mơ hình đối tượng sở liệu quan hệ RDBMS thể liệu định dạng bảng, ngôn ngữ hướng đối tượng đại diện cho liệu đồ thị kết nối đối tượng (ORM) đời nhằm giải toán ánh xạ table-to-object object-to-table ORM kỹ thuật thực ánh xạ CSDL sang đối tượng ngơn ngữ lập trình hướng đối tượng Trong ORM bảng tương ứng class, mối ràng buộc bảng tương ứng quan hệ class Học kết hợp Trang 11 HỌC PHẦN: LẬP TRÌNH NET ORM Là khái niệm phổ biến, cài đặt tất ngôn ngữ đại như: c#, java, php, node.js, … Tại nên cài đặt ORM để truy xuất liệu?  Portable – tính động: ORM sử dụng để viết cấu trúc lần lớp ORM xử lý câu lệnh cuối phù hợp với DBMS cấu hình Đây lợi tuyệt vời thao tác truy xuất liệu đơn giản giới hạn thêm vào dạng ‘limit 0,100’ cuối câu lệnh Select MySQL, với cách truy xuất thông thường phải viết ‘Select Top 100 From Table’ MS SQL  Nesting Of Data – truy xuất lồng liệu: trường hợp database có nhiều bảng bảng liên hệ phức tạp liệu ORM tự động lấy liệu cách đơn giản (ở không bàn vấn đề tối ưu truy xuất)  Single Language – không cần biết SQL: với nguyên lý thiết kế ánh xạ toàn liệu lấy từ DBMS sang nhớ nên việc thao tác truy xuất phụ thuộc vào ngơn ngữ lập trình bạn xử dụng, bạn chẳng cần quan tâm phía đằng sau ORM làm sinh mã SQL truy xuất SQL, kết cần nhuần nhuyễn ngôn ngữ lập trình dùng  Adding is like modifying – thêm sửa liệu nhau: ORM, không phân biệt thêm cập nhật tác vụ có liên quan đến sửa đổi hay chèn liệu xem định nghĩa thêm mới, hai tác vụ xem 2.2 EF – ENTITY FRAMEWORK  EF cách thực ORM NET Đây cải tiến ADO.NET, cung cấp cho nhà phát triển chế tự động để truy cập lưu trữ liệu sở liệu  Các tính năng: + Cung cấp chế để truy cập lưu trữ liệu vào CSDL + Cung cấp dịch vụ theo dõi thay đổi liệu, dịch truy vấn + Sử dụng LINQ to Entities để truy vấn, thêm, sửa, xóa liệu + Tăng khả bảo trì mở rộng cho ứng dụng Kiến trúc EF Học kết hợp Trang 12 HỌC PHẦN: LẬP TRÌNH NET  EDM (Entity Data Model - mơ hình liệu thực thể): EDM chứa thành phần - Conceptual model, Mapping Storage model  Conceptual model - mơ hình khái niệm: mơ hình class mối quan hệ chúng Mơ hình độc lập với thiết kế bảng csdl  Storage Model – mơ hình lưu trữ: mơ hình thiết kế csdl, bao gồm tables, views, stored procedures, mối quan hệ chúng khóa  Mapping – mơ hình ánh xạ: chứa thơng tin cách mơ hình khái niệm ánh xạ đến mơ hình lưu trữ  LINQ to Entities: ngôn ngữ truy vấn sử dụng để truy vấn mơ hình đối tượng Nó trả lại thực thể định nghĩa mơ hình khái niệm  Entity SQL: ngôn ngữ truy vấn giống LINQ to Entities Tuy nhiên phức tạp LINQ to Entity chút người phát triển cần học riêng  Object Service: tự động sinh class tương ứng với mô hình liệu  Entity Client Data Provider: trách nhiệm tầng chuyển đổi truy vấn LINQ to Entites Entity SQL thành câu lệnh truy vấn SQL Nó giao tiếp với CSDL thơng qua ADO.Net để gửi nhận liệu từ csdl  ADO.Net Data Provider: tầng giao tiếp với CSDL sử dụng ADO.NET tiêu chuẩn Entity Framework Core (EF Core) EF Core phiên EF Nó phiên nhẹ, mở rộng, mã nguồn mở đa tảng EF EF Core làm việc ánh xạ ORM, cho phép : + Các nhà phát triển NET làm việc với sở liệu sử dụng đối tượng NET + Loại bỏ hầu hết lệnh truy cập liệu thường phải viết Có thể truy cập nhiều csdl khác SQL Server, Sqllite, MySQL … thông qua thư viện plug-in gọi Database Providers Học kết hợp Trang 13 HỌC PHẦN: LẬP TRÌNH NET 3.1.1 Model (mơ hình liệu) Với EF Core, việc truy cập liệu thực sử dụng Model Một model tạo thành từ lớp thực thể (Entities) đối tượng context đại diện cho phiên làm việc với sở liệu (DbContext) Đối tượng context cho phép truy vấn lưu liệu EF hỗ trợ cách tiếp cận phát triển model sau: • Tạo model từ sở liệu có (Database First) • Viết code model để tạo sở liệu (Code First) • Ngay model tạo, sử dụng EF Migrations để tạo sở liệu từ mơ hình 3.1.2 Truy vấn liệu Các instance lớp thực thể truy xuất từ sở liệu cách sử dụng LINQ using (var db = new BloggingContext()) { var blogs = db.Blogs Where(b => b.Rating > 3) OrderBy(b => b.Url) ToList(); } 3.1.3 Cập nhật liệu liệu tạo, xóa sửa đổi sở liệu cách sử dụng instances lớp thực thể using (var db = new BloggingContext()) { var blog = new Blog { Url = "http://sample.com" }; db.Blogs.Add(blog); db.SaveChanges(); } Truy vấn liệu sử dụng EF Core Theo cách tiếp cận Database First, EF Core API tạo lớp thực thể Context dựa sở liệu có cách sử dụng lệnh EF Core Các bước để truy vấn liệu sử dụng EF Core B1 Thêm thư viện gồm EF Core số package khác tùy mục đích sử dụng B2 Tạo model B3 Truy vấn liệu sử dụng LINQ Học kết hợp Trang 14 HỌC PHẦN: LẬP TRÌNH NET 4.1 Thêm package cần thiết EF Core thành phần NET tiêu chuẩn Ta cần cài đặt NuGet package cho hai thành phần sau để sử dụng EF Core ứng dụng: Data Provider EF Core Công cụ EF Core 4.1.1 Cài đặt Data Provider EF Core cho phép truy cập sở liệu qua Data Provider Data Provider tập hợp lớp cho phép ta giao tiếp hiệu với sở liệu EF Core có Data Provider cho sở liệu khác Các Data Provider có sẵn dạng gói NuGet., bảng sau: Cơ sở liệu NuGet packate Microsoft SQL Server 2012 trở lên Microsoft.EntityFrameworkCore.SqlServer SQLLite 3.7 trở lên Microsoft.EntityFrameworkCore.Sqlite MySQL MySql.EntityFrameworkCore Oracle DB 11.2 Oracle.EntityFrameworkCore Azure Cosmos DB SQL API Microsoft.EntityFrameworkCore.Cosmos Xem danh sách đầy đủ EF Core Data Provider có theo link sau: https://docs.microsoft.com/en-us/ef/core/providers/?tabs=dotnet-core-cli Ta cài đặt NuGet package theo hai cách sau: Cách 1: Để cài đặt gói NuGet DataProvider, ta nhấp phải chuột vào project cửa sổ Solution Explorer chọn  Manage NuGet Packages for Solution ( chọn menu Tools  NuGet Package Manager  Manage NuGet Packages for Solution ) Học kết hợp Trang 15 HỌC PHẦN: LẬP TRÌNH NET Giao diện trình quản lý gói NuGet mở Trong cửa sổ NuGet –Solution  chọn tab Browse tìm kiếm gói muốn cài đặt hình sau: Chọn DataProvider muốn cài Trong trường hợp ta muốn truy cập sở liệu SQL Server ta cần cài đặt Microsoft.EntityFrameworkCore.SqlServer, đảm bảo có logo NET tác giả Microsoft  nhấn Install để cài đặt Cửa sổ xem trước hiển thị danh sách gói cài đặt ứng dụng, xem lại thay đổi nhấn OK Học kết hợp Trang 16 HỌC PHẦN: LẬP TRÌNH NET Cuối chấp nhận điều khoản cấp phép liên quan đến gói cài đặt Sau kiểm tra xem gói Microsoft.EntityFrameworkCore.SqlServer cài đặt Dependencies  NuGet sau Học kết hợp Trang 17 HỌC PHẦN: LẬP TRÌNH NET Cách 2: Cài đặt package Package Manager Console • Chọn menu Tools  NuGet Package Manager  Package Manager Console • Nhập lệnh theo cú pháp sau vào cửa sổ Package Manager Console PM> Install-Package tên-package –Version tên-phiên-bản + Nếu khơng có tùy chọn –Version, mặc định cài phiên Ví dụ: PM> Install-Package Microsoft.EntityFrameworkCore.SqlServer -Version 5.0.6 4.1.2 Cài đặt EF Core Tool Để thực thi lệnh EF Core ta cần cài đặt NuGet package Microsoft.EntityFrameworkCore.Tools, điều giúp ta thực nhiệm vụ liên quan đến EF Core dự án thời điểm thiết kế scaffolding … Cài đặt EFCore Tool thực tương tự cài đặt DataProvider Kiểm tra xem gói Microsoft.EntityFrameworkCore.Tools cài đặt thành công Dependencies  NuGet: Học kết hợp Trang 18 HỌC PHẦN: LẬP TRÌNH NET 4.2 Tạo model Trong phần ta sử dụng công cụ để tạo lớp biểu diễn mơ hình liệu cho sở liệu có sẵn, sử dụng kỹ thuật đảo ngược 4.2.1 Chuẩn bị sở liệu Tạo sở liệu QLBanHang gồm bảng LoaiSanPham SanPham có lược đồ sở liệu sau: Thiết kế bảng: 4.2.2 Tạo model EF Core không hỗ trợ thiết kế trực quan cho mơ hình sở liệu, tạo lớp thực thể lớp Context phiên trước Thay vào ta sử dụng lệnh ScaffoldDbContext Lệnh tạo lớp thực thể Context (bằng cách dẫn xuất từ lớp DbContext) dựa lược đồ sở liệu có Trong cửa sổ Package Manager Console thực lệnh Scaffold-DbContext sau: Scaffold-DbContext “chuỗi-kết-nối“ tên-DataProvider –OutputDir tên-thư-mục Ví dụ: Scaffold-DbContext "Data Source=MyPC;Initial Catalog=QLBanHang;Integrated Security=True" Microsoft.EntityFrameworkCore.SqlServer –OutputDir Models Học kết hợp Trang 19 HỌC PHẦN: LẬP TRÌNH NET  Tham số chuỗi kết nối gồm ba phần: máy chủ sở liệu, tên sở liệu thơng tin bảo mật Ví dụ, chuỗi kết nối: "Data Source=PC;Initial Catalog=QLBanHang;Integrated Security=True" + Máy chủ sở liệu : MyPC + Tên sở liệu là: QLBanHang + Kết nối với SQL Server sử dụng xác thực Windows Authentication  Tham số thứ hai tên DataProvider, ta sử dụng sở liệu SQL Server nên ví dụ tên DataProvider Microsoft.EntityFrameworkCore.SqlServer  Tham số thứ ba tên thư mục, nơi mà ta muốn lưu tất lớp thực thể, ví dụ thư mục Models project Lệnh Scaffold-DbContext tạo lớp thực thể cho bảng sở liệu QLBanHang lớp Context với cấu hình cho tất thực thể thư mục Models Sau lớp thực thể LoaiSanPham tạo từ bảng LoaiSanPham lớp thực thể SanPham tạo từ bảng SanPham using System; using System.Collections.Generic; #nullable disable namespace DemoEFCore.Models { public partial class LoaiSanPham { public LoaiSanPham() { SanPhams = new HashSet(); } Học kết hợp Trang 20 HỌC PHẦN: LẬP TRÌNH NET public string MaLoai { get; set; } public string TenLoai { get; set; } public virtual ICollection SanPhams { get; set; } } } using System; using System.Collections.Generic; #nullable disable namespace DemoEFCore.Models { public partial class SanPham { public string MaSp { get; set; } public string TenSp { get; set; } public string MaLoai { get; set; } public int? SoLuong { get; set; } public int? DonGia { get; set; } public virtual LoaiSanPham MaLoaiNavigation { get; set; } } } Lớp thực thể (Entity class): Các lớp thực thể ánh xạ vào bảng bên sở liệu Các thuộc tính lớp ánh xạ vào cột bảng Mỗi thể lớp thực thể biểu diễn dịng bảng Trong ví dụ trên, lớp thực thể SanPham ánh xạ vào bảng SanPham sở liệu Các thuộc tính MaSP, TenSP … ánh xạ cột MaSP, TenSP… bảng sản phẩm Mỗi thể lớp SanPham biểu diễn dòng bảng SanPham Lớp thực thể LoaiSanPham ánh xạ vào bảng LoaiSanPham, thể lớp LoaiSanPham biểu diễn dòng bảng LoaiSanPham Quan hệ lớp thực thể: Được tự động tạo dựa mối quan hệ primary key/foreign key CSDL, thuộc tính tương ứng thêm vào lớp thực thể Ví dụ: quan hệ lớp thực thể LoaiSanPham SanPham tạo dựa quan hệ 1–nhiều bảng LoaiSanPham bảng SanPham Mối quan hệ làm lớp thực thể SanPham có thêm thuộc tính “LoaiSanPham”, dùng để truy cập vào thực thể LoaiSanPham SanPham Lớp LoaiSanPham có thêm thuộc tính “SanPhams”, tập hợp cho phép ta lấy tất SanPham có LoaiSanPham Học kết hợp Trang 21 HỌC PHẦN: LẬP TRÌNH NET Sau lớp QLBanHangContext sử dụng để lưu truy xuất liệu using System; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata; #nullable disable namespace DemoEFCore.Models { public partial class QLBanHangContext : DbContext { public QLBanHangContext() { } public QLBanHangContext(DbContextOptions options) : base(options) { } public virtual DbSet LoaiSanPhams { get; set; } public virtual DbSet SanPhams { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { if (!optionsBuilder.IsConfigured) { #warning To protect potentially sensitive information in your connection string, you should move it out of source code You can avoid scaffolding the connection string by using the Name= syntax to read it from configuration - see https://go.microsoft.com/fwlink/?linkid=2131148 For more guidance on storing connection strings, see http://go.microsoft.com/fwlink/?LinkId=723263 optionsBuilder.UseSqlServer("Data Source=NhungNT;Initial Catalog=QLBanHang;Integrated Security=True"); } } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.HasAnnotation("Relational:Collation", "SQL_Latin1_General_CP1_CI_AS"); modelBuilder.Entity(entity => { entity.HasKey(e => e.MaLoai) HasName("PK LoaiSanP 730A575920049E9B"); Học kết hợp Trang 22 HỌC PHẦN: LẬP TRÌNH NET entity.ToTable("LoaiSanPham"); entity.Property(e => e.MaLoai) HasMaxLength(3) IsUnicode(false) IsFixedLength(true); entity.Property(e => e.TenLoai) IsRequired() HasMaxLength(50); }); modelBuilder.Entity(entity => { entity.HasKey(e => e.MaSp) HasName("PK SanPham 2725081CACF60F4F"); entity.ToTable("SanPham"); entity.Property(e => e.MaSp) HasMaxLength(4) IsUnicode(false) HasColumnName("MaSP") IsFixedLength(true); entity.Property(e => e.MaLoai) HasMaxLength(3) IsUnicode(false) IsFixedLength(true); entity.Property(e => e.TenSp) IsRequired() HasMaxLength(50) HasColumnName("TenSP"); entity.HasOne(d => d.MaLoaiNavigation) WithMany(p => p.SanPhams) HasForeignKey(d => d.MaLoai) HasConstraintName("FK SanPham MaLoai 1273C1CD"); }); OnModelCreatingPartial(modelBuilder); } partial void OnModelCreatingPartial(ModelBuilder modelBuilder); } } Lớp DbContext phần thiếu Entity Framework Một thể DbContext đại diện cho phiên làm việc với sở liệu, sử dụng để truy vấn lưu thể thực thể bạn vào sở liệu Học kết hợp Trang 23 HỌC PHẦN: LẬP TRÌNH NET Để sử dụng DbContext ứng dụng, cần tạo lớp kế thừa từ lớp DbContext, gọi lớp Context Lớp Context có thuộc tính Dbset cho thực thể mơ hình 4.2.3 Truy vấn liệu sử dụng LINQ Thiết kế giao diện: Tạo window để xem cập nhật liệu bảng LoaiSanPham sau: XAML để tạo giao diện: Mã loại sản phẩm: Học kết hợp Trang 24 HỌC PHẦN: LẬP TRÌNH NET Tên loại sản phẩm: Thêm Sửa Xóa Thoát Truy vấn liệu: Sau định nghĩa mơ hình liệu, dễ dàng truy vấn lấy liệu từ sở liệu EF Core cho phép bạn làm điều cách viết câu truy vấn dùng cú pháp LINQ với lớp Context tạo Ví dụ, để lấy duyệt qua tập đối tượng LoaiSanPham, ta viết: //Khởi tạo thể lớp Context QLBanHangContext db = new QLBanHangContext(); //truy vấn liệu sử dụng lớp Context var query = from lsp in db.LoaiSanPhams select lsp; //Hiển thị liệu lên data grid dgvLoaiSanPham.ItemsSource = query.ToList(); Để lấy tập đối tượng thỏa mãn điều kiện, sử dụng mệnh đề where truy vấn Ví dụ, lấy loại sản phẩm có tên loại bắt đầu Đ var query = from lsp in db.LoaiSanPhams where lsp.TenLoai.StartsWith("Đ") select new { lsp.MaLoai, lsp.TenLoai }; Trong câu truy vấn trên, ta dùng mệnh đề where cú pháp LINQ để trả loại sản phẩm mà tên bắt đầu ký tự Đ Ta tùy biến câu lệnh nắm bắt Học kết hợp Trang 25 HỌC PHẦN: LẬP TRÌNH NET ưu điểm mối quan hệ thực thể tạo mơ hình hóa lớp làm cho câu lệnh phong phú tự nhiên Ví dụ, lấy loại sản phẩm có từ sản phẩm trở lên var query = from lsp in db.LoaiSanPhams where lsp.SanPhams.Count >= select lsp; Chú ý cách dùng tập hợp SanPhams mà EF tạo lớp LoaiSanPham nhờ vào mối quan hệ – nhiều mơ hình hóa Tập hợp SanPhams chứa sản phẩm loại sản phẩm xét đến Cập nhật sở liệu sử dụng EF core Như ví dụ trước, dễ dàng dùng truy vấn LINQ để lấy liệu từ sở liệu cách dùng lớp Context Ví dụ: ta viết biểu thức LINQ sau để lấy đối tượng loại sản phẩm có nhiều sản phẩm var loaiSanPhamQuery = from lsp in db.LoaiSanPhams where lsp.SanPhams.Count>=2 select lsp; Khi ta thực truy vấn lấy đối tượng đối tượng loại sản phẩm ví dụ trên, LINQ lưu lại vết tất thay đổi hay cập nhật mà ta thực đối tượng (gọi change tracking) Chúng ta thực câu truy vấn thay đổi mà muốn cách dùng LINQ, tất thay đổi lưu vết lại Sau cập nhật đối tượng lấy từ LINQ, gọi phương thức ”SaveChanges()” lớp Context để lưu thay đổi lên CSDL 5.1 Thêm liệu Thực bước sau để thêm ghi vào CSDL: • Tạo (các) đối tượng thuộc lớp thực thể muốn thêm • Gán giá trị cho thuộc tính (các) đối tượng • Thêm đối tượng vào tập hợp đối tượng tương ứng sử dụng phương thức Add() AddRange()của lớp Context • Thực thi phương thức SaveChanges() lớp Context để cập nhật thay đổi vào csdl Ví dụ: thêm loại sản phẩm vào csdl //1 tạo đối tượng loại sản phẩm có thuộc tính user nhập LoaiSanPham lspMoi = new LoaiSanPham(); lspMoi.MaLoai = txtMa.Text; lspMoi.TenLoai = txtTen.Text; Học kết hợp Trang 26 HỌC PHẦN: LẬP TRÌNH NET //2.Thêm vào tập hợp LoaiSanPhams db.LoaiSanPhams.Add(lspMoi); //3.Lưu thay đổi vào csdl db.SaveChanges(); 5.2 Sửa liệu Thực bước sau để sửa liệu: • Sử dụng LINQ lấy đối tượng muốn sửa liệu • Sửa thơng tin đối tượng • Thực thi phương thức SaveChanges() lớp Context để cập nhật thay đổi vào csdl Ví dụ: sửa thơng tin loại sản phẩm có mã text box txtMa //lấy sản phẩm muốn sửa var spSua = db.LoaiSanPhams.SingleOrDefault( lsp => lsp.MaLoai == txtMa.Text); //cập nhật thông tin sản phẩm spSua.TenLoai = txtTen.Text; //Lưu thay đổi vào csdl db.SaveChanges(); 5.3 Xóa liệu Thực bước sau để xóa liệu: • Sử dụng LINQ lấy (các) đối tượng muốn xóa • Xóa (các) đối tượng khỏi tập hợp tương ứng lớp Context sử dụng phương thức Remove() RemoveRange() • Thực thi phương thức SubmitChanges() DataContext để cập nhật thay đổi vào csdl Ví dụ: xóa loại sản phẩm có mã text box txtMa //1 lấy sản phẩm muốn xóa var spSua = db.LoaiSanPhams.SingleOrDefault(lsp => lsp.MaLoai == txtMa.Text); //2 xóa đối tượng khỏi tập hợp db.LoaiSanPhams.Remove(spSua); //3 lưu thay đổi vào csdl db.SaveChanges(); Ví dụ 11.1: Truy vấn, cập nhật liệu bảng – bảng Loại sản phẩm Giao diện Học kết hợp Trang 27 HỌC PHẦN: LẬP TRÌNH NET Yêu cầu: - Ngay hiển thị cửa sổ, liệu bảng Loại sản phẩm hiển thị data grid - Người dùng nhập mã loại, tên loại sản phẩm sau nhấn nút Thêm, loại sản phẩm thêm vào bảng Có kiểm tra không cho thêm loại sản phẩm trùng mã Hiển thị lại thông tin sau thêm - Người dùng nhập mã tên loại sản phẩm muốn sửa, sau nhấn nút Sửa, thơng tin loại sản phẩm lưu vào bảng Có thơng báo khơng tìm thấy loại sản phẩm muốn sửa Hiển thị lại thông tin sau sửa - Người dùng nhập mã loại sản phẩm muốn xóa, sau nhấn nút Xóa, loại sản phẩm bị xóa khỏi bảng Có thơng báo khơng tìm thấy loại sản phẩm muốn xóa Hiển thị lại thơng tin sau xóa Code cửa sổ Loại sản phẩm using System; using System.Collections.Generic; using System.Linq; using System.Windows; using DemoEFCore.Models; namespace DemoEFCore { /// /// Interaction logic for WindowLoaiSanPham.xaml /// public partial class WindowLoaiSanPham : Window { public WindowLoaiSanPham() { InitializeComponent(); } //Tạo thể lớp Context Học kết hợp Trang 28 HỌC PHẦN: LẬP TRÌNH NET QLBanHangContext db = new QLBanHangContext(); private void Window_Loaded(object sender, RoutedEventArgs e) { HienThiDuLieu(); } private void HienThiDuLieu() { //Truy vấn liệu sử dụng LINQ var query = from lsp in db.LoaiSanPhams select lsp; //Hiển thị liệu lên data grid dgvLoaiSanPham.ItemsSource = query.ToList(); } private void btnThem_Click(object sender, RoutedEventArgs e) { //Tạo đối tượng lớp Loại sản phẩm LoaiSanPham lspMoi = new LoaiSanPham(); //Thuộc tính đối tượng loại sản phẩm user nhập vào lspMoi.MaLoai = txtMa.Text; lspMoi.TenLoai = txtTen.Text; //nếu chưa có loại sản phẩm thêm, ngược lại thông báo lỗi if (!db.LoaiSanPhams.Contains(lspMoi)) { //Thêm đối tượng vào tập hợp loại sản phẩm db.LoaiSanPhams.Add(lspMoi); //Lưu thay đổi vào sở liệu db.SaveChanges(); //Hiển thị lại liệu sau thêm HienThiDuLieu(); } else { MessageBox.Show("Đã có sản phẩm " + txtMa.Text, "THÊM DỮ LIỆU", MessageBoxButton.OK, MessageBoxImage.Error); } } private void btnSua_Click(object sender, RoutedEventArgs e) { //Lấy đối tượng loại sản muốn sửa var lspSua = (from lsp in db.LoaiSanPhams where lsp.MaLoai == txtMa.Text select lsp).SingleOrDefault(); //Nếu có sản phẩm muốn sửa thực cập nhật thông tin, //ngược lại hiển thị thông báo if (lspSua != null) { //sửa thông tin sản phẩm không sửa mã lspSua.TenLoai = txtTen.Text; //lưu thay đổi vào sở liệu db.SaveChanges(); //Hiển thị lại liệu sau sửa HienThiDuLieu(); } Học kết hợp Trang 29 HỌC PHẦN: LẬP TRÌNH NET else { MessageBox.Show("Khơng có sản phẩm mã " + txtMa.Text, "SỬA DỮ LIỆU", MessageBoxButton.OK, MessageBoxImage.Information); } } private void btnXoa_Click(object sender, RoutedEventArgs e) { //Lấy đối tượng loại sản muốn xóa var lspXoa = (from lsp in db.LoaiSanPhams where lsp.MaLoai == txtMa.Text select lsp).SingleOrDefault(); //Nếu có sản phẩm muốn xóa thực xóa, ngược lại hiển //thị thơng báo if (lspXoa != null) { //xóa sản phẩm db.LoaiSanPhams.Remove(lspXoa); //lưu thay đổi vào sở liệu db.SaveChanges(); //Hiển thị lại liệu sau xóa HienThiDuLieu(); } else { MessageBox.Show("Khơng có sản phẩm mã " + txtMa.Text, "XÓA DỮ LIỆU", MessageBoxButton.OK, MessageBoxImage.Information); } } } } Ví dụ 11.2: Cập nhật liệu bảng – bảng Hóa đơn Hóa đơn chi tiết Giao diện: Yêu cầu: Học kết hợp Trang 30 HỌC PHẦN: LẬP TRÌNH NET - Khi hiển thị cửa sổ lập hóa đơn, ngày lập lấy ngày hệ thống - Người dùng nhập mã sản phẩm, số lượng mua vào text box sau nhấn nút Thêm hàng mua, thơng tin mặt hàng vừa nhập hiển thị lên data grid Trong tên sản phẩm, đơn giá lấy từ bảng Sản phẩm, Thành tiền = số lượng mua * đơn giá - Sau nhập thơng tin hóa đơn, thơng tin hàng mua Người dùng nhấn nút Lập hóa đơn thơng tin hóa đơn lưu vào bảng Hóa đơn, thơng tin hàng mua lưu vào bảng Hóa đơn chi tiết Code cửa sổ Lập hóa đơn: using System; using System.Collections.Generic; using System.Data; using System.Linq; using System.Reflection; using System.Windows; using DemoEFCore.Models; namespace DemoEFCore { /// /// Interaction logic for WindowLapHoaDon.xaml /// public partial class WindowLapHoaDon : Window { public WindowLapHoaDon() { InitializeComponent(); } QLBanHangContext db = new QLBanHangContext(); private void Window_Loaded(object sender, RoutedEventArgs e) { txtNgay.Text = DateTime.Now.ToString("dd-MM-yyyy"); } private void btnThem_Click(object sender, RoutedEventArgs e) { //Lấy sản phẩm có mã nhập SanPham spMua = new SanPham(); spMua = db.SanPhams.Where(sp => sp.MaSp == txtMaSP.Text).SingleOrDefault(); if (spMua!=null) { //Thêm sản phẩm mua vào data grid dgvSanPham.Items.Add(new { MaSP = spMua.MaSp, TenSP = spMua.TenSp, SoLuong = txtSoLuong.Text, spMua.DonGia, ThanhTien = int.Parse(txtSoLuong.Text) * spMua.DonGia }); //Xóa điều khiển Học kết hợp Trang 31 HỌC PHẦN: LẬP TRÌNH NET txtMaSP.Text = ""; txtSoLuong.Text = ""; txtMaSP.Focus(); } else { MessageBox.Show("Khơng có sản phẩm mã " + txtMaSP.Text, "LẬP HĨA ĐƠN", MessageBoxButton.OK, MessageBoxImage.Error); //Xóa điều khiển text box Mã sản phẩm txtMaSP.Text = ""; txtMaSP.Focus(); } } private void btnLapHoaDon_Click(object sender, RoutedEventArgs e) { //tạo đối tượng Hóa đơn HoaDon hdMoi = new HoaDon(); hdMoi.MaHd = txtMaHD.Text; hdMoi.NguoiLap = txtNguoiLap.Text; //Thêm vào tập hợp Hóa đơn db.HoaDons.Add(hdMoi); //Thêm hóa đơn chi tiết foreach (var item in dgvSanPham.Items) { Type t = item.GetType(); PropertyInfo[] sanPhamInfo = t.GetProperties(); HoaDonChiTiet hdctMoi = new HoaDonChiTiet(); hdctMoi.Mahd = hdMoi.MaHd; hdctMoi.MaSp = sanPhamInfo[0].GetValue(item).ToString(); hdctMoi.SoLuongMua = Convert.ToInt32(sanPhamInfo[2].GetValue(item)); db.HoaDonChiTiets.Add(hdctMoi); } //Lưu vào sở liệu db.SaveChanges(); MessageBox.Show("Đã lưu hóa đơn vào sở liệu", "LẬP HÓA ĐƠN", MessageBoxButton.OK, MessageBoxImage.Information); } } } Học kết hợp Trang 32

Ngày đăng: 19/09/2023, 00:30

Tài liệu cùng người dùng

Tài liệu liên quan