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

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


为清晰起见编辑:

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

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

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

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

 public interface IDataResult { IDataResult FilterBy(Expression> predicate); IDataResult ProjectTo(Expression> predicate); IDataResult SortBy(Expression> keySelector); IDataResult SortByDescending(Expression> keySelector); List ToList(); IEnumerable AsEnumerable(); } public class DataResult : IDataResult { private IQueryable Query { get; set; } public DataResult(IQueryable originalQuery) { this.Query = originalQuery; } public IDataResult FilterBy(Expression> predicate) { return new DataResult(this.Query.Where(predicate)); } public IDataResult SortBy(Expression> keySelector) { return new DataResult(this.Query.OrderBy(keySelector)); } public IDataResult SortByDescending(Expression> keySelector) { return new DataResult(this.Query.OrderByDescending(keySelector)); } public IDataResult ProjectTo(Expression> predicate) { return new DataResult(this.Query.Select(predicate)); } public List ToList() { return this.Query.ToList(); } public IEnumerable AsEnumerable() { return this.Query.AsEnumerable(); } } 

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

所以我(想)尝试了相同的,我找到的唯一解决方案是在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)下载这个 nuget包。 它启用可查询的存储过程。 这是项目基础。


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

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

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

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

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

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

希望我找对你想要的。

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

这是多么容易:

 public static class FreezeEx { public static Func> Freeze(this IQueryable queryable, Expression> projection) { return () => queryable.Select(projection).ToList(); } public static Func> Freeze(this IQueryable queryable, Expression> predicate, Expression> projection) { return () => queryable.Where(predicate).Select(projection).ToList(); } } 

然后你可以这样做:

 IQueryable query = ...; Func> frozen = query.Freeze(t => t > 10, t => t); List results = frozen.Invoke(); 

这是一些基本代码,以便可以测试:

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

现在我称之为:

 IQueryable query = Test().AsQueryable(); Console.WriteLine("Before Freeze"); Func> frozen = query.Freeze(t => t > 10, t => t); Console.WriteLine("After Freeze"); Console.WriteLine("Before Invokes"); List results1 = frozen.Invoke(); List results2 = frozen.Invoke(); Console.WriteLine("After Invokes"); 

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

在冻结之前
冻结后
在调用之前
 1
 2
 1
 2
在Invokes之后

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