Bootstrap

EF6(Entity Framework 6)基础知识

一、Entity Framework 6 概述

Entity Framework (EF) 是 Microsoft 提供的一个对象关系映射器 (ORM),它使得 .NET 开发人员能够使用 .NET 对象来处理数据库,从而无需经常编写大部分数据访问代码。EF 提供了许多功能,包括更改跟踪、查询构建、视图生成和映射存储过程等。

Entity Framework 6 (EF6) 是 Entity Framework 的一个版本,它在 Entity Framework 5 的基础上进行了许多改进和优化,提供了更高效的查询性能、更灵活的配置选项以及更好的可扩展性。

二、定义 DbContext 派生类

在 EF6 中,DbContext 是与数据库交互的主要类。它表示一个数据库上下文,用于包含 DbSet 属性,这些属性表示数据库中的表或视图。下面是一个简单的示例,演示如何定义一个派生自 DbContext 的类:

using System.Data.Entity;  
  
namespace MyApp.DataModels  
{  
    public class MyDbContext : DbContext  
    {  
        public MyDbContext() : base("name=MyConnectionString") { }  
  
        public DbSet<Category> Categories { get; set; }  
        public DbSet<Product> Products { get; set; }  
        // 其他 DbSet 属性...  
    }  
}

在这个例子中,MyDbContext 类继承自 DbContext,并定义了两个 DbSet 属性:Categories 和 Products。这些属性分别表示数据库中的 Categories 表和 Products 表。构造函数中的 base("name=MyConnectionString") 指定了用于连接到数据库的连接字符串的名称,该名称通常在应用的配置文件(如 App.config 或 Web.config)中定义。

三、使用 DbContext 进行数据库操作 

一旦定义了 DbContext 派生类,就可以使用它来执行各种数据库操作,如查询、添加、更新和删除实体。

1. 查询实体

要查询数据库中的实体,可以访问 DbContext 上的 DbSet 属性。这不会立即执行查询,而是创建一个可执行的查询对象。当实际需要数据时(例如,在遍历查询结果或在调用如 ToListToArray 等方法时),查询才会执行。

using (var context = new MyDbContext())  
{  
    // 查询所有类别  
    var allCategories = context.Categories.ToList();  
  
    // 查询具有特定名称的产品  
    var productByName = context.Products.FirstOrDefault(p => p.Name == "ProductName");  
}

2. 添加实体

要向数据库添加新实体,可以使用 DbSet 的 Add 方法。添加后,需要调用 SaveChanges 方法来将更改提交到数据库。

using (var context = new MyDbContext())  
{  
    var newCategory = new Category { Name = "NewCategory" };  
    context.Categories.Add(newCategory);  
    context.SaveChanges(); // 提交更改到数据库  
}

3. 更新实体

要更新数据库中的现有实体,首先需要将实体从数据库中检索出来,修改其属性,然后调用 SaveChanges 方法。EF6 会跟踪实体的更改,并只更新发生更改的属性。

using (var context = new MyDbContext())  
{  
    var categoryToUpdate = context.Categories.Find(categoryId); // 假设 categoryId 是要更新的类别的 ID  
    if (categoryToUpdate != null)  
    {  
        categoryToUpdate.Name = "UpdatedCategoryName";  
        context.SaveChanges(); // 提交更改到数据库  
    }  
}

4. 删除实体

要从数据库中删除实体,可以使用 DbSet 的 Remove 方法。同样,需要调用 SaveChanges 方法来提交删除操作。

using (var context = new MyDbContext())  
{  
    var categoryToDelete = context.Categories.Find(categoryId); // 假设 categoryId 是要删除的类别的 ID  
    if (categoryToDelete != null)  
    {  
        context.Categories.Remove(categoryToDelete);  
        context.SaveChanges(); // 提交删除操作到数据库  
    }  
}

四、处理导航属性和外键

在 EF6 中,实体之间的关系可以通过导航属性和外键来表示。导航属性是指向其他实体的引用,而外键是用于在数据库中维护关系的字段。EF6 会自动处理这些关系的同步。

例如,假设 Product 实体有一个导航属性 Category,表示产品所属的类别,并且有一个外键 CategoryId。当将一个产品添加到 Products DbSet 并设置其 Category 属性时,EF6 会自动处理 CategoryId 的设置。同样,当谈到处理导航属性和外键时,Entity Framework 6 (EF6) 提供了强大的支持来管理实体之间的关系。导航属性使得你可以通过对象模型直接访问关联的其他实体,而无需显式地处理外键。

以下是一个示例,展示了如何在实体类中使用导航属性和外键,并在 DbContext 中进行相应的配置:

using System.ComponentModel.DataAnnotations;  
using System.ComponentModel.DataAnnotations.Schema;  
using System.Data.Entity;  
  
namespace MyApp.DataModels  
{  
    public class Category  
    {  
        [Key]  
        public int CategoryId { get; set; }  
        public string Name { get; set; }  
  
        // 导航属性,指向属于此类别的所有产品  
        public virtual ICollection<Product> Products { get; set; }  
    }  
  
    public class Product  
    {  
        [Key]  
        public int ProductId { get; set; }  
        public string Name { get; set; }  
        public decimal Price { get; set; }  
  
        // 外键属性,指向此产品所属的类别  
        public int CategoryId { get; set; }  
  
        // 导航属性,指向此产品所属的类别对象  
        public virtual Category Category { get; set; }  
    }  
  
    public class MyDbContext : DbContext  
    {  
        public MyDbContext() : base("name=MyConnectionString") { }  
  
        public DbSet<Category> Categories { get; set; }  
        public DbSet<Product> Products { get; set; }  
  
        // 配置模型(可选),例如指定外键和级联删除等  
        protected override void OnModelCreating(DbModelBuilder modelBuilder)  
        {  
            modelBuilder.Entity<Product>()  
                .HasRequired(p => p.Category) // 指定 Category 是必需的  
                .WithMany(c => c.Products) // 并与 Categories 中的 Products 集合关联  
                .HasForeignKey(p => p.CategoryId); // 指定外键属性  
  
            // 其他配置...  
        }  
    }  
}

 

在这个例子中,Product 类有一个 CategoryId 属性作为外键,指向 Category 类。同时,它也有一个 Category 导航属性,这是一个 Category 类型的对象,使得你可以方便地访问与产品关联的类别信息。

在 MyDbContext 类的 OnModelCreating 方法中,你可以使用 DbModelBuilder 来进一步配置实体之间的关系。在这个例子中,我们使用 HasRequired 和 WithMany 方法来配置 Product 和 Category 之间的多对一关系,并指定 CategoryId 作为外键。

五、DbContext 的高级功能

 除了基本的 CRUD 操作外,EF6 还提供了许多高级功能,可以帮助你更有效地处理数据库操作。

 一、延迟加载

延迟加载是Entity Framework中的一项特性,它允许在需要时才从数据库中加载相关的数据。具体来说,当访问一个实体的导航属性时,Entity Framework不会立即加载与该导航属性相关的数据,而是等到真正需要这些数据时才去数据库查询。这种方式可以有效减少数据库查询的次数,从而提高性能。

延迟加载的实现依赖于动态代理技术。当Entity Framework启用延迟加载时,它会为实体类生成一个动态代理类,这个代理类会覆盖导航属性的访问器,实现延迟加载的逻辑。因此,在使用延迟加载时,导航属性通常会被标记为virtual,以允许Entity Framework创建代理类。

需要注意的是,延迟加载虽然方便,但也可能导致N+1查询问题。即当遍历一个包含多个实体的集合时,如果每个实体都通过延迟加载访问其导航属性,那么将会对数据库发起多次查询,从而导致性能下降。因此,在使用延迟加载时,需要谨慎考虑其可能带来的影响。

二、异步操作

传统的同步操作会阻塞调用线程,直到操作完成才返回结果。而异步操作则允许调用线程在等待操作完成时继续执行其他任务,从而提高应用程序的吞吐量和响应速度。

Entity Framework提供了许多异步操作的方法,如SaveChangesAsyncToListAsync等。这些方法会返回一个TaskTask<T>对象,表示异步操作的任务。调用这些方法的线程可以继续执行其他任务,而不必等待数据库操作完成。当数据库操作完成时,Task对象会触发完成事件,此时可以获取操作的结果。

使用异步操作需要注意以下几点:

  1. 异步方法应该与异步上下文一起使用,例如在ASP.NET Core的控制器中,应该使用asyncawait关键字来调用异步方法。
  2. 异步方法不应该被阻塞调用,即不应该使用ResultWait方法来等待异步操作完成,这样会导致异步操作的优势失效。
  3. 需要注意异常处理,确保异步操作中的异常能够被正确捕获和处理。

三、查询优化

优化查询可以减少数据库的负担,提高查询速度,从而提升应用程序的性能。

以下是一些常见的查询优化技术:

  1. 投影查询:只选择需要的列,避免查询多余的列。这可以减少数据传输的量和数据库的工作负担。
  2. 使用索引:为经常查询的列创建索引,可以加快查询速度。但需要注意,过多的索引会增加数据库的维护成本,因此需要权衡利弊。
  3. 避免N+1查询:如前所述,延迟加载可能导致N+1查询问题。可以通过显式加载(Eager Loading)或显式指定查询的关联数据来避免这个问题。
  4. 编写高效的LINQ查询:避免在LINQ查询中使用复杂的逻辑和不必要的计算,尽量让数据库完成查询工作。
  5. 监控和分析:使用Entity Framework的性能分析工具和数据库监控工具来识别性能瓶颈和优化点。
;