使用Skip / Take在编译时不知道的类型

时间:2015-07-31 00:43:51

标签: c# entity-framework

我想编写一个函数,逐页检索数据库表中的数据。这里的目标是节省内存。这是验证程序的一部分,我们将偶尔在我们的数据库上运行,以确保我们拥有一致的数据。表可能非常大,所以我不想将整个表加载到内存中进行验证。

考虑到这一点,我写了这个函数:

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内的属性在函数内部未知。

2 个答案:

答案 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的多个实例。同样动态构建这样的查询比手动排序要慢。

所以我建议使用大卫的解决方案而不是我的(坦白说这很简单,应该是显而易见的)但是我仍然想在这里记录它,以防备用在不同场景中有用。