【问题标题】:Using Skip/Take on a type not know at compile-time对编译时不知道的类型使用 Skip/Take
【发布时间】:2015-07-31 00:43:50
【问题描述】:

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

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

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 行抛出以下异常:

“跳过”方法仅支持 LINQ to Entities 中的排序输入。必须在方法“Skip”之前调用方法“OrderBy”。

还有其他方法可以实现我想要的吗?

更新

链接为重复的问题的答案建议按列排序。 .OrderBy 在这里不起作用,因为 T 上的属性在函数内部是未知的。

【问题讨论】:

标签: c# entity-framework


【解决方案1】:

您可以将已排序的查询传入您的方法并将输入类型更改为IOrderedEnumerable&lt;T&gt; 或传入选择器以在您的方法中排序,如下所示:

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;

【讨论】:

  • 我喜欢。两种解决方案都很实用。在我的情况下,在传递给函数之前排序效果最好(duh!)也是最简单的。我在寻找解决方案时遇到了另一种方法,我将很快为后代发布,但我会使用你的。如果没有人提出更好的想法,我会在几天内接受您的回答,我觉得这值得怀疑。谢谢!
【解决方案2】:

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

所以我建议使用 David 的解决方案而不是我的解决方案(坦率地说,这很简单,应该很明显)但我仍然想在这里记录它,以防替代方案在不同的场景中有用。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-05-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-08-08
    • 1970-01-01
    • 2010-11-11
    相关资源
    最近更新 更多