Entity Framework动态Where子句与LINQ多表扁平化自定义列查询需求
三表关联、动态过滤与扁平化的LINQ实现方案
嘿,这需求我太熟了!咱们一步步来搞定这个LINQ的实现,涵盖三表关联、动态Where过滤、自定义列查询还有一对多关系的扁平化处理。
首先,先把对应数据库表的实体类定义好(毕竟LINQ操作一般都是基于实体模型的):
// 对应Person表 public class Person { public int Id { get; set; } public string SocialClass { get; set; } public DateTime CreatedDate { get; set; } // 导航属性,用来关联朋友和组 public ICollection<PersonFriend> Friends { get; set; } public ICollection<PersonGroup> Groups { get; set; } } // 对应Person_Friend表 public class PersonFriend { public int Id { get; set; } public int PersonId { get; set; } public int FriendPersonId { get; set; } public string FriendType { get; set; } // 可选:导航到朋友的Person实体,方便获取朋友的详细信息 public Person Friend { get; set; } } // 对应Person_Group表(这里假设表有GroupName字段,你可以根据实际结构调整) public class PersonGroup { public int Id { get; set; } public int PersonId { get; set; } public string GroupName { get; set; } }
1. 动态生成Where过滤条件
要动态拼接过滤条件,最方便的是用PredicateBuilder——你可以自己实现一个简单版本,或者用LINQKit库。下面先给你手写一个轻量版的PredicateBuilder,然后演示怎么动态组合条件:
先加个PredicateBuilder辅助类
public static class PredicateBuilder { public static Expression<Func<T, bool>> True<T>() { return _ => true; } public static Expression<Func<T, bool>> False<T>() { return _ => false; } public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> expr1, Expression<Func<T, bool>> expr2) { var invokedExpr = Expression.Invoke(expr2, expr1.Parameters.Cast<Expression>()); return Expression.Lambda<Func<T, bool>>(Expression.AndAlso(expr1.Body, invokedExpr), expr1.Parameters); } public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> expr1, Expression<Func<T, bool>> expr2) { var invokedExpr = Expression.Invoke(expr2, expr1.Parameters.Cast<Expression>()); return Expression.Lambda<Func<T, bool>>(Expression.OrElse(expr1.Body, invokedExpr), expr1.Parameters); } }
动态拼接过滤条件
假设这些过滤参数是动态传入的(比如从前端表单来的),我们可以灵活组合:
// 初始化一个"总是为真"的谓词,方便后续拼接 var predicate = PredicateBuilder.True<Person>(); // 动态参数示例 string targetSocialClass = "Middle"; DateTime? startCreatedDate = new DateTime(2020, 1, 1); DateTime? endCreatedDate = DateTime.Now; // 按社会阶层过滤 if (!string.IsNullOrEmpty(targetSocialClass)) { predicate = predicate.And(p => p.SocialClass == targetSocialClass); } // 按创建日期范围过滤 if (startCreatedDate.HasValue) { predicate = predicate.And(p => p.CreatedDate >= startCreatedDate.Value); } if (endCreatedDate.HasValue) { predicate = predicate.And(p => p.CreatedDate <= endCreatedDate.Value); }
2. 三表关联+自定义列+扁平化处理
接下来就是核心的查询部分了,分两种场景:用EF Core(直接操作数据库)和纯LINQ to Objects(操作内存集合)。
场景1:EF Core查询数据库
如果是用EF Core连接数据库,直接利用导航属性关联三张表,然后做扁平化处理:
using (var dbContext = new YourDbContext()) // 替换成你的DbContext { var query = dbContext.Persons .Include(p => p.Friends) // 关联Person_Friend表 .ThenInclude(f => f.Friend) // 如果需要朋友的Person信息,就加这个 .Include(p => p.Groups) // 关联Person_Group表 .Where(predicate) // 应用动态过滤条件 .Select(p => new { // 自定义列,按需选择 PersonId = p.Id, SocialClass = p.SocialClass, CreatedDate = p.CreatedDate, // 扁平化朋友信息:把多个朋友的信息拼接成一个字符串 FriendsList = string.Join(", ", p.Friends.Select(f => $"ID:{f.FriendPersonId}, Type:{f.FriendType}")), // 扁平化组信息:把所属组名拼接成一个字符串 GroupsList = string.Join(", ", p.Groups.Select(g => g.GroupName)) }); // 执行查询,得到结果列表 var result = query.ToList(); }
场景2:LINQ to Objects操作内存集合
如果是操作内存里的集合,就用join语法来关联三张表:
// 假设这是你的内存集合 List<Person> persons = GetPersons(); List<PersonFriend> personFriends = GetPersonFriends(); List<PersonGroup> personGroups = GetPersonGroups(); var query = from p in persons // 左连接朋友表,得到当前用户的所有朋友 join pf in personFriends on p.Id equals pf.PersonId into friendsGroup // 左连接组表,得到当前用户的所有组 join pg in personGroups on p.Id equals pg.PersonId into groupsGroup // 应用动态过滤条件(需要编译谓词) where predicate.Compile()(p) select new { PersonId = p.Id, SocialClass = p.SocialClass, CreatedDate = p.CreatedDate, FriendsList = string.Join(", ", friendsGroup.Select(f => $"ID:{f.FriendPersonId}, Type:{f.FriendType}")), GroupsList = string.Join(", ", groupsGroup.Select(g => g.GroupName)) }; var result = query.ToList();
小提示
- 扁平化的其他方式:如果不想用字符串拼接,也可以把一对多的序列化成JSON字符串,比如用
System.Text.Json.JsonSerializer.Serialize(p.Friends),这样后续解析更方便。 - 自定义DTO:如果需要把结果返回给前端或者其他层,建议定义一个专门的DTO类,代替匿名类,比如
PersonDetailDto,这样类型更明确。 - 性能优化:如果是EF Core查询,记得不要加载不需要的字段,用
Select只选需要的列,避免查询全表数据。
内容的提问来源于stack exchange,提问作者Blee
相关产品推荐
相关产品推荐

