田敏
返回博客列表
技术

EF Core 的核心概念

田敏
2024-12-058 分钟阅读
.NETC#Backend

Entity Framework Core (EF Core) 是一个轻量级、可扩展、跨平台的对象关系映射 (ORM) 框架。它允许开发者通过 C# 等 .NET 语言直接与数据库交互,而无需编写 SQL 语句。EF Core 是微软提供的经典 ORM 框架 Entity Framework 的升级版,支持最新的 .NET 平台,包括 .NET 6 和 .NET 7。

EF Core 的核心概念

  1. DbContext: DbContext 是 EF Core 与数据库交互的基础。它负责管理实体类与数据库表之间的映射,以及管理对象生命周期。它可以视为数据库的会话。

    public class AppDbContext : DbContext
    {
        public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
        {
        }
    
        public DbSet<User> Users { get; set; } // Users 表的映射
    }
    
  2. 实体类 (Entity Classes): 实体类代表数据库中的表。每个类通常对应数据库中的一张表,每个类中的属性对应表中的列。EF Core 通过这些实体类与数据库中的数据交互。

    public class User
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Email { get; set; }
    }
    
  3. DbSet: DbSet<T> 代表数据库表中的数据集合,T 是实体类。通过它,我们可以执行 CRUD 操作。它是 EF Core 中访问数据的核心 API。

    var users = context.Users.ToList(); // 从数据库中获取所有用户
    
  4. Migrations: 迁移(Migrations)是 EF Core 提供的功能,用来自动管理数据库的结构变化。通过 Migrations,你可以跟踪模型的变化,并将这些变化应用到数据库中。

    • 添加迁移

      dotnet ef migrations add InitialCreate
      
    • 更新数据库

      dotnet ef database update
      

使用步骤

  1. 安装 EF Core 包 使用 NuGet 安装 EF Core 和相关数据库提供程序。例如,要使用 SQL Server:

    dotnet add package Microsoft.EntityFrameworkCore.SqlServer
    dotnet add package Microsoft.EntityFrameworkCore.Tools
    
  2. 配置 DbContextProgram.csStartup.cs 中配置 DbContext,将数据库连接字符串传递给 EF Core。

    public class Program
    {
        public static void Main(string[] args)
        {
            var builder = WebApplication.CreateBuilder(args);
    
            // 配置 SQL Server 数据库
            builder.Services.AddDbContext<AppDbContext>(options =>
                options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
    
            var app = builder.Build();
            app.Run();
        }
    }
    

    appsettings.json 中添加连接字符串:

    {
        "ConnectionStrings": {
            "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=MyAppDb;Trusted_Connection=True;"
        }
    }
    
  3. 添加实体类 定义你的实体类,它们将映射到数据库中的表。

    public class User
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Email { get; set; }
    }
    
  4. 添加数据上下文 (DbContext)AppDbContext 中添加 DbSet 属性,用于实体类和数据库表的映射。

    public class AppDbContext : DbContext
    {
        public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
        {
        }
    
        public DbSet<User> Users { get; set; }
    }
    
  5. 创建迁移和数据库 在命令行中运行以下命令来创建迁移并更新数据库:

    dotnet ef migrations add InitialCreate
    dotnet ef database update
    

    这将基于实体类创建数据库表。

  6. 执行 CRUD 操作 在代码中,你可以使用 DbContext 执行 CRUD 操作。

    public class UserService
    {
        private readonly AppDbContext _context;
    
        public UserService(AppDbContext context)
        {
            _context = context;
        }
    
        public async Task AddUserAsync(User user)
        {
            _context.Users.Add(user);
            await _context.SaveChangesAsync();
        }
    
        public async Task<List<User>> GetAllUsersAsync()
        {
            return await _context.Users.ToListAsync();
        }
    }
    

EF Core 常用功能

  1. LINQ 查询: EF Core 支持通过 LINQ 进行查询:

    var users = await _context.Users
        .Where(u => u.Email.Contains("example"))
        .ToListAsync();
    
  2. 延迟加载 (Lazy Loading): 延迟加载是指只有在需要访问关联数据时,EF Core 才会加载相关联的数据。你可以通过安装 Microsoft.EntityFrameworkCore.Proxies 来实现延迟加载。

    dotnet add package Microsoft.EntityFrameworkCore.Proxies
    

    然后启用延迟加载:

    services.AddDbContext<AppDbContext>(options =>
        options.UseSqlServer(connectionString)
               .UseLazyLoadingProxies());
    
  3. 关系映射: EF Core 支持一对一、一对多、多对多的关系映射。你可以通过 Fluent APIData Annotations 来配置表之间的关系。

    • 一对多关系

      public class Post
      {
          public int PostId { get; set; }
          public string Title { get; set; }
      
          public int BlogId { get; set; }
          public Blog Blog { get; set; }
      }
      
      public class Blog
      {
          public int BlogId { get; set; }
          public string Url { get; set; }
      
          public List<Post> Posts { get; set; }
      }
      
  4. 并发控制: EF Core 提供了并发控制的功能,防止数据被多个用户同时修改时出现冲突。你可以使用 ConcurrencyToken 来实现。

    public class Product
    {
        public int ProductId { get; set; }
        public string Name { get; set; }
    
        [ConcurrencyCheck]
        public int StockCount { get; set; }
    }
    
  5. 事务: 在某些场景下,你可能需要事务支持来确保多个操作的原子性。EF Core 支持显式事务:

    using var transaction = await _context.Database.BeginTransactionAsync();
    try
    {
        // 执行数据库操作
        await _context.SaveChangesAsync();
        await transaction.CommitAsync();
    }
    catch
    {
        await transaction.RollbackAsync();
        throw;
    }
    

EF Core 与 EF6 的区别

  • 跨平台:EF Core 支持 Windows、Linux 和 macOS,而 EF6 只支持 Windows。
  • 性能:EF Core 在大多数情况下比 EF6 更快,并且提供了更好的查询优化。
  • 功能差异:虽然 EF Core 不包含所有 EF6 的功能,但它包含了许多现代特性,如批量操作、异步编程、LINQ 支持、NoSQL 数据库支持等。

总结

EF Core 是现代化的 ORM 工具,适用于跨平台开发。它的优势在于强大的 LINQ 查询、灵活的关系映射、多数据库支持,以及高性能。通过 EF Core,开发者可以在不关心底层数据库实现的情况下,编写高效、可扩展的数据访问代码。

DBFirst 的使用

在使用 Entity Framework Core 时,有两种常见的开发模式:Code FirstDatabase First。其中,Database First(简称 DB First)用于从现有数据库生成模型和上下文类。它适用于已经存在的数据库,并且你希望基于该数据库生成实体模型而不是手动编写实体类。

下面是 DB First 的使用步骤及示例:

1. 安装必要的 NuGet 包

首先,需要安装一些必要的 NuGet 包,确保你可以通过 EF Core 访问数据库并生成实体模型。

使用 .NET CLI 安装:

dotnet add package Microsoft.EntityFrameworkCore.Design
dotnet add package Microsoft.EntityFrameworkCore.Tools
dotnet add package Pomelo.EntityFrameworkCore.MySql -- 如果使用 MySQL
dotnet add package Microsoft.EntityFrameworkCore.SqlServer -- 如果使用 SQL Server

你可以根据具体的数据库类型选择合适的 EF Core 提供程序。例如,如果你使用的是 SQL Server,则安装 Microsoft.EntityFrameworkCore.SqlServer,如果使用 MySQL,则安装 Pomelo.EntityFrameworkCore.MySql

2. 使用 Scaffold-DbContext 生成模型

使用 Scaffold-DbContext 命令从现有数据库生成实体类和 DbContext 类。具体的语法如下:

dotnet ef dbcontext scaffold "YourConnectionString" Microsoft.EntityFrameworkCore.SqlServer -o Models
  • "YourConnectionString":替换为你的数据库连接字符串。
  • Microsoft.EntityFrameworkCore.SqlServer:替换为你使用的数据库提供程序,如 Pomelo.EntityFrameworkCore.MySql 对于 MySQL。
  • -o Models:指定生成的实体类和上下文类的输出目录。

3. 示例:使用 SQL Server

假设你要从 SQL Server 数据库中生成实体模型。以下是完整的步骤:

连接字符串:

"Server=localhost;Database=MyDatabase;User Id=myuser;Password=mypassword;"

生成实体模型:

dotnet ef dbcontext scaffold "Server=localhost;Database=MyDatabase;User Id=myuser;Password=mypassword;" Microsoft.EntityFrameworkCore.SqlServer -o Models

此命令会从数据库中读取架构信息并生成相应的实体类和 DbContext 类。生成的类会放在 Models 文件夹下。

4. 生成的代码结构

运行 Scaffold-DbContext 命令后,会在指定的目录中生成实体模型和上下文类。下面是它们的基本结构:

DbContext 类:

DbContext 是数据库上下文类,它管理实体模型与数据库的交互。

public partial class MyDatabaseContext : DbContext
{
    public MyDatabaseContext()
    {
    }

    public MyDatabaseContext(DbContextOptions<MyDatabaseContext> options)
        : base(options)
    {
    }

    public virtual DbSet<User> Users { get; set; }
    public virtual DbSet<Course> Courses { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        if (!optionsBuilder.IsConfigured)
        {
            optionsBuilder.UseSqlServer("Server=localhost;Database=MyDatabase;User Id=myuser;Password=mypassword;");
        }
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<User>(entity =>
        {
            entity.HasKey(e => e.Id);
            entity.Property(e => e.Name).HasMaxLength(100);
        });

        modelBuilder.Entity<Course>(entity =>
        {
            entity.HasKey(e => e.Id);
            entity.Property(e => e.CourseName).HasMaxLength(100);
        });
    }
}

实体类:

根据数据库中的表,EF Core 会生成对应的实体类。

public partial class User
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int CourseId { get; set; }

    public virtual Course Course { get; set; }
}

public partial class Course
{
    public int Id { get; set; }
    public string CourseName { get; set; }

    public virtual ICollection<User> Users { get; set; }
}

5. 使用生成的 DbContext 进行数据库操作

生成代码后,可以直接通过 DbContext 类进行数据库操作。以下是如何使用 MyDatabaseContext 来查询数据的示例:

using System;
using System.Linq;

public class Program
{
    public static void Main(string[] args)
    {
        using (var context = new MyDatabaseContext())
        {
            var users = context.Users
                               .Where(u => u.Name.Contains("Alice"))
                               .ToList();

            foreach (var user in users)
            {
                Console.WriteLine($"User: {user.Name}, Course: {user.Course.CourseName}");
            }
        }
    }
}

6. 重新生成模型

如果数据库架构发生了变化,例如添加了新的表或更改了现有表结构,您可以再次运行 Scaffold-DbContext 命令来重新生成模型。使用 --force 选项来覆盖现有的模型类。

dotnet ef dbcontext scaffold "YourConnectionString" Microsoft.EntityFrameworkCore.SqlServer -o Models --force

7. 迁移和数据库更新

如果你希望通过迁移来管理数据库结构更新,可以使用 EF Core 的迁移功能。在使用 DB First 模式时,通常需要手动管理数据库架构变更。你可以通过以下命令为模型添加迁移并更新数据库:

dotnet ef migrations add MyNewMigration
dotnet ef database update

总结

  1. DB First 是在 EF Core 中根据已有数据库生成模型和上下文类的方式,适用于已有数据库的项目。
  2. 通过使用 Scaffold-DbContext 命令,EF Core 会根据数据库架构生成实体类和 DbContext 类。
  3. 你可以使用生成的 DbContext 类直接进行数据库的 CRUD 操作。
  4. 当数据库架构发生变更时,可以重新运行 Scaffold-DbContext 命令来更新模型,或使用 EF Core 迁移功能管理数据库的更新。

CodeFirst

Code First 是 Entity Framework Core(EF Core)的一种开发方式,它允许开发者通过编写实体类代码来定义数据库结构,而不是从现有的数据库生成实体类。在这种方式下,EF Core 根据你定义的实体类自动生成数据库,并通过迁移功能管理数据库架构的变更。

Code First 使用步骤

  1. 创建实体类
  2. 配置 DbContext
  3. 使用迁移管理数据库

1. 创建实体类

Code First 的核心是通过 C# 类来定义数据库表。每个实体类通常对应数据库中的一张表,每个类的属性对应表中的列。

示例:

public class User
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }
}

public class Post
{
    public int Id { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }
    public int UserId { get; set; }
    
    // 定义外键关系
    public User User { get; set; }
}

在上面的例子中,UserPost 类将映射到数据库中的 UsersPosts 表,类中的属性映射到表中的列。

2. 配置 DbContext

DbContext 是用于与数据库交互的核心类,继承自 Microsoft.EntityFrameworkCore.DbContext。在 DbContext 中,开发者需要定义实体集 (DbSet<TEntity>) 来映射到数据库中的表。

示例:

public class ApplicationDbContext : DbContext
{
    // 定义表的映射
    public DbSet<User> Users { get; set; }
    public DbSet<Post> Posts { get; set; }

    // 配置连接字符串
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        // 这里使用 SQL Server 作为例子
        optionsBuilder.UseSqlServer("Server=localhost;Database=MyApp;User Id=sa;Password=yourpassword;");
    }
}

3. 使用迁移管理数据库

EF Core 的迁移功能允许开发者将实体类中的变更应用到数据库中。迁移可以追踪每次实体类的修改,并生成 SQL 代码以更新数据库结构。

步骤:

  1. 安装必要的 NuGet 包

确保项目中安装了以下 NuGet 包:

dotnet add package Microsoft.EntityFrameworkCore.SqlServer
dotnet add package Microsoft.EntityFrameworkCore.Design
dotnet add package Microsoft.EntityFrameworkCore.Tools
  1. 添加初始迁移

通过以下命令创建初始迁移,这会为数据库生成对应的 SQL 结构。

dotnet ef migrations add InitialCreate

InitialCreate 是迁移的名称。这个命令会在项目中创建一个 Migrations 文件夹,其中包含生成的迁移代码。

  1. 更新数据库

运行以下命令,将迁移应用到数据库:

dotnet ef database update

此命令会根据迁移文件生成并执行 SQL 语句,将数据库结构与代码中的实体类保持同步。

4. CRUD 操作

数据库创建完毕后,你可以使用 DbContext 进行基本的 CRUD 操作。

示例:插入和查询数据

using (var context = new ApplicationDbContext())
{
    // 添加用户
    var user = new User { Name = "Alice", Email = "alice@example.com" };
    context.Users.Add(user);
    context.SaveChanges(); // 保存变更到数据库

    // 查询用户
    var users = context.Users.ToList();
    foreach (var u in users)
    {
        Console.WriteLine($"User: {u.Name}, Email: {u.Email}");
    }
}

示例:更新数据

using (var context = new ApplicationDbContext())
{
    var user = context.Users.FirstOrDefault(u => u.Name == "Alice");
    if (user != null)
    {
        user.Email = "newalice@example.com";
        context.SaveChanges(); // 保存变更
    }
}

示例:删除数据

using (var context = new ApplicationDbContext())
{
    var user = context.Users.FirstOrDefault(u => u.Name == "Alice");
    if (user != null)
    {
        context.Users.Remove(user);
        context.SaveChanges(); // 保存变更
    }
}

5. 迁移和更新

如果需要对实体类进行修改(例如添加新字段或更改表结构),可以通过以下方式管理数据库更新:

  1. 修改实体类 修改实体类,例如在 User 类中添加新的属性:

    public string PhoneNumber { get; set; }
    
  2. 添加迁移 为新的修改创建一个迁移:

    dotnet ef migrations add AddPhoneNumberToUser
    
  3. 更新数据库 将新迁移应用到数据库:

    dotnet ef database update
    

总结

  1. Code First 模式允许开发者通过编写 C# 实体类定义数据库结构。
  2. DbContext 类用于管理数据库操作,开发者需要在该类中配置数据库连接和实体集。
  3. 迁移 是 Code First 的核心功能,用于跟踪代码中的数据库架构变更,并将这些变更应用到实际的数据库。
  4. 通过简单的 CRUD 操作,开发者可以轻松管理数据库中的数据。

这种方式非常适合从头开始定义数据库结构,并且在开发过程中随着需求的变更灵活地更新数据库。

影子属性和Set适配

在 Entity Framework Core 中,影子属性(Shadow Properties)Set 适配 是两个有助于扩展和简化数据库模型处理的特性。

1. 影子属性(Shadow Properties)

1.1 什么是影子属性?

影子属性是指那些不在实体类中显式定义的属性,它们仅在 EF Core 的模型中存在并维护。这些属性在实体类代码中不可见,但会映射到数据库表中的列,并由 EF Core 在幕后进行管理。

影子属性常用于:

  • 追踪审计字段(如 CreatedDateModifiedDate)。
  • 在不改变现有实体模型代码的情况下,扩展数据库表的功能。

1.2 使用影子属性

影子属性通常通过 Fluent API 来定义,而不是在实体类中声明。

示例:

假设你有一个简单的 Blog 实体类:

public class Blog
{
    public int Id { get; set; }
    public string Url { get; set; }
}

现在,你想为这个实体添加一个 LastUpdated 字段来存储最后更新时间,但你不想在 Blog 类中显式定义它,可以通过影子属性来实现。

public class ApplicationDbContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        // 定义一个名为 "LastUpdated" 的影子属性
        modelBuilder.Entity<Blog>()
            .Property<DateTime>("LastUpdated");
    }

    // 保存操作时自动更新 "LastUpdated"
    public override int SaveChanges()
    {
        foreach (var entry in ChangeTracker.Entries<Blog>())
        {
            if (entry.State == EntityState.Added || entry.State == EntityState.Modified)
            {
                entry.Property("LastUpdated").CurrentValue = DateTime.Now;
            }
        }
        return base.SaveChanges();
    }
}

在这个例子中,虽然 LastUpdated 没有在 Blog 实体类中定义,但它依然会被映射到数据库表中的一列,并且每次添加或更新 Blog 实体时,都会更新此属性。

1.3 使用影子属性的好处:

  • 可以减少实体类中不必要的字段。
  • 在不修改代码的情况下扩展数据模型(如添加审计字段)。
  • 适用于需要在数据库中存储额外信息但不希望让开发者或其他层接触的场景。

1.4 访问影子属性

你不能直接访问影子属性,因为它们不在实体类中声明。要访问影子属性,你可以使用 Entry API 来获取和设置值。

示例:获取影子属性值

using (var context = new ApplicationDbContext())
{
    var blog = context.Blogs.First();
    var lastUpdated = context.Entry(blog).Property("LastUpdated").CurrentValue;
    Console.WriteLine(lastUpdated);
}

示例:设置影子属性值

using (var context = new ApplicationDbContext())
{
    var blog = context.Blogs.First();
    context.Entry(blog).Property("LastUpdated").CurrentValue = DateTime.Now;
    context.SaveChanges();
}

2. Set 适配

2.1 什么是 Set 适配?

在 Entity Framework Core 中,DbContext.Set<TEntity>() 是一个灵活的适配方法,允许开发者在 运行时动态处理实体类型。当你需要在不知道确切实体类型的情况下进行数据库操作时,Set<TEntity>() 非常有用。例如,如果你构建一个通用的存储库模式,或是在处理动态的、基于配置的实体类型时,Set 可以极大简化你的代码。

2.2 Set 的使用示例

假设我们有多个实体类,但希望能够以一种通用的方式处理它们。

public class ApplicationDbContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }
    public DbSet<Post> Posts { get; set; }
}

public class Blog
{
    public int Id { get; set; }
    public string Url { get; set; }
}

public class Post
{
    public int Id { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }
}

现在,如果你想构建一个通用的方法,可以接受任何实体类型并查询它的所有数据:

public IEnumerable<object> GetAllEntities(Type entityType)
{
    using (var context = new ApplicationDbContext())
    {
        // 使用 Set 动态获取实体类型的 DbSet
        return context.Set(entityType).ToList();
    }
}

你可以像这样调用 GetAllEntities 方法:

var blogs = GetAllEntities(typeof(Blog)); // 获取所有 Blog 实体
var posts = GetAllEntities(typeof(Post)); // 获取所有 Post 实体

2.3 Set 的好处

  • 通用化操作:可以编写无需知道具体实体类的通用代码。
  • 灵活性:适用于仓储模式或动态实体类型的处理。
  • 代码简洁:避免了大量重复的 DbSet<TEntity> 获取代码。

3. 影子属性和 Set 适配结合使用

Set 和影子属性可以一起使用来实现动态和通用的操作,同时在不修改实体类代码的情况下扩展数据模型。例如,你可以使用 Set 动态获取实体,并使用影子属性扩展这些实体的数据模型。

示例:

public void UpdateLastUpdatedForAllEntities(Type entityType)
{
    using (var context = new ApplicationDbContext())
    {
        var entities = context.Set(entityType).ToList();

        foreach (var entity in entities)
        {
            var entry = context.Entry(entity);
            if (entry.Property("LastUpdated") != null)
            {
                entry.Property("LastUpdated").CurrentValue = DateTime.Now;
            }
        }

        context.SaveChanges();
    }
}

总结

  • 影子属性:允许开发者不在实体类中定义属性,但可以在数据库中存储并管理这些数据。常用于审计、日志记录等场景。
  • Set 适配:提供了一种通用化的方式来动态处理不同类型的实体,尤其适合通用仓储模式和基于配置的场景。

join 的使用

在 C# 中,join 是 LINQ 查询表达式的一部分,用于将两个或多个集合(例如列表或数据库表)根据指定条件进行内连接操作。join 允许你根据两个集合中的公共字段关联它们,并返回符合条件的结果。

join 的工作方式类似于 SQL 中的 INNER JOIN,它只返回那些在两个集合中匹配的元素。如果没有匹配项,则不会包含在结果集中。

1. LINQ join 的基本用法

语法:

var result = from item1 in collection1
             join item2 in collection2
             on item1.Property equals item2.Property
             select new { item1, item2 };

2. 示例代码

示例 1:简单 join 操作

假设你有两个集合:studentscourses。你希望根据课程 ID 将学生与他们所选的课程关联起来。

public class Student
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int CourseId { get; set; }
}

public class Course
{
    public int Id { get; set; }
    public string CourseName { get; set; }
}

public class Program
{
    public static void Main(string[] args)
    {
        List<Student> students = new List<Student>
        {
            new Student { Id = 1, Name = "Alice", CourseId = 101 },
            new Student { Id = 2, Name = "Bob", CourseId = 102 },
            new Student { Id = 3, Name = "Charlie", CourseId = 101 }
        };

        List<Course> courses = new List<Course>
        {
            new Course { Id = 101, CourseName = "Math" },
            new Course { Id = 102, CourseName = "English" }
        };

        var studentCourses = from student in students
                             join course in courses
                             on student.CourseId equals course.Id
                             select new { StudentName = student.Name, CourseName = course.CourseName };

        foreach (var sc in studentCourses)
        {
            Console.WriteLine($"Student: {sc.StudentName}, Course: {sc.CourseName}");
        }
    }
}

输出结果:

Student: Alice, Course: Math
Student: Charlie, Course: Math
Student: Bob, Course: English

示例 2:带条件的 join

假设我们只希望得到选修了 "Math" 课程的学生。

var mathStudents = from student in students
                   join course in courses
                   on student.CourseId equals course.Id
                   where course.CourseName == "Math"
                   select new { StudentName = student.Name, CourseName = course.CourseName };

foreach (var ms in mathStudents)
{
    Console.WriteLine($"Student: {ms.StudentName}, Course: {ms.CourseName}");
}

输出结果:

Student: Alice, Course: Math
Student: Charlie, Course: Math

3. join 的其他形式

示例 3:join 多个集合

假设我们有三个集合,并希望将它们关联在一起。可以通过链式 join 来实现。

public class Teacher
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int CourseId { get; set; }
}

List<Teacher> teachers = new List<Teacher>
{
    new Teacher { Id = 1, Name = "Mr. Smith", CourseId = 101 },
    new Teacher { Id = 2, Name = "Mrs. Johnson", CourseId = 102 }
};

var studentTeacherCourses = from student in students
                            join course in courses on student.CourseId equals course.Id
                            join teacher in teachers on course.Id equals teacher.CourseId
                            select new
                            {
                                StudentName = student.Name,
                                CourseName = course.CourseName,
                                TeacherName = teacher.Name
                            };

foreach (var item in studentTeacherCourses)
{
    Console.WriteLine($"Student: {item.StudentName}, Course: {item.CourseName}, Teacher: {item.TeacherName}");
}

输出结果:

Student: Alice, Course: Math, Teacher: Mr. Smith
Student: Charlie, Course: Math, Teacher: Mr. Smith
Student: Bob, Course: English, Teacher: Mrs. Johnson

4. GroupJoin

GroupJoin 是 LINQ 的另一种形式,它类似于 SQL 的 LEFT JOIN。它会返回所有的左侧集合元素,即使右侧集合没有匹配的元素。

示例 4:GroupJoin

var grouped = from course in courses
              join student in students
              on course.Id equals student.CourseId
              into studentGroup
              select new
              {
                  CourseName = course.CourseName,
                  Students = studentGroup
              };

foreach (var item in grouped)
{
    Console.WriteLine($"Course: {item.CourseName}");
    foreach (var student in item.Students)
    {
        Console.WriteLine($"  Student: {student.Name}");
    }
}

输出结果:

Course: Math
  Student: Alice
  Student: Charlie
Course: English
  Student: Bob

5. join 与扩展方法

除了查询表达式,LINQ 还提供了扩展方法的方式来使用 join

var studentCourses = students.Join(
    courses,
    student => student.CourseId,
    course => course.Id,
    (student, course) => new { StudentName = student.Name, CourseName = course.CourseName }
);

foreach (var sc in studentCourses)
{
    Console.WriteLine($"Student: {sc.StudentName}, Course: {sc.CourseName}");
}

总结

  • join:用于连接两个集合,根据指定条件匹配元素。
  • GroupJoin:类似于 SQL 的 LEFT JOIN,即使没有匹配元素,左侧集合的元素仍然返回。
  • 扩展方法形式:除了查询表达式,还可以使用 Join 扩展方法来进行连接操作。

LINQ 的 join 操作让你可以轻松地在内存中进行集合之间的关联,并且使用方式非常灵活。

版权协议:MIT返回列表