对编译时不知道的类型使用Skip/Take
本文关键字:Skip Take 类型 编译 不知道 | 更新日期: 2023-09-27 18:05:44
我想写一个函数,从数据库表逐页检索数据。这里的目标是节省内存。这是验证程序的一部分,我们将偶尔在我们的数据库上运行,以确保我们有一致的数据。表可能非常大,所以我不想为了进行验证而将整个表加载到内存中。
考虑到这一点,我写了这个函数:static IEnumerable<T> RetreivePages<T>(IQueryable<T> query, int count, int pageSize)
{
int pages = count / pageSize;
if (count % pageSize > 0)
{
pages++;
}
for (int i = 0; i < pages; i++)
{
foreach (T item in query.Skip(i * pageSize).Take(pageSize))
{
yield return item;
}
}
}
这里的想法是,我们一次只检索pageSize
行,所以我们不会用表中的所有行填充内存。
不幸的是,这不起作用。query.Skip
行抛出以下异常:
方法'Skip'仅支持LINQ to Entities中的排序输入。方法' order '必须在方法'Skip'之前调用。
还有其他方法可以完成我想要的吗?
链接为重复的问题的答案建议按列排序。.OrderBy
不会在这里工作,因为T
的属性在函数内部是未知的。
你可以在你的方法中传递一个已经排序的查询,并将输入类型更改为IOrderedEnumerable<T>
,或者在你的方法中传递选择器来排序,就像这样:
static IEnumerable<T> RetreivePages<T, U>(
IQueryable<T> query,
Func<T, U> orderBy, //<--- Additional parameter
int count, int pageSize)
{
//Apply the ordering
var orderedQuery = query.OrderBy(orderBy);
int pages = count / pageSize;
if (count % pageSize > 0)
{
pages++;
}
for (int i = 0; i < pages; i++)
{
//Use the new ordered version
foreach (T item in orderedQuery.Skip(i * pageSize).Take(pageSize))
{
yield return item;
}
}
}
然后这样命名:
var query = ...;
//Assuming your query object have a property called "ID":
var pagedQuery = RetrievePages(query, x => x.ID, 10, 100;
DavidG是对的,你必须以某种方式排序,所以让我们看看可以做些什么。
这个答案提供了一个很好的通用函数,可以按字符串名称排序:
public static class QueryHelper
{
public static IQueryable<T> OrderByField<T>(this IQueryable<T> q, string sortField, bool ascending = true)
{
var param = Expression.Parameter(typeof(T), "p");
var prop = Expression.Property(param, sortField);
var exp = Expression.Lambda(prop, param);
string method = ascending ? "OrderBy" : "OrderByDescending";
Type[] types = { q.ElementType, exp.Body.Type };
var mce = Expression.Call(typeof(Queryable), method, types, q.Expression, exp);
return q.Provider.CreateQuery<T>(mce);
}
}
现在排序在哪个字段上?通常情况下,表上有一个主键。我想你知道。在这种情况下,您可以检索键并对其进行排序。你的代码将变成:
public class DbHelper<T> where T : class
{
private readonly string[] _keyNames;
public DbHelper(DbContext context)
{
ObjectSet<T> objectSet = ((IObjectContextAdapter)context).ObjectContext.CreateObjectSet<T>();
_keyNames = objectSet.EntitySet.ElementType.KeyMembers.Select(k => k.Name).ToArray();
}
public IEnumerable<T> RetreivePages(IQueryable<T> query, int count, int pageSize)
{
int pages = count / pageSize;
if (count % pageSize > 0)
{
pages++;
}
for (int i = 0; i < pages; i++)
{
IQueryable<T> queryToRun = _keyNames.Aggregate(query, (current, keyName) => current.OrderByField(keyName));
foreach (T item in queryToRun.Skip(i * pageSize).Take(pageSize))
{
yield return item;
}
}
}
}
使用这种方法有很多注意事项。获取键的成本相当高,因此您肯定不希望为相同的类型参数值创建多个DbHelper
实例。此外,动态构造这样的查询比手动排序要慢。
因此,我建议使用David的解决方案,而不是我的解决方案(坦率地说,这很简单,应该是显而易见的),但我仍然想在这里记录它,以防替代方案在不同的场景中有用。