EF6中如何用Linq Where+Any查询非原始对象集合匹配数据?
解决EF6中批量匹配实体集合的查询报错问题
问题背景
原代码通过循环遍历itemData集合,每个元素单独查询数据库匹配ItemId和AttributeId,导致N次数据库调用,性能低下。尝试改用批量查询时,使用itemData.Any(...)触发错误:无法创建ItemData类型的常量值,仅支持基元类型或枚举类型,原因是EF6无法将内存中的实体集合直接转换为SQL查询条件。
原低效实现代码
List<ItemData> itemData = new List<ItemData>() { new ItemData() { Id = null, ItemId = 1, AttributeId = 1001, Value = "Test" }, new ItemData() { Id = null, ItemId = 1, AttributeId = 1002, Value = "Test2" }, new ItemData() { Id = null, ItemId = 2, AttributeId = 1001, Value = "Test3" } }; List<ItemData> dbData = new List<ItemData>() // 模拟数据库数据 { new ItemData() { Id = 1, ItemId = 1, AttributeId = 1001, Value = "Test234324" }, new ItemData() { Id = 2, ItemId = 1, AttributeId = 1005, Value = "Test223234" }, new ItemData() { Id = 3, ItemId = 1, AttributeId = 1006, Value = "Test234343" }, new ItemData() { Id = 4, ItemId = 2, AttributeId = 1001, Value = "Test234242" }, new ItemData() { Id = 5, ItemId = 2, AttributeId = 1002, Value = "Test234342" }, new ItemData() { Id = 6, ItemId = 2, AttributeId = 1005, Value = "Test323423" } }; var newItems = new List<ItemData>(); foreach (var item in itemData) { var dbItem = db.ItemData.Where(c => c.ItemId == item.ItemId && c.AttributeId == item.AttributeId).SingleOrDefault(); if (dbItem != null) // 更新 { dbItem.Value = item.Value; } else // 添加 { newItems.Add(item); } } db.ItemData.AddRange(newItems); db.SaveChanges();
尝试的批量查询代码(报错)
using (var db = new MyDbContext()) { var dbItems = db.ItemData.Where(c => itemData.Any(d => d.ItemId == c.ItemId && d.AttributeId == c.AttributeId)).ToList(); foreach (var dbItem in dbItems) { var item = itemData.Where(c => c.ItemId == dbItem.ItemId && c.AttributeId == dbItem.ItemId).SingleOrDefault(); dbItem.Value = item.Value; } // 处理新增逻辑 ... db.SaveChanges(); }
解决方案
EF6不支持直接将实体集合作为查询条件的对比对象,需要将itemData中的复合键(ItemId+AttributeId)转换为基元类型的组合,再通过Contains或构造OR条件实现批量查询。
方法1:使用Tuple组合键(推荐)
先将itemData的键转换为Tuple<int, int>集合,再用Contains查询数据库中匹配的记录:
using (var db = new MyDbContext()) { // 提取itemData中的复合键集合 var targetKeys = itemData.Select(d => Tuple.Create(d.ItemId, d.AttributeId)).ToList(); // 批量查询数据库中匹配的记录 var dbItems = db.ItemData.Where(c => targetKeys.Contains(Tuple.Create(c.ItemId, c.AttributeId))).ToList(); // 构建内存查询字典,提升匹配效率 var itemDataDict = itemData.ToDictionary(d => Tuple.Create(d.ItemId, d.AttributeId)); // 批量更新匹配的记录 foreach (var dbItem in dbItems) { var key = Tuple.Create(dbItem.ItemId, dbItem.AttributeId); if (itemDataDict.TryGetValue(key, out var item)) { dbItem.Value = item.Value; } } // 处理新增:找出itemData中不在数据库里的记录 var existingKeys = dbItems.Select(c => Tuple.Create(c.ItemId, c.AttributeId)).ToHashSet(); var itemsToAdd = itemData.Where(d => !existingKeys.Contains(Tuple.Create(d.ItemId, d.AttributeId))).ToList(); db.ItemData.AddRange(itemsToAdd); db.SaveChanges(); }
方法2:手动构建OR条件(适合少量数据)
如果itemData数据量不大,可以手动拼接多个(ItemId == x && AttributeId == y)的OR条件:
using (var db = new MyDbContext()) { var predicate = PredicateBuilder.False<ItemData>(); foreach (var item in itemData) { var tempItem = item; // 避免闭包陷阱 predicate = predicate.Or(c => c.ItemId == tempItem.ItemId && c.AttributeId == tempItem.AttributeId); } var dbItems = db.ItemData.Where(predicate).ToList(); // 后续更新和新增逻辑同方法1 var itemDataDict = itemData.ToDictionary(d => Tuple.Create(d.ItemId, d.AttributeId)); foreach (var dbItem in dbItems) { if (itemDataDict.TryGetValue(Tuple.Create(dbItem.ItemId, dbItem.AttributeId), out var item)) { dbItem.Value = item.Value; } } var existingKeys = dbItems.Select(c => Tuple.Create(c.ItemId, c.AttributeId)).ToHashSet(); var itemsToAdd = itemData.Where(d => !existingKeys.Contains(Tuple.Create(d.ItemId, d.AttributeId))).ToList(); db.ItemData.AddRange(itemsToAdd); db.SaveChanges(); }
注:
PredicateBuilder来自System.Linq.Dynamic包,需要先安装该NuGet包。
关键说明
- 核心思路是将实体对象的对比转换为基元类型组合的对比,EF6能正确将Tuple的
Contains转换为SQL的复合键IN条件。 - 使用字典或HashSet做内存匹配,避免循环中多次遍历
itemData,提升内存操作效率。 - 整个流程仅触发1次数据库查询+1次保存,大幅减少数据库交互次数。
内容的提问来源于stack exchange,提问作者Robert
相关产品推荐
相关产品推荐

