冻结linq IQueryable(作为ToList()。AsQueryable()会这样做)

时间:2015-06-11 01:21:00

标签: c# linq entity-framework odata

有没有办法冻结IQueryable,以便在点击数据库时不会向查询添加额外的连接?例如,我可以执行.ToList()来冻结查询,但这会产生性能影响,因为我所做的任何过滤都在中间层上,而我在数据库服务器上进行预过滤没有任何性能提升?

为了清晰起见进行编辑:

我有一个OData服务,它返回一个IQueryable,客户端可以根据需要过滤/排序/项目。我只想阻止他们提取更多数据。我可以通过执行ToList().AsQueryable()来做到这一点,但是这会失去lazyLoading的优势,并且使用它,允许客户端过滤请求的整个目的。

我看到的一个选项是设置:EnableQueryAttribute.AllowedQueryOptions以排除Expand,但即使我的初始查询已经展开,客户仍然无法选择这些部分。

3 个答案:

答案 0 :(得分:2)

我假设你实际上有一个IQueryable<T>而不是IQueryable

如果您不希望您的客户访问所有IQueryable<T>方法,请不要返回IQueryable<T>。由于您希望它们只能过滤/排序/投影,因此创建一个包含IQueryable<T>的对象,但只显示所需的方法,并使用它:

public interface IDataResult<T>
{
    IDataResult<T> FilterBy(Expression<Func<T, bool>> predicate);

    IDataResult<TResult> ProjectTo<TResult>(Expression<Func<T, TResult>> predicate);

    IDataResult<T> SortBy<TKey>(Expression<Func<T, TKey>> keySelector);

    IDataResult<T> SortByDescending<TKey>(Expression<Func<T, TKey>> keySelector);

    List<T> ToList();

    IEnumerable<T> AsEnumerable();
}

public class DataResult<T> : IDataResult<T>
{
    private IQueryable<T> Query { get; set; }

    public DataResult(IQueryable<T> originalQuery)
    {
        this.Query = originalQuery;
    }

    public IDataResult<T> FilterBy(Expression<Func<T, bool>> predicate)
    {
        return new DataResult<T>(this.Query.Where(predicate));
    }

    public IDataResult<T> SortBy<TKey>(Expression<Func<T, TKey>> keySelector)
    {
        return new DataResult<T>(this.Query.OrderBy(keySelector));
    }

    public IDataResult<T> SortByDescending<TKey>(Expression<Func<T, TKey>> keySelector)
    {
        return new DataResult<T>(this.Query.OrderByDescending(keySelector));
    }

    public IDataResult<TResult> ProjectTo<TResult>(Expression<Func<T, TResult>> predicate)
    {
        return new DataResult<TResult>(this.Query.Select(predicate));
    }

    public List<T> ToList()
    {
        return this.Query.ToList();
    }

    public IEnumerable<T> AsEnumerable()
    {
        return this.Query.AsEnumerable();
    }
} 

通过这种方式,您还可以防止与应用程序相关的EF和DB相关依赖性。 IQueryable<T>方法的任何更改都将包含在此类中,而不是遍布整个地方。

答案 1 :(得分:0)

所以我(想)尝试了相同的,我发现的唯一解决方案是在SQL Server Manager中使用TVF。我假设EF 6.1.1和Web API。以下是一些步骤:

(1)创建一个程序(如果你愿意,你可以添加参数):

CREATE PROCEDURE [dbo].[GetArticleApiKey] @a nvarchar(max) AS
SELECT  a.*
FROM    [dbo].[Blogs] b, [dbo].[Articles] a
WHERE   b.ApiKey = @a AND b.Id = a.Blog_Id

如果您需要在Code-First中使用此功能,则可以在DbContext构造函数中使用带有Database.SetInitializer的Initalizer。

(2)下载this nuget包。它启用可查询的存储过程。 Here是项目基础。

(3)将以下内容添加到DbContext:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
     modelBuilder.Conventions.Add(new FunctionsConvention<YourContext>("dbo"));
}

(4)将存储过程添加到您的上下文中:

public ObjectResult<Article> GetArticleApiKey(string apiKey)
{
 var apikeyParameter = new ObjectParameter(ApiKeyParameter, apiKey); // Make sure to validate this, because of sql injection
 ((IObjectContextAdapter)this).ObjectContext.ExecuteFunction<Article>("GetArticleApiKey", apikeyParameter);
}

(5)您现在可以在控制器中使用此功能并进行预过滤。之后所有的Odata查询仍然可能,但仅限于sql-procedure的结果。

context.GetArticleApiKey("MyApiKey").AsQueryable();

希望我能找到你所要求的。

答案 2 :(得分:0)

在我看来,最简单的方法是将IQueryable<T>变成Func<List<T>>。然后你不会失去懒惰的方面,但你肯定会删除在数据库上进行进一步连接的能力。

这是多么容易:

public static class FreezeEx
{

    public static Func<List<R>> Freeze<T, R>(this IQueryable<T> queryable, Expression<Func<T, R>> projection)
    {
        return () => queryable.Select(projection).ToList();
    }

    public static Func<List<R>> Freeze<T, R>(this IQueryable<T> queryable, Expression<Func<T, bool>> predicate, Expression<Func<T, R>> projection)
    {
        return () => queryable.Where(predicate).Select(projection).ToList();
    }
}

然后你可以这样做:

IQueryable<int> query = ...;

Func<List<int>> frozen = query.Freeze(t => t > 10, t => t);

List<int> results = frozen.Invoke();

以下是一些基本代码,以便对此进行测试:

public IEnumerable<int> Test()
{
    Console.WriteLine(1);
    yield return 1;
    Console.WriteLine(2);
    yield return 2;
}

现在我称之为:

IQueryable<int> query = Test().AsQueryable();

Console.WriteLine("Before Freeze");
Func<List<int>> frozen = query.Freeze(t => t > 10, t => t);
Console.WriteLine("After Freeze");

Console.WriteLine("Before Invokes");
List<int> results1 = frozen.Invoke();
List<int> results2 = frozen.Invoke();
Console.WriteLine("After Invokes");

在控制台上我得到了这个:

Before Freeze
After Freeze
Before Invokes
1
2
1
2
After Invokes

你可以看到它是懒惰的,它只在被调用时运行。