IQueryable 与 IEnumerable
解决一些更深层次问题的一个好的开始是花一些时间发现之间的差异
-
IQueryable andIEnumerable<T>
也许也在之间
- “常规代表”,例如Func<T>和“表达式”,例如Expression<T>
因为虽然它们具有相似的形式,但它们的目的和行为不同。
现在回到你的问题
首先,让我们举几件事:
- 我们将 RAM 托管集合称为
MyEmployee
实例THE LIST
- 让我们调用数据库表(最有可能的标题是“Employee(s)”)桌子
遗憾的是,您在撰写问题时没有指定一些非常重要的细节。
这导致我提出 4 个不同的答案。
答案将根据以下 2 个问题的真值进行分类:
- Is THE LIST huge?
- Is 桌子 huge?
我们有 4 个截然不同的案例:
- No, No
- No, Yes
- Yes, No
- Yes, Yes
现在你可以想象第四个可能是最丑的。
当列表不是很大时
对于情况 1 和 2,您可以从不同的角度思考您的问题:
假设你需要获取ONE(或零)来自数据库的记录基于正是 1参数是一个ID。如果您要表演JOIN?
答案是:绝对NOT。
看一下这段代码:
var query = from employee in _context.Employee
where employee.EmployeeId == 23
select employee;
var found = query.FirstOrDefault();
如果我想获取与以下内容关联的记录怎么办正是 2参数?
我可以用类似的方式实现这一点:
var query = from employee in _context.Employee
where employee.EmployeeId == 23 || employee.EmployeeId == 24
select employee;
var results = query.ToArray();
if (results.Length == 0)
// didn't find anyone of the presumably existing records
else if (results.Length == 1) {
if (results[0].EmployeeId == 23)
// then we found the 23
else
// the other one
} else if (results.Length == 2)
// found both, look inside to see which is which
我特意写了算法的收尾工作(if
部分)以一种愚蠢的方式,以避免额外的混乱。
这将是一种更人性化的收尾方式:
...
var results = ... got them (see above)
var map = results.ToDictionary(keySelector: x => x.EmployeeId);
var count = map.Count; // this gives you the number of results, same as results.Length
var have23 = map.ContainsKey(23); // this tells you whether you managed to fetch a certain id
var record23 = map[23]; // this actually gives you the record
foreach (var key in map.Keys) { .. } // will iterate over the fetched ids
foreach (var record in map.Values) { .. } // will iterate over the fetched values
不用担心ToDictionary扩展方法。
它有NOTHING与 EntityFramework 相关(通过单击进行查找)。
现在..回到我们的故事:如果您想获取与 15 个 id 关联的记录怎么办?
停止。这是要去哪里?我是否要求您为每个可能的 id 数量硬编码一个不同的查询?
当然不是。
只要 id 的数量“相对较小”(意味着某人或您自己允许您以该请求大小轰炸数据库),您就可以很好地使用“参数列表中的列”SQL 构造。
如何指示 LINQ to SQL 或 EF 在 SQL 端转换为“x IN y”操作而不是“x = y”操作?
通过使用相应类型的原始数组和Contains
方法。
换句话说,获得以下负载:
var query = from employee in _context.Employee
where listOfIds.Contains( employee.EmployeeId )
select employee;
var results = query.ToArray();
但是您需要一个“Id 列表”而不是“MyEmployee 实例列表”。
你可以很容易地做到这一点,如下所示:
List<MyEmployee> originalList = new List<MyEmployee>();
// ... say you populate this somehow, or you've received it from elsewhere
int[] listOfIds = (from employee in originalList
select employee.EntityId).ToArray();
// .. and then carry on with the EF query
请注意对集合的查询表现为IEnumerable<T>
实例,而不是作为IQueryable<T>
实例,与 EF 或 LINQ to SQL 或任何其他数据库或外部数据服务无关。
如果桌子不是很大
然后,您可以避免实际使用 EF 进行复杂查询,仅将其用于“全表获取”,将结果临时存储在 .NET 进程中,并根据您的喜好使用常规 LINQ。
这个故事的关键是从头开始获取整个表。
在你的问题中你写道:
return (from p in _context.Employee
join pList in myEmployees on p.EmployeeID equals pList.EntitySourceID
select new Person
{
PersonID = pList.PersonID,
FirstName = p.FirstName
... etc
只需用以下方法来增强它:
var entityList = _context.Employee.ToArray();
return (from p in entityList // PLEASE NOTE THIS CHANGE ALSO
join pList in myEmployees on p.EmployeeID equals pList.EntitySourceID
select ...
总结一下
您可以:
- 指示数据库完成工作,但在这种情况下,您无法在此过程中向其发送精美的 .NET 实例
- 自己在楼上用 .NET 完成这项工作
一侧或另一侧(数据库或 .NET 进程)需要拥有所有卡(需要具有另一侧的克隆)才能执行JOIN.
所以这只是一个妥协的游戏。
剩下的情况怎么样
If both 桌子 and THE LIST太大了,那你就完蛋了。
不——我只是开玩笑。
没有人听说有人要求别人创造奇迹,而实际上却无法做到。
If this既然如此,那么你就得把问题简化成大量更小的问题。
我建议转变为桌子很大 + 清单不是那么大问题乘以N。
那么你该怎么做呢?
List<MyEmployee> original = ...
// you take your list
// and you split it in sections of .. say 50 (which in my book is not huge for a database
// although be careful - the pressure on the database will be almost that of 50 selects running in parallel for each select)
// how do you split it?
// you could try this
public static IEnumerable<List<MyEmployee>> Split(List<MyEmployee> source, int sectionLength) {
List<MyEmployee> buffer = new List<MyEmployee>();
foreach (var employee in source) {
buffer.Add(employee);
if (buffer.Count == sectionLength) {
yield return buffer.ToList(); // MAKE SURE YOU .ToList() the buffer in order to clone it
buffer.Clear(); // or otherwise all resulting sections will actually point to the same instance which gets cleared and refilled over and over again
}
}
if (buffer.Count > 0) // and if you have a remainder you need that too
yield return buffer; // except for the last time when you don't really need to clone it
}
List<List<MyEmployee>> sections = Split(original, 50).ToList();
// and now you can use the sections
// as if you're in CASE 2 (the list is not huge but the table is)
// inside a foreach loop
List<Person> results = new List<Person>(); // prepare to accumulate results
foreach (var section in sections) {
int[] ids = (from x in section select x.EntityID).ToArray();
var query = from employee in _context.Employee
where ids.Contains(employee.EmployeeId)
... etc;
var currentBatch = query.ToArray();
results.AddRange(currentBatch);
}
现在你可以说,这只是欺骗数据库的一种方式,让它相信它几乎没有什么工作可做,而实际上我们仍然在向它注入大量工作,并且maybe让其他并发客户端的生活变得困难。
嗯-是的,但至少你可以放慢速度。
你可以Thread.Sleep
各部分之间...你可以使用iterators
(查找它们),实际上并不是用需要很长时间才能处理的记录淹没 RAM,而是“流式传输”。
你对局势有更多的控制权。
祝你好运!