有没有办法冻结IQueryable
,以便在点击数据库时不会向查询添加额外的连接?例如,我可以执行.ToList()
来冻结查询,但这会产生性能影响,因为我所做的任何过滤都在中间层上,而我在数据库服务器上进行预过滤没有任何性能提升?
为了清晰起见进行编辑:
我有一个OData服务,它返回一个IQueryable
,客户端可以根据需要过滤/排序/项目。我只想阻止他们提取更多数据。我可以通过执行ToList().AsQueryable()
来做到这一点,但是这会失去lazyLoading的优势,并且使用它,允许客户端过滤请求的整个目的。
我看到的一个选项是设置:EnableQueryAttribute.AllowedQueryOptions
以排除Expand
,但即使我的初始查询已经展开,客户仍然无法选择这些部分。
答案 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
你可以看到它是懒惰的,它只在被调用时运行。