使用实体数组进行批量连表查询

使用实体数组进行批量连表查询


场景说明

在实际业务中,经常需要根据一组条件批量查询某张表。例如:有一批学生信息(姓名、年龄、班级),需要判断哪些在数据库中存在、哪些不存在。

传统做法是逐条查询或在 SQL 中用 IN 拼接,而 LINQ 提供了一种更优雅的方式——将条件封装为实体数组,直接与数据表进行连接查询


表结构

CREATE TABLE [dbo].[Student](
    [id] [int] IDENTITY(1,1) NOT NULL,
    [name] [nvarchar](50) NULL,
    [age] [int] NULL,
    [birthday] [datetime] NULL,
    [nation] [nvarchar](20) NULL,
    [classname] [nvarchar](20) NULL,
    CONSTRAINT [PK_Student] PRIMARY KEY CLUSTERED ([id] ASC)
) ON [PRIMARY]Code language: CSS (css)

查询条件实体

public class SearchKeyInfo
{
    public string name;
    public int age;
    public string classname;
}Code language: PHP (php)

完整示例代码

using System;
using System.Collections.Generic;
using System.Linq;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var db = new DataClasses1DataContext())
            {
                // 构造查询条件列表
                var list = new List<SearchKeyInfo>();
                list.Add(new SearchKeyInfo() { name = "张三丰", age = 12, classname = "五班" });
                list.Add(new SearchKeyInfo() { name = "李四", age = 36, classname = "二班" });
                list.Add(new SearchKeyInfo() { name = "赵六", age = 2, classname = "四班" });

                // 方式一:内连接 —— 只返回能匹配到的记录
                var q = from l in list
                        join s in db.Student
                        on new { l.name, l.age, l.classname }
                        equals new { s.name, s.age, s.classname }
                        select new { l, s };
                var result = q.ToList();

                // 方式二:调用 ToArray() 后再连接(效果相同,条件数组先加载到内存)
                var q1 = from l in list.ToArray()
                         join s in db.Student
                         on new { l.name, l.age, l.classname }
                         equals new { s.name, s.age, s.classname }
                         select new { l, s };
                var result1 = q1.ToList();

                // 方式三:左连接 —— 判断哪些条件能查到、哪些查不到
                var q3 = from l in list
                         join s in db.Student
                         on new { l.name, l.age, l.classname }
                         equals new { s.name, s.age, s.classname } into sl
                         from g in sl.DefaultIfEmpty()
                         select new { l, g, nofound = g == null };
                var result3 = q3.ToList();
            }
        }
    }

    public class SearchKeyInfo
    {
        public string name;
        public int age;
        public string classname;
    }
}Code language: JavaScript (javascript)

三种方式对比

方式连接类型行为适用场景
方式一 q内连接只返回数据库中存在匹配的条件记录批量校验哪些条件有效
方式二 q1内连接 + ToArray()先将条件列表加载到内存再连接,效果同上条件数据量小、需提前物化
方式三 q3左连接所有条件都列出,匹配不到的 gnullnofound 标记是否存在批量校验哪些条件缺失

执行结果示意

假设数据库中只有”李四, 36, 二班”存在:

方式一 / 方式二结果(仅 1 条):
┌────────────┬──────────────────────────────┐
│ 条件       │ 匹配的学生记录               │
├────────────┼──────────────────────────────┤
│ 李四,36,二班│ { id:2, name:"李四", ... }   │
└────────────┴──────────────────────────────┘

方式三结果(3 条,全部条件):
┌──────────────┬──────────────────┬─────────┐
│ 条件         │ 匹配记录 g       │ nofound │
├──────────────┼──────────────────┼─────────┤
│ 张三丰,12,五班│ nullTrue    │
│ 李四,36,二班  │ { id:2, ... }    │ False   │
│ 赵六,2,四班   │ nullTrue    │
└──────────────┴──────────────────┴─────────┘Code language: PHP (php)

关键要点

匿名对象字段必须一致

复合键连接时,两边匿名对象的属性名、类型、顺序必须完全匹配:

// 正确
new { l.name, l.age, l.classname } equals new { s.name, s.age, s.classname }

// 错误:属性名不一致
new { l.name, l.age, l.classname } equals new { s.name, s.age, s.classname } // 如果 s 的属性名不同则编译失败Code language: JavaScript (javascript)

list vs list.ToArray()

写法行为
from l in listlist 作为 IEnumerable,LINQ to SQL 会尝试将整个集合作为参数化查询发送
from l in list.ToArray()先调用 ToArray() 将集合物化到内存,后续连接操作在内存中完成

当条件集合较大时,建议先 ToArray() 避免 LINQ to SQL 尝试将整个列表翻译为 SQL 参数数组导致参数过多异常。

左连接中 nofound 标记

select new { l, g, nofound = g == null }Code language: JavaScript (javascript)

通过 g == null 判断当前条件在数据库中是否无匹配,方便后续业务逻辑区分处理(如插入缺失数据)。


典型应用场景

场景做法
批量查重左连接,筛选 nofound == true 的记录进行插入
批量校验内连接,只处理已存在的记录
数据同步左连接,根据 nofound 决定 INSERT 或 UPDATE

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注