我想编写一个函数,逐页检索数据库表中的数据。这里的目标是节省内存。这是验证程序的一部分,我们将偶尔在我们的数据库上运行,以确保我们拥有一致的数据。表可能非常大,所以我不想将整个表加载到内存中进行验证。
考虑到这一点,我写了这个函数:
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
行抛出以下异常:
方法&#39; Skip&#39;仅支持LINQ to Entities中的排序输入。方法&#39; OrderBy&#39;必须在方法之前调用&#39;跳过&#39;。
还有其他方法可以实现我的目标吗?
更新
作为重复链接的问题的答案建议按列排序。 .OrderBy
在此不起作用,因为T
内的属性在函数内部未知。
答案 0 :(得分:2)
您可以将已经排序的查询传递到您的方法中,并将输入类型更改为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;
答案 1 :(得分:0)
DavidG是对的,你必须以某种方式排序,所以让我们看看可以做些什么。
This answer提供了一个很好的通用函数,可以按字符串名称排序:
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
的多个实例。同样动态构建这样的查询比手动排序要慢。
所以我建议使用大卫的解决方案而不是我的(坦白说这很简单,应该是显而易见的)但是我仍然想在这里记录它,以防备用在不同场景中有用。