Truy vấn trong Entity Framework

Bạn có thể xây dựng và thực hiện các truy vấn bằng Entity Framework để lấy dữ liệu từ cơ sở dữ liệu. Entity Framework 6 hỗ trợ các loại truy vấn khác nhau lần lượt chuyển đổi thành truy vấn SQL cho cơ sở dữ liệu.

Entity Framework hỗ trợ ba loại truy vấn:

  • LINQ-to-Entities
  • Entity SQL
  • Truy vấn SQL

Truy vấn LINQ-to-Entities

Truy vấn tích hợp ngôn ngữ (LINQ) là ngôn ngữ truy vấn mạnh mẽ được giới thiệu trong Visual Studio 2008.

Như tên gọi của nó, các truy vấn LINQ-to-Entities hoạt động trên tập thực thể (thuộc tính kiểu DbSet) để truy cập dữ liệu từ cơ sở dữ liệu. Bạn có thể sử dụng cú pháp phương thức hoặc cú pháp truy vấn LINQ khi truy vấn với EDM.

Truy vấn LINQ-to-Entities sau đây lấy dữ liệu từ bảng Student trong cơ sở dữ liệu.

Cú pháp phương thức LINQ:

using (var context = new SchoolDBEntities())
{
    var query = context.Students
        .Where(s => s.StudentName == "Bill")
        .FirstOrDefault<Student>();
}

Cú pháp truy vấn LINQ:

using (var context = new SchoolDBEntities())
{
    var query = from st in context.Students
                where st.StudentName == "Bill"
                select st;
   
    var student = query.FirstOrDefault<Student>();
}

Như bạn có thể thấy ở trên, chúng tôi đã tạo một thể hiện của lớp Context SchoolDBEntities. Bạn nên khởi tạo nó sử dụng using() để sau đi ra khỏi phạm vi khối using nó sẽ tự động được hủy và giải phóng bộ nhớ (disposed).

Bạn có thể xem chi tiết về LINQ-to-Entities ở bài viết này:

LINQ-to-Entities trong Entity Framework | Comdy
LINQ-to-Entities trong Entity Framework là gì? Hướng dẫn sử dụng LINQ-to-Entities trong Entity Framework.

Entity SQL

Entity SQL là một cách khác để tạo một truy vấn. Nó được xử lý trực tiếp bởi Dịch vụ đối tượng của Entity Framework. Nó trả về ObjectQuery thay vì IQueryable.

Bạn cần một đối tượng ObjectContext để tạo một truy vấn bằng cách sử dụng Entity SQL.

Đoạn mã sau đây cho thấy kết quả truy vấn tương tự như truy vấn LINQ ở trên.

string sqlString = "SELECT VALUE st FROM SchoolDBEntities.Students " +
                   "AS st WHERE st.StudentName == 'Bill'";
    
var objctx = (ctx as IObjectContextAdapter).ObjectContext;
                
ObjectQuery<Student> student = objctx.CreateQuery<Student>(sqlString);
Student newStudent = student.First<Student>();

Bạn cũng có thể sử dụng EntityConnectionEntityCommand để thực thi Entity SQL như dưới đây:

using (var con = new EntityConnection("name=SchoolDBEntities"))
{
    con.Open();
    EntityCommand cmd = con.CreateCommand();
    cmd.CommandText = "SELECT VALUE st FROM SchoolDBEntities.Students as st where st.StudentName='Bill'";
    Dictionary<int, string> dict = new Dictionary<int, string>();
    using (EntityDataReader rdr = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.CloseConnection))
    {
        while (rdr.Read())
        {
            int a = rdr.GetInt32(0);
            var b = rdr.GetString(1);
            dict.Add(a, b);
        }
    }               
}

Truy vấn SQL

Bạn có thể thực thi các truy vấn SQL gốc cho cơ sở dữ liệu quan hệ, như được trình bày bên dưới:

using (var ctx = new SchoolDBEntities())
{
    var studentName = ctx.Students
        .SqlQuery("Select studentid, studentname, standardId from Student where studentname='Bill'")
        .FirstOrDefault<Student>();
}

Thực hiện các truy vấn SQL trong Entity Framework

Entity Framework cho phép bạn thực hiện các truy vấn SQL cho cơ sở dữ liệu quan hệ.

Các phương thức sau đây có thể được sử dụng để thực thi các truy vấn SQL vào cơ sở dữ liệu bằng Entity Framework 6.x:

  • DbSet.SqlQuery()
  • DbContext.Database.SqlQuery()
  • DbContext.Database.ExecuteSqlCommand()

DbSet.SqlQuery()

Sử dụng phương thức DbSet.SqlQuery() để viết các truy vấn SQL trả về các thể hiện của thực thể. Các thực thể trả về sẽ được theo dõi bởi Context, như thể chúng được trả về bởi truy vấn LINQ.

using (var ctx = new SchoolDBEntities())
{
    var studentList = ctx.Students
                         .SqlQuery("Select * from Students")
                         .ToList<Student>();
}

Truy vấn trên thực thi câu lệnh SQL Select * from Students trong cơ sở dữ liệu để trả về tất cả sinh viên và được chuyển đổi thành một danh sách các thực thể Student. Tên cột trong truy vấn SQL phải khớp với các thuộc tính của kiểu thực thể, nếu không nó sẽ đưa ra một ngoại lệ.

Bạn có thể chỉ định các tham số bằng cách sử dụng đối tượng SqlParameter, như sau:

using (var ctx = new SchoolDBEntities())
{
    var student = ctx.Students
        .SqlQuery("Select * from Students where StudentId=@id", 
            new SqlParameter("@id", 1))
        .FirstOrDefault();
}

Nếu bạn thay đổi tên cột trong truy vấn SQL, thì nó sẽ đưa ra một ngoại lệ vì nó phải khớp với tên cột. Ví dụ sau đây sẽ đưa ra một ngoại lệ.

using (var ctx = new SchoolDBEntities())
{                
    //this will throw an exception
    var studentName = ctx.Students.SqlQuery("Select studentid as id, studentname as name from Student where studentname='Steve'").ToList();
}

Phương thức DbSet<TEntity>.SqlQuery() thực hiện truy vấn SQL chỉ dành cho bảng được ánh xạ với thực thể được chỉ định (ví dụ: DbSet<Student>.SqlQuery() chỉ trả về kết quả từ bảng Students tương ứng chứ không phải từ bất kỳ bảng nào khác). Sau đây sẽ ném một ngoại lệ.

using (var ctx = new SchoolDBEntities())
{                
    //this will throw an exception
    var studentName = ctx.Students.SqlQuery("Select * from Courses").ToList();
}

Database.SqlQuery()

Lớp Database đại diện cho cơ sở dữ liệu và cung cấp phương thức khác nhau để truy vấn cơ sở dữ liệu. Phương thức Database.SqlQuery() trả về một giá trị của bất kỳ kiểu dữ liệu nào.

using (var ctx = new SchoolDBEntities())
{
    //Get student name of string type
    string studentName = ctx.Database.SqlQuery<string>("Select studentname from Student where studentid=1")
                            .FirstOrDefault();

    //or
    string studentName = ctx.Database.SqlQuery<string>("Select studentname from Student where studentid=@id", new SqlParameter("@id", 1))
                            .FirstOrDefault();
}

Database.ExecuteSqlCommand()

Phương thức Database.ExecuteSqlCommnad() rất hữu ích trong thực hiện các lệnh Insert, Update và Delete.

using (var ctx = new SchoolDBEntities())
{
    int noOfRowUpdated = ctx.Database.ExecuteSqlCommand("Update student 
            set studentname ='changed student by command' where studentid=1");

    int noOfRowInserted = ctx.Database.ExecuteSqlCommand("insert into student(studentname) 
            values('New Student')");

    int noOfRowDeleted = ctx.Database.ExecuteSqlCommand("delete from student 
            where studentid=1");
}

Eager Loading trong Entity Framework

Eager Loading là gì?

Eager Loading là quá trình trong đó một truy vấn cho một kiểu thực thể cũng tải các thực thể liên quan như một phần của truy vấn, do đó chúng ta không cần phải thực hiện một truy vấn riêng cho các thực thể liên quan.

Eager Loading được thực hiện bằng cách sử dụng phương thức Include().

Trong ví dụ sau sẽ lấy Student có tên "Bill" từ cơ sở dữ liệu kèm theo thông tin Standard của nó bằng phương thức Include().

Cú pháp truy vấn LINQ:

using (var context = new SchoolDBEntities())
{
    var stud1 = (from s in context.Students.Include("Standard")
                where s.StudentName == "Bill"
                select s).FirstOrDefault<Student>();
}

Cú pháp phương thức LINQ:

using (var ctx = new SchoolDBEntities())
{
    var stud1 = ctx.Students
                   .Include("Standard")
                   .Where(s => s.StudentName == "Bill")
                   .FirstOrDefault<Student>();
}

Các truy vấn LINQ ở trên sẽ tạo ra truy vấn SQL sau:

SELECT TOP (1) 
[Extent1].[StudentID] AS [StudentID], 
[Extent1].[StudentName] AS [StudentName], 
[Extent2].[StandardId] AS [StandardId], 
[Extent2].[StandardName] AS [StandardName], 
[Extent2].[Description] AS [Description]
FROM  [dbo].[Student] AS [Extent1]
LEFT OUTER JOIN [dbo].[Standard] AS [Extent2] ON [Extent1].[StandardId] = [Extent2].[StandardId]
WHERE 'Bill' = [Extent1].[StudentName]

Sử dụng biểu thức Lambda

Bạn cũng có thể sử dụng biểu thức lambda LINQ làm tham số trong phương thức Include. Đối với điều này, hãy sử dụng namespace System.Data.Entity và sử dụng biểu thức lambda như dưới đây:

using System;
using System.Data.Entity; 
   
class Program
{
    static void Main(string[] args)
    {
        using (var ctx = new SchoolDBEntities())
        {
            var stud1 = ctx.Students.Include(s => s.Standard)
                           .Where(s => s.StudentName == "Bill")
                           .FirstOrDefault<Student>();
        }
    }
}

Tải nhiều thực thể

Bạn cũng có thể Eager Loading nhiều cấp độ của các thực thể liên quan. Truy vấn sau sẽ tải các thực thể Student, StandardTeacher:

using (var ctx = new SchoolDBEntities())
{
    var stud1 = ctx.Students.Include("Standard.Teachers")
                   .Where(s => s.StudentName == "Bill")
                   .FirstOrDefault<Student>();
}

Hoặc sử dụng biểu thức lambda như dưới đây:

using (var ctx = new SchoolDBEntities())
{
    var stud1 = ctx.Students.Include(s => s.Standard.Teachers)
                   .Where(s => s.StudentName == "Bill")
                   .FirstOrDefault<Student>();
}

Truy vấn trên sẽ thực hiện truy vấn SQL sau trong cơ sở dữ liệu:

SELECT [Project2].[StudentID] AS [StudentID], 
[Project2].[StudentName] AS [StudentName], 
[Project2].[StandardId] AS [StandardId], 
[Project2].[StandardName] AS [StandardName], 
[Project2].[Description] AS [Description], 
[Project2].[C1] AS [C1], 
[Project2].[TeacherId] AS [TeacherId], 
[Project2].[TeacherName] AS [TeacherName], 
[Project2].[StandardId1] AS [StandardId1]
FROM ( SELECT 
    [Limit1].[StudentID] AS [StudentID], 
    [Limit1].[StudentName] AS [StudentName], 
    [Limit1].[StandardId1] AS [StandardId], 
    [Limit1].[StandardName] AS [StandardName], 
    [Limit1].[Description] AS [Description], 
    [Project1].[TeacherId] AS [TeacherId], 
    [Project1].[TeacherName] AS [TeacherName], 
    [Project1].[StandardId] AS [StandardId1], 
    CASE WHEN ([Project1].[TeacherId] IS NULL) THEN CAST(NULL AS int) ELSE 1 END AS [C1]
    FROM   (SELECT TOP (1) [Extent1].[StudentID] AS [StudentID], [Extent1].[StudentName] AS [StudentName], [Extent1].[StandardId] AS [StandardId2], [Extent2].[StandardId] AS [StandardId1], [Extent2].[StandardName] AS [StandardName], [Extent2].[Description] AS [Description]
        FROM  [dbo].[Student] AS [Extent1]
        LEFT OUTER JOIN [dbo].[Standard] AS [Extent2] ON [Extent1].[StandardId] = [Extent2].[StandardId]
        WHERE 'updated student' = [Extent1].[StudentName] ) AS [Limit1]
    LEFT OUTER JOIN  (SELECT 
        [Extent3].[TeacherId] AS [TeacherId], 
        [Extent3].[TeacherName] AS [TeacherName], 
        [Extent3].[StandardId] AS [StandardId]
        FROM [dbo].[Teacher] AS [Extent3]
        WHERE [Extent3].[StandardId] IS NOT NULL ) AS [Project1] ON [Limit1].[StandardId2] = [Project1].[StandardId]
)  AS [Project2]
ORDER BY [Project2].[StudentID] ASC, [Project2].[StandardId] ASC, [Project2].[C1] ASC

Lazy Loading trong Entity Framework

Lazy loading là gì?

Lazy loading trì hoãn việc tải các dữ liệu liên quan, cho đến khi bạn yêu cầu cụ thể. Lazy loading đối lập hoàn toàn với Eager loading.

Ví dụ, thực thể Student chứa thực thể StudentAddress. Trong lazy loading, Context trước tiên tải dữ liệu thực thể Student từ cơ sở dữ liệu, sau đó nó sẽ tải thực thể StudentAddress khi chúng ta truy cập thuộc tính StudentAddress như dưới đây.

using (var ctx = new SchoolDBEntities())
{
    //Loading students only
    IList<Student> studList = ctx.Students.ToList<Student>();

    Student std = studList[0];

    //Loads Student address for particular Student only (seperate SQL query)
    StudentAddress add = std.StudentAddress;
}

Mã được trình bày ở trên sẽ dẫn đến hai truy vấn SQL. Đầu tiên, nó sẽ lấy tất cả các sinh viên:

SELECT 
[Extent1].[StudentID] AS [StudentID], 
[Extent1].[StudentName] AS [StudentName], 
[Extent1].[StandardId] AS [StandardId]
FROM [dbo].[Student] AS [Extent1]

Sau đó, nó sẽ gửi truy vấn sau đây khi chúng tôi truy cập thuộc tính StudentAddress của đối tượng sinh viên đầu tiên:

exec sp_executesql N'SELECT 
[Extent1].[StudentID] AS [StudentID], 
[Extent1].[Address1] AS [Address1], 
[Extent1].[Address2] AS [Address2], 
[Extent1].[City] AS [City], 
[Extent1].[State] AS [State]
FROM [dbo].[StudentAddress] AS [Extent1]
WHERE [Extent1].[StudentID] = @EntityKeyValue1',N'@EntityKeyValue1 int',@EntityKeyValue1=1

Vô hiệu hóa lazy loading

Chúng ta có thể vô hiệu hóa lazy loading cho một thực thể cụ thể hoặc một Context. Để tắt lazy loading cho một thuộc tính cụ thể, đừng khai báo từ khóa virtual. Để tắt lazy loading cho tất cả các thực thể trong Context, thiết lập thuộc tính cấu hình của nó thành false.

using System;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Data.Entity.Core.Objects;
using System.Linq;
    
public partial class SchoolDBEntities : DbContext
{
    public SchoolDBEntities(): base("name=SchoolDBEntities")
    {
        this.Configuration.LazyLoadingEnabled = false;
    }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
    }
}

Quy tắc lazy loading:

  1. context.Configuration.ProxyCreationEnables phải là true.
  2. context.Configuration.LazyLoadingEnables phải là true.
  3. Thuộc tính Navigation nên được định nghĩa là public, virtual. Context sẽ KHÔNG lazy loading nếu thuộc tính không được định nghĩa là virtual.

Explicit Loading trong Entity Framework

Ở đây bạn sẽ tìm hiểu làm thế nào để tải các thực thể liên quan trong một biểu đồ thực thể rõ ràng.

Ngay cả khi lazy loading bị vô hiệu hóa (trong EF 6), vẫn có thể lazy loading các thực thể liên quan, nhưng nó phải được gọi rõ ràng. Sử dụng phương thức Load() để tải các thực thể liên quan một cách rõ ràng. Hãy xem xét ví dụ sau.

using (var context = new SchoolContext())
{
    var student = context.Students
                         .Where(s => s.FirstName == "Bill")
                         .FirstOrDefault<Student>();
    
    // loads StudentAddress
    context.Entry(student).Reference(s => s.StudentAddress).Load(); 
    
    // loads Courses collection 
    context.Entry(student).Collection(s => s.StudentCourses).Load();
}

Trong ví dụ trên, câu lệnh context.Entry(student).Reference(s => s.StudentAddress).Load() sẽ tải thực thể StudentAddress. Phương thức Reference() được sử dụng để lấy một đối tượng của thuộc tính điều hướng tham chiếu được chỉ định và các phương thức Load() tải nó một cách rõ ràng.

Theo cùng một cách, câu lệnh context.Entry(student).Collection(s => s.Courses).Load() tải thuộc tính điều hướng danh sách Courses của thực thể Student. Phương thức Collection() nhận một đối tượng đại diện cho thuộc tính điều hướng danh sách.

Phương thức Load() thực hiện truy vấn SQL trong cơ sở dữ liệu để lấy dữ liệu và lấp đầy các tham chiếu được chỉ định hoặc thuộc tính danh sách trong bộ nhớ, như hình dưới đây.

Explicit loading trong Entity Framework

Phương thức Query()

Bạn cũng có thể viết các truy vấn LINQ-to-Entities để lọc dữ liệu liên quan trước khi tải. Phương thức Query() cho phép chúng ta viết các truy vấn LINQ thêm cho các đối tượng có liên quan để lọc ra dữ liệu có liên quan.

using (var context = new SchoolContext())
{
    var student = context.Students
                         .Where(s => s.FirstName == "Bill")
                         .FirstOrDefault<Student>();
    
    context.Entry(student)
           .Collection(s => s.StudentCourses)
           .Query()
               .Where(sc => sc.CourseName == "Maths")
               .FirstOrDefault();
}

Trong ví dụ trên, biểu thức .Collection(s => s.StudentCourses).Query() cho phép chúng tôi viết các truy vấn tiếp theo cho thực thể StudentCourses.

Entity Framework
Bài Viết Liên Quan:
2 kịch bản lưu dữ liệu trong Entity Framework Core
Trung Nguyen 30/04/2020
2 kịch bản lưu dữ liệu trong Entity Framework Core

2 kịch bản lưu dữ liệu trong Entity Framework Core là kịch bản được kết nối và kịch bản ngắt kết nối.

Ứng dụng Entity Framework Core đầu tiên
Trung Nguyen 29/04/2020
Ứng dụng Entity Framework Core đầu tiên

Tạo ứng dụng .NET Core Console đầu tiên và cấu hình sử dụng Entity Framework Core.

Truy vấn trong Entity Framework Core
Trung Nguyen 29/04/2020
Truy vấn trong Entity Framework Core

Truy vấn trong Entity Framework Core có gì mới? Truy vấn trong EF Core khác EF ở những điểm nào.

Entity Framework Core toàn tập
Trung Nguyen 29/04/2020
Entity Framework Core toàn tập

Entity Framework Core toàn tập sẽ hướng dẫn bạn tất cả mọi thứ về Entity Framework Core.