---
# System prepended metadata

title: Tài liệu MVC - Model - Controller - View (1)

---

Dưới đây là tài liệu về chủ đề "Data Validation and Annotation" trong lập trình MVC. Tài liệu này bao gồm các phần sau: Data Validation, Data Annotation, và ModelState Validation. Mỗi phần sẽ đi sâu vào từng khía cạnh và cung cấp ví dụ cụ thể.

---

## Session 5 – Data Validation and Annotation

### Data Validation

#### Khái niệm Data Validation

Data validation là quá trình kiểm tra tính hợp lệ của dữ liệu trước khi nó được xử lý hoặc lưu trữ. Trong ngữ cảnh lập trình MVC, việc kiểm tra tính hợp lệ của dữ liệu là một phần quan trọng để đảm bảo rằng ứng dụng của bạn hoạt động một cách đáng tin cậy và an toàn.

#### Data Validation trong MVC

MVC cung cấp nhiều cách để thực hiện data validation, bao gồm:

1. **Server-Side Validation**: Kiểm tra dữ liệu trên máy chủ trước khi nó được chấp nhận. Điều này thường được thực hiện trong phương thức hành động của controller trước khi nó được xử lý.

2. **Client-Side Validation**: Kiểm tra dữ liệu trên phía máy khách bằng JavaScript trước khi dữ liệu được gửi đến máy chủ. Điều này giúp tối ưu hóa trải nghiệm người dùng.

### Data Annotation

#### Data Annotation là gì?

Data Annotation là một cách để đánh dấu các thành phần của mô hình dữ liệu của bạn bằng các thuộc tính hoặc metadata để xác định cách dữ liệu nên được xử lý hoặc kiểm tra tính hợp lệ.

#### Sử dụng Data Annotation

MVC sử dụng Data Annotation để áp dụng các kiểm tra tính hợp lệ vào các thành phần của mô hình dữ liệu của bạn. Dưới đây là một số Data Annotation phổ biến:

- `[Required]`: Đánh dấu một trường là bắt buộc.
- `[StringLength(maximumLength)]`: Xác định độ dài tối đa cho một chuỗi.
- `[Range(minimum, maximum)]`: Xác định một phạm vi giá trị cho một số nguyên hoặc số thực.
- `[RegularExpression(pattern)]`: Xác định một biểu thức chính quy để kiểm tra tính hợp lệ của một chuỗi.

### ModelState Validation

#### ModelState là gì?

`ModelState` là một đối tượng trong MVC giúp theo dõi trạng thái của mô hình dữ liệu và lưu trữ các thông báo lỗi.

#### ModelState Validation

Trong quá trình xử lý yêu cầu, MVC kiểm tra dữ liệu đầu vào của người dùng với các Data Annotation được áp dụng trên mô hình dữ liệu. Nếu có lỗi, các thông báo lỗi được thêm vào `ModelState`. Sau đó, bạn có thể kiểm tra `ModelState.IsValid` để xác định xem có lỗi hay không và hiển thị thông báo lỗi cho người dùng nếu cần.

Ví dụ:

```csharp
[HttpPost]
public ActionResult Create(Employee employee)
{
    if (ModelState.IsValid)
    {
        // Xử lý lưu dữ liệu vào cơ sở dữ liệu
        // ...
        return RedirectToAction("Index");
    }
    return View(employee);
}
```

---

Tài liệu này đã cung cấp một cái nhìn tổng quan về Data Validation, Data Annotation và ModelState Validation trong lập trình MVC. Để hiểu chi tiết hơn và thực hành, bạn nên tìm hiểu thêm qua ví dụ cụ thể và tài liệu hướng dẫn chi tiết của Microsoft hoặc các nguồn tài liệu khác về MVC.

<hr>

Dưới đây là tài liệu về chủ đề "Data Access" trong lập trình MVC, bao gồm các phần sau: Entity Framework, Working with Entity Framework, Initializing a Database with Test Data, LINQ Query, và LINQ Data Providers. Mỗi phần sẽ đi sâu vào từng khía cạnh và cung cấp ví dụ cụ thể.

---

## Session 6 – Data Access

### Entity Framework

#### Khái niệm Entity Framework

Entity Framework (EF) là một công cụ ORM (Object-Relational Mapping) trong .NET cho phép bạn làm việc với cơ sở dữ liệu bằng cách sử dụng các đối tượng và classes thay vì truy vấn trực tiếp cơ sở dữ liệu.

#### Working with Entity Framework

- **Model-First**: Tạo mô hình dữ liệu trước và sau đó sinh mã cho cơ sở dữ liệu từ mô hình đó.
- **Code-First**: Viết các lớp và thuộc tính, sau đó Entity Framework sẽ tự động tạo cơ sở dữ liệu dựa trên mã nguồn của bạn.
- **Database-First**: Sử dụng một cơ sở dữ liệu có sẵn và sau đó tạo các lớp và đối tượng tương ứng với cơ sở dữ liệu đó.

### Initializing a Database with Test Data

Trong quá trình phát triển ứng dụng, bạn có thể cần tạo dữ liệu thử nghiệm để kiểm tra và phát triển ứng dụng của mình. Entity Framework cho phép bạn khởi tạo cơ sở dữ liệu với dữ liệu thử nghiệm bằng cách sử dụng các cơ chế như Code-First Migration hoặc Seed Data.

### LINQ Query

#### Khái niệm LINQ

Language-Integrated Query (LINQ) là một tính năng trong .NET cho phép bạn thực hiện các truy vấn dữ liệu trong mã nguồn C# hoặc VB.NET một cách dễ dàng và bảo mật.

#### Sử dụng LINQ Query

Bạn có thể sử dụng LINQ để truy vấn dữ liệu từ cơ sở dữ liệu của bạn, ví dụ:

```csharp
var query = from employee in dbContext.Employees
            where employee.Department == "IT"
            select employee;
```

### LINQ Data Providers

LINQ cho phép bạn làm việc với nhiều loại dữ liệu khác nhau thông qua LINQ Data Providers. Một số ví dụ phổ biến bao gồm:

- **LINQ to SQL**: Dành cho cơ sở dữ liệu SQL Server.
- **Entity Framework**: Hỗ trợ nhiều loại cơ sở dữ liệu, bao gồm SQL Server, MySQL, Oracle, và nhiều loại khác.
- **LINQ to XML**: Cho phép truy vấn và xử lý dữ liệu XML.

---

Tài liệu này đã cung cấp một cái nhìn tổng quan về Data Access trong lập trình MVC, bao gồm Entity Framework, các cách làm việc với Entity Framework, khởi tạo cơ sở dữ liệu với dữ liệu thử nghiệm, LINQ Query và LINQ Data Providers. Để hiểu chi tiết hơn và thực hành, bạn nên tìm hiểu thêm qua ví dụ cụ thể và tài liệu hướng dẫn chi tiết của Microsoft hoặc các nguồn tài liệu khác về Entity Framework và LINQ.

<hr>

Dưới đây là các ví dụ về các truy vấn LINQ sử dụng các toán tử phổ biến như `select`, `where`, `join`, `left join`, `order by`, và `group by` trên một danh sách các đối tượng ví dụ. Trong ví dụ này, chúng ta sẽ sử dụng một danh sách các đối tượng `Person` để thực hiện các truy vấn LINQ.

```csharp
using System;
using System.Collections.Generic;
using System.Linq;

class Person
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int Age { get; set; }
    public int DepartmentId { get; set; }
}

class Department
{
    public int DepartmentId { get; set; }
    public string DepartmentName { get; set; }
}

class Program
{
    static void Main()
    {
        List<Person> people = new List<Person>
        {
            new Person { Id = 1, Name = "John", Age = 25, DepartmentId = 1 },
            new Person { Id = 2, Name = "Alice", Age = 30, DepartmentId = 2 },
            new Person { Id = 3, Name = "Bob", Age = 22, DepartmentId = 1 },
            new Person { Id = 4, Name = "Eve", Age = 35, DepartmentId = 3 },
        };

        List<Department> departments = new List<Department>
        {
            new Department { DepartmentId = 1, DepartmentName = "HR" },
            new Department { DepartmentId = 2, DepartmentName = "IT" },
            new Department { DepartmentId = 3, DepartmentName = "Finance" },
        };

        // Ví dụ truy vấn SELECT và WHERE
        var hrPeople = people.Where(p => p.DepartmentId == 1).Select(p => p.Name);
        Console.WriteLine("People in HR Department:");
        foreach (var name in hrPeople)
        {
            Console.WriteLine(name);
        }

        // Ví dụ truy vấn JOIN
        var peopleWithDepartments = people
            .Join(departments,
                person => person.DepartmentId,
                department => department.DepartmentId,
                (person, department) => new { person.Name, department.DepartmentName });

        Console.WriteLine("\nPeople with Departments:");
        foreach (var item in peopleWithDepartments)
        {
            Console.WriteLine($"{item.Name} works in {item.DepartmentName}");
        }

        // Ví dụ truy vấn LEFT JOIN
        var allPeopleWithDepartments = people
            .GroupJoin(departments,
                person => person.DepartmentId,
                department => department.DepartmentId,
                (person, department) => new { person.Name, Department = department.DefaultIfEmpty() })
            .SelectMany(pd => pd.Department.Select(d => new { pd.Name, Department = d }));

        Console.WriteLine("\nAll People with Departments (including those without departments):");
        foreach (var item in allPeopleWithDepartments)
        {
            Console.WriteLine($"{item.Name} works in {(item.Department == null ? "No Department" : item.Department.DepartmentName)}");
        }

        // Ví dụ truy vấn ORDER BY
        var orderedPeople = people.OrderBy(p => p.Age);
        Console.WriteLine("\nPeople ordered by Age:");
        foreach (var person in orderedPeople)
        {
            Console.WriteLine($"{person.Name} ({person.Age} years old)");
        }

        // Ví dụ truy vấn GROUP BY
        var peopleByDepartment = people.GroupBy(p => p.DepartmentId);
        Console.WriteLine("\nPeople grouped by Department:");
        foreach (var group in peopleByDepartment)
        {
            Console.WriteLine($"Department {group.Key}:");
            foreach (var person in group)
            {
                Console.WriteLine($"- {person.Name}");
            }
        }
    }
}
```

Lưu ý rằng ví dụ trên được thực hiện trong môi trường .NET sử dụng LINQ để truy vấn một danh sách các đối tượng `Person` và `Department`. Các truy vấn LINQ này cho phép bạn thao tác với dữ liệu một cách mạnh mẽ và linh hoạt.

<hr>

Làm ơn xin lỗi về sự nhầm lẫn. Dưới đây là các ví dụ sử dụng cú pháp LINQ Query kiểu "from...join...where...":

```csharp
using System;
using System.Collections.Generic;
using System.Linq;

class Person
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int Age { get; set; }
    public int DepartmentId { get; set; }
}

class Department
{
    public int DepartmentId { get; set; }
    public string DepartmentName { get; set; }
}

class Program
{
    static void Main()
    {
        List<Person> people = new List<Person>
        {
            new Person { Id = 1, Name = "John", Age = 25, DepartmentId = 1 },
            new Person { Id = 2, Name = "Alice", Age = 30, DepartmentId = 2 },
            new Person { Id = 3, Name = "Bob", Age = 22, DepartmentId = 1 },
            new Person { Id = 4, Name = "Eve", Age = 35, DepartmentId = 3 },
        };

        List<Department> departments = new List<Department>
        {
            new Department { DepartmentId = 1, DepartmentName = "HR" },
            new Department { DepartmentId = 2, DepartmentName = "IT" },
            new Department { DepartmentId = 3, DepartmentName = "Finance" },
        };

        // Ví dụ truy vấn SELECT và WHERE bằng cú pháp LINQ
        var hrPeople = from person in people
                      where person.DepartmentId == 1
                      select person.Name;

        Console.WriteLine("People in HR Department:");
        foreach (var name in hrPeople)
        {
            Console.WriteLine(name);
        }

        // Ví dụ truy vấn JOIN bằng cú pháp LINQ
        var peopleWithDepartments = from person in people
                                    join department in departments
                                    on person.DepartmentId equals department.DepartmentId
                                    select new { person.Name, department.DepartmentName };

        Console.WriteLine("\nPeople with Departments:");
        foreach (var item in peopleWithDepartments)
        {
            Console.WriteLine($"{item.Name} works in {item.DepartmentName}");
        }

        // Ví dụ truy vấn LEFT JOIN bằng cú pháp LINQ
        var allPeopleWithDepartments = from person in people
                                       join department in departments
                                       on person.DepartmentId equals department.DepartmentId into pd
                                       from d in pd.DefaultIfEmpty()
                                       select new { person.Name, Department = d };

        Console.WriteLine("\nAll People with Departments (including those without departments):");
        foreach (var item in allPeopleWithDepartments)
        {
            Console.WriteLine($"{item.Name} works in {(item.Department == null ? "No Department" : item.Department.DepartmentName)}");
        }

        // Ví dụ truy vấn ORDER BY bằng cú pháp LINQ
        var orderedPeople = from person in people
                            orderby person.Age
                            select person;

        Console.WriteLine("\nPeople ordered by Age:");
        foreach (var person in orderedPeople)
        {
            Console.WriteLine($"{person.Name} ({person.Age} years old)");
        }

        // Ví dụ truy vấn GROUP BY bằng cú pháp LINQ
        var peopleByDepartment = from person in people
                                 group person by person.DepartmentId into departmentGroup
                                 select new { DepartmentId = departmentGroup.Key, People = departmentGroup };

        Console.WriteLine("\nPeople grouped by Department:");
        foreach (var group in peopleByDepartment)
        {
            Console.WriteLine($"Department {group.DepartmentId}:");
            foreach (var person in group.People)
            {
                Console.WriteLine($"- {person.Name}");
            }
        }
    }
}
```

Mong rằng ví dụ này sẽ giúp bạn hiểu cách sử dụng cú pháp LINQ Query kiểu "from...join...where..." để thực hiện các truy vấn dữ liệu.

<hr>

Trong Linq Query, cơ chế tham số hóa cho phép bạn truyền các biến hoặc tham số từ phạm vi bên ngoài vào biểu thức Lambda để sử dụng trong quá trình thực thi. Cơ chế này cho phép bạn tạo những Linq Query linh hoạt có khả năng thay đổi dựa trên các giá trị bên ngoài.

Trong ví dụ trước, chúng ta sử dụng cơ chế tham số hóa để truyền các biến `people` và `departments` từ phạm vi bên ngoài vào Linq Query.

Ví dụ 1:
```csharp
var hrPeople = from person in people
              where person.DepartmentId == 1
              select person.Name;
```
Ở đây, biến `people` là một danh sách các đối tượng `Person` được sử dụng trong Linq Query để lọc ra những người làm trong phòng HR (DepartmentId == 1).

Ví dụ 2:
```csharp
var peopleWithDepartments = from person in people
                            join department in departments
                            on person.DepartmentId equals department.DepartmentId
                            select new { person.Name, department.DepartmentName };
```
Trong truy vấn JOIN, chúng ta truyền biến `people` và `departments` vào Linq Query để kết hợp thông tin từ cả hai danh sách.

Cơ chế tham số hóa giúp chúng ta xây dựng các truy vấn LINQ hoặc Linq Query mà có thể sử dụng các dữ liệu từ nhiều nguồn khác nhau hoặc tham số đầu vào khác nhau mà không cần phải tạo mã rắc rối và trùng lặp.

Ví dụ 3:
```csharp
var orderedPeople = from person in people
                    orderby person.Age
                    select person;
```
Ở đây, chúng ta sử dụng tham số `person` để sắp xếp danh sách người dựa trên trường `Age`.

Ví dụ 4:
```csharp
var peopleByDepartment = from person in people
                         group person by person.DepartmentId into departmentGroup
                         select new { DepartmentId = departmentGroup.Key, People = departmentGroup };
```
Trong ví dụ này, chúng ta sử dụng tham số `person` và `departmentGroup` để nhóm người theo `DepartmentId`.

Cơ chế tham số hóa giúp việc làm việc với dữ liệu trở nên linh hoạt và tiện lợi trong các truy vấn LINQ hoặc Linq Query.

<hr>

Làm ơn xin lỗi về sự hiểu lầm. Giờ, hãy giải thích cơ chế tham số hóa trong các phương thức mở rộng LINQ như `.Where()`, `.Select()`, `.OrderBy()`, và `.GroupBy()`.

Cơ chế tham số hóa trong các phương thức LINQ cho phép bạn truyền biểu thức hoặc hàm lambda từ phạm vi bên ngoài vào các phương thức này để tùy chỉnh cách dữ liệu được lọc, chọn, sắp xếp hoặc nhóm hóa.

Dưới đây là ví dụ và giải thích cụ thể:

1. **Phương thức `.Where()`**:

   Ví dụ:
   ```csharp
   var adults = people.Where(person => person.Age >= 18);
   ```

   Trong phương thức `.Where()`, bạn có thể truyền một biểu thức lambda `person => person.Age >= 18` để lọc các đối tượng trong danh sách `people`. Biểu thức này xác định rằng chỉ những người có tuổi lớn hơn hoặc bằng 18 mới được bao gồm trong kết quả.

2. **Phương thức `.Select()`**:

   Ví dụ:
   ```csharp
   var names = people.Select(person => person.Name);
   ```

   Trong phương thức `.Select()`, bạn có thể truyền một biểu thức lambda `person => person.Name` để chọn một thuộc tính cụ thể từ danh sách `people`. Kết quả là danh sách các tên của mọi người.

3. **Phương thức `.OrderBy()`**:

   Ví dụ:
   ```csharp
   var sortedPeople = people.OrderBy(person => person.Age);
   ```

   Trong phương thức `.OrderBy()`, bạn có thể truyền một biểu thức lambda `person => person.Age` để sắp xếp danh sách `people` dựa trên thuộc tính `Age`.

4. **Phương thức `.GroupBy()`**:

   Ví dụ:
   ```csharp
   var peopleByDepartment = people.GroupBy(person => person.DepartmentId);
   ```

   Trong phương thức `.GroupBy()`, bạn có thể truyền một biểu thức lambda `person => person.DepartmentId` để nhóm danh sách `people` thành các nhóm dựa trên giá trị của trường `DepartmentId`.

Cơ chế tham số hóa này cho phép bạn linh hoạt thực hiện các thao tác trên dữ liệu dựa trên logic tùy chỉnh mà bạn định nghĩa trong biểu thức lambda. Biểu thức lambda này có thể tham chiếu đến các biến hoặc tham số từ phạm vi bên ngoài để tùy chỉnh truy vấn LINQ một cách linh hoạt.

<hr>

Trong .NET, `IQueryable` và `List` là hai cách để làm việc với dữ liệu tập hợp, nhưng chúng có mục tiêu và ứng dụng khác nhau.

1. `List`:
   - `List` là một danh sách dựa trên mảng, có thể chứa nhiều phần tử và thường được sử dụng để lưu trữ và xử lý dữ liệu trong bộ nhớ.
   - Dữ liệu trong `List` được lưu trữ dưới dạng mảng, do đó việc truy cập và thao tác với dữ liệu trong `List` thường rất nhanh chóng.
   - `List` là một phần của `System.Collections.Generic` namespace và không hỗ trợ các truy vấn phức tạp hoặc tối ưu hóa cơ sở dữ liệu.

Ví dụ về việc sử dụng `List`:

```csharp
List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };
int sum = numbers.Sum(); // Tính tổng các số trong List
```

2. `IQueryable`:
   - `IQueryable` là một giao diện trong LINQ (Language-Integrated Query) và thường được sử dụng để thao tác với các nguồn dữ liệu từ cơ sở dữ liệu hoặc các nguồn dữ liệu từ xa.
   - `IQueryable` cho phép xây dựng các truy vấn LINQ phức tạp và trì hoãn thực hiện truy vấn cho đến khi cần thiết. Điều này giúp tối ưu hóa việc truy xuất cơ sở dữ liệu và giảm lượng dữ liệu truyền về từ cơ sở dữ liệu.
   - Để sử dụng `IQueryable`, thường cần sử dụng ORM (Object-Relational Mapping) như Entity Framework để tạo truy vấn cơ sở dữ liệu và ánh xạ kết quả vào đối tượng C#.

Ví dụ về việc sử dụng `IQueryable`:

```csharp
var query = dbContext.Customers.Where(c => c.City == "New York");
int customerCount = query.Count(); // Thực hiện truy vấn và đếm số khách hàng ở New York
```

Tóm lại, `List` thích hợp cho việc lưu trữ và xử lý dữ liệu trong bộ nhớ, trong khi `IQueryable` thường được sử dụng để thao tác với dữ liệu từ cơ sở dữ liệu hoặc các nguồn dữ liệu từ xa và hỗ trợ việc tối ưu hóa truy vấn.


1. **Bộ nhớ lưu trữ của IQueryable và List**:

   - **List**: Dữ liệu trong một `List` được lưu trữ trực tiếp trong bộ nhớ của ứng dụng. Các phần tử của `List` nằm trong bộ nhớ heap của ứng dụng, và bạn có thể truy cập và thao tác với chúng bất cứ khi nào bạn muốn. Dữ liệu ở đây nằm trong bộ nhớ và không được đồng bộ hóa với cơ sở dữ liệu hoặc nguồn dữ liệu từ xa.

   - **IQueryable**: `IQueryable` không lưu trữ dữ liệu thực sự trong bộ nhớ. Thay vào đó, nó biểu diễn một truy vấn (query) đến nguồn dữ liệu (thường là cơ sở dữ liệu) và thực hiện truy vấn khi bạn yêu cầu dữ liệu. Dữ liệu sẽ được truy vấn từ nguồn và ánh xạ vào đối tượng C# theo yêu cầu.

2. **Thời điểm thực hiện truy vấn (query) trong IQueryable**:

   - `IQueryable` trì hoãn thực hiện truy vấn cho đến khi dữ liệu thực sự cần được truy cập. Điều này có nghĩa rằng khi bạn xây dựng một truy vấn `IQueryable`, ví dụ như sử dụng LINQ để lọc dữ liệu, thì truy vấn đó chỉ được định nghĩa, nhưng không thực hiện ngay lập tức. Truy vấn sẽ thực hiện khi bạn thực sự yêu cầu dữ liệu, ví dụ như khi gọi `ToList()`, `Count()`, hoặc lặp qua kết quả.

3. **So sánh hiệu suất**:

   - `IQueryable` thường tối ưu hóa truy vấn đến cơ sở dữ liệu bằng cách chuyển truy vấn LINQ vào một truy vấn SQL hoặc tương tự, nếu bạn sử dụng ORM như Entity Framework. Điều này có thể giúp giảm lượng dữ liệu truyền về từ cơ sở dữ liệu và cải thiện hiệu suất tổng thể của ứng dụng.

   - `List` thích hợp cho việc xử lý dữ liệu trong bộ nhớ một cách nhanh chóng, nhưng không thể tối ưu hóa truy vấn cơ sở dữ liệu.

Lựa chọn giữa `IQueryable` và `List` phụ thuộc vào nhu cầu cụ thể của ứng dụng. Nếu bạn làm việc với cơ sở dữ liệu hoặc cần thực hiện các truy vấn phức tạp, `IQueryable` là sự lựa chọn tốt hơn để tối ưu hóa hiệu suất truy vấn dữ liệu. Trong trường hợp bạn chỉ làm việc với dữ liệu trong bộ nhớ và cần tốc độ xử lý nhanh chóng, `List` có thể phù hợp hơn.

`IEnumerable` là một giao diện (interface) trong .NET Framework và .NET Core, nằm trong namespace `System.Collections`. Giao diện này là một phần quan trọng của tập hợp các giao diện và lớp mà thể hiện các tập hợp dữ liệu trong .NET.

Dưới đây là một số điểm quan trọng về `IEnumerable`:

1. **Tính chất trì hoãn (Deferred Execution)**:
   - `IEnumerable` thể hiện một tập hợp các phần tử dữ liệu mà bạn có thể lặp qua.
   - Một trong những đặc điểm quan trọng của `IEnumerable` là tính chất trì hoãn của nó. Điều này có nghĩa rằng truy vấn trên một `IEnumerable` không được thực hiện ngay lập tức khi bạn định nghĩa nó. Thay vào đó, truy vấn chỉ thực hiện khi bạn bắt đầu lặp qua tập hợp dữ liệu hoặc gọi một phương thức kích hoạt như `ToList()`, `Count()`,...

2. **Phương thức GetEnumerator**:
   - `IEnumerable` định nghĩa một phương thức có tên là `GetEnumerator()`, cho phép bạn lặp qua tập hợp dữ liệu sử dụng một vòng lặp `foreach`.

3. **Sử dụng trong LINQ**:
   - `IEnumerable` thường được sử dụng trong LINQ (Language-Integrated Query) để truy vấn và xử lý dữ liệu. LINQ cho phép bạn viết các truy vấn dễ đọc và dễ bảo trì trên các tập hợp dữ liệu `IEnumerable`.

Ví dụ về việc sử dụng `IEnumerable`:

```csharp
IEnumerable<int> numbers = new List<int> { 1, 2, 3, 4, 5 };

// Tính tổng các số chẵn
int sum = numbers.Where(n => n % 2 == 0).Sum();

foreach (int number in numbers)
{
    Console.WriteLine(number);
}
```

Trong ví dụ này, `numbers` là một `IEnumerable<int>` và chúng ta sử dụng LINQ để lọc các số chẵn và tính tổng của chúng. Sau đó, chúng ta lặp qua danh sách số bằng một vòng lặp `foreach`.

Tóm lại, `IEnumerable` là một giao diện quan trọng trong .NET cho phép bạn lặp qua các tập hợp dữ liệu và sử dụng LINQ để thực hiện các truy vấn trên chúng.

<hr>

Dưới đây là hướng dẫn triển khai một ứng dụng CRUD đơn giản với ASP.NET Core MVC bao gồm cả code cho phần controller:

Bước 1: Tạo một dự án ASP.NET Core MVC mới
- Sử dụng Visual Studio hoặc Visual Studio Code để tạo một dự án ASP.NET Core MVC. Chọn một dự án MVC trống (Empty) hoặc dự án MVC với các tùy chọn đã có sẵn tùy theo nhu cầu của bạn.

Bước 2: Tạo một model
- Tạo một class model để đại diện cho các đối tượng bạn muốn quản lý trong ứng dụng. Ví dụ, nếu bạn muốn quản lý danh sách các sản phẩm, bạn có thể tạo một class `Product.cs` như sau:

```csharp
public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
}
```

Bước 3: Tạo DbContext
- Sử dụng Entity Framework Core để tạo một DbContext để tương tác với cơ sở dữ liệu. Đảm bảo bạn cấu hình DbContext để nói với Entity Framework về model của bạn. Ví dụ:

```csharp
using Microsoft.EntityFrameworkCore;

public class ApplicationDbContext : DbContext
{
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
        : base(options)
    {
    }

    public DbSet<Product> Products { get; set; }
}
```

Bước 4: Tạo controller
- Tạo một controller để xử lý các yêu cầu từ người dùng. Sử dụng scaffolding hoặc tạo thủ công một controller với các action như Create, Read, Update và Delete. Dưới đây là một ví dụ về cách tạo một controller cho quản lý sản phẩm:

```csharp
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System.Linq;
using System.Threading.Tasks;

public class ProductController : Controller
{
    private readonly ApplicationDbContext _context;

    public ProductController(ApplicationDbContext context)
    {
        _context = context;
    }

    public async Task<IActionResult> Index()
    {
        var products = await _context.Products.ToListAsync();
        return View(products);
    }

    public IActionResult Create()
    {
        return View();
    }

    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> Create(Product product)
    {
        if (ModelState.IsValid)
        {
            _context.Add(product);
            await _context.SaveChangesAsync();
            return RedirectToAction(nameof(Index));
        }
        return View(product);
    }

    public async Task<IActionResult> Edit(int? id)
    {
        if (id == null)
        {
            return NotFound();
        }

        var product = await _context.Products.FindAsync(id);
        if (product == null)
        {
            return NotFound();
        }
        return View(product);
    }

    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> Edit(int id, Product product)
    {
        if (id != product.Id)
        {
            return NotFound();
        }

        if (ModelState.IsValid)
        {
            try
            {
                _context.Update(product);
                await _context.SaveChangesAsync();
            }
            catch (DbUpdateConcurrencyException)
            {
                if (!ProductExists(product.Id))
                {
                    return NotFound();
                }
                else
                {
                    throw;
                }
            }
            return RedirectToAction(nameof(Index));
        }
        return View(product);
    }

    public async Task<IActionResult> Delete(int? id)
    {
        if (id == null)
        {
            return NotFound();
        }

        var product = await _context.Products
            .FirstOrDefaultAsync(m => m.Id == id);
        if (product == null)
        {
            return NotFound();
        }

        return View(product);
    }

    [HttpPost, ActionName("Delete")]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> DeleteConfirmed(int id)
    {
        var product = await _context.Products.FindAsync(id);
        _context.Products.Remove(product);
        await _context.SaveChangesAsync();
        return RedirectToAction(nameof(Index));
    }

    private bool ProductExists(int id)
    {
        return _context.Products.Any(e => e.Id == id);
    }
}
```

Bước 5: Tạo các view
- Tạo các view để hiển thị dữ liệu và giao diện người dùng. Sử dụng Razor View Engine để tạo các file `.cshtml` cho các action trong controller.

Bước 6: Cấu hình và chạy ứng dụng
- Cấu hình ứng dụng để sử dụng DbContext và cơ sở dữ liệu. Sau đó, chạy ứng dụng và kiểm tra tính năng CRUD, tìm kiếm và phân trang.

Đây là một hướng dẫn cơ bản về cách triển khai một ứng dụng CRUD sử dụng ASP.NET Core MVC với phần controller hoàn chỉnh. Bạn cần thực hiện các bước cụ thể hơn và tuỳ chỉnh ứng dụng dựa trên nhu cầu cụ thể của bạn.

<hr>

Nếu bạn muốn thực hiện phân trang mà không sử dụng thư viện PagedList, bạn có thể tính tổng số trang, tổng số sản phẩm, và trang hiện tại bằng cách sử dụng các tính toán trực tiếp trong controller. Dưới đây là một ví dụ về cách làm điều này:

```csharp
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System;
using System.Linq;
using System.Threading.Tasks;

public class ProductController : Controller
{
    private readonly ApplicationDbContext _context;

    public ProductController(ApplicationDbContext context)
    {
        _context = context;
    }

    public async Task<IActionResult> Index(string searchString, string sortOrder, int? page)
    {
        // Sắp xếp
        ViewBag.NameSortParam = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
        var products = from p in _context.Products
                       select p;

        switch (sortOrder)
        {
            case "name_desc":
                products = products.OrderByDescending(p => p.Name);
                break;
            default:
                products = products.OrderBy(p => p.Name);
                break;
        }

        // Tìm kiếm theo keyword
        if (!String.IsNullOrEmpty(searchString))
        {
            products = products.Where(p => p.Name.Contains(searchString));
        }

        // Tính tổng số sản phẩm
        var totalProducts = await products.CountAsync();

        // Phân trang
        int pageSize = 10; // Số lượng sản phẩm trên mỗi trang
        int totalPages = (int)Math.Ceiling((double)totalProducts / pageSize); // Tổng số trang
        int pageNumber = page ?? 1; // Trang hiện tại (nếu không có thì là trang 1)

        // Giới hạn số trang hiển thị
        const int maxPageLinks = 5;
        int startPage = Math.Max(1, pageNumber - maxPageLinks / 2);
        int endPage = Math.Min(totalPages, startPage + maxPageLinks - 1);

        // Trích xuất danh sách sản phẩm cho trang hiện tại
        var currentProducts = await products
            .Skip((pageNumber - 1) * pageSize)
            .Take(pageSize)
            .ToListAsync();

        ViewBag.TotalPages = totalPages;
        ViewBag.CurrentPage = pageNumber;
        ViewBag.StartPage = startPage;
        ViewBag.EndPage = endPage;

        return View(currentProducts);
    }

    // Rest of your CRUD actions...

    private bool ProductExists(int id)
    {
        return _context.Products.Any(e => e.Id == id);
    }
}
```

Trong action `Index`, chúng ta đã thêm tính toán tổng số sản phẩm (`totalProducts`) và tổng số trang (`totalPages`). Đồng thời, chúng ta giới hạn số trang hiển thị để không hiển thị quá nhiều trang số (ví dụ: chỉ hiển thị 5 trang trung tâm xung quanh trang hiện tại). Sau đó, chúng ta trích xuất danh sách sản phẩm cho trang hiện tại bằng cách sử dụng `Skip` và `Take` để lấy dữ liệu từ cơ sở dữ liệu.

Trong view, bạn có thể sử dụng `ViewBag` để hiển thị thông tin về tổng số trang (`TotalPages`), trang hiện tại (`CurrentPage`), và các trang số cần hiển thị (`StartPage` và `EndPage`).