冻结 linq IQueryable(就像 ToList().AsQueryable() 会做的那样)
Freeze a linq IQueryable (as a ToList().AsQueryable() would do)
有没有办法冻结 IQueryable
以便在访问数据库时不会向查询添加额外的连接?例如,我可以执行 .ToList()
来冻结查询,但这会影响性能,因为我所做的任何过滤都是在中间层进行的,而我没有从数据库服务器上的预过滤中获得任何性能提升?
为清楚起见编辑:
我有一个 OData 服务,其中 returns 一个 IQueryable
客户端可以根据需要 filter/sort/project。我只是想阻止他们提取更多数据。我可以通过 ToList().AsQueryable()
来做到这一点,但这失去了 lazyLoading 的优势,并且失去了允许客户端过滤请求的全部目的。
我查看的一个选项是设置:EnableQueryAttribute.AllowedQueryOptions
以排除 Expand
,但是即使我的初始查询已展开,客户端仍然无法选择这些部分。
所以我(认为)尝试了相同的方法,我发现的唯一解决方案是在 SQL 服务器管理器中使用 TVF。我假设 EF 6.1.1 和 Web API。以下是需要采取的一些步骤:
(1)创建一个Procedure(可以加参数):
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 package. It enables Stored Procedures that are queryable. 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-过程的结果。
context.GetArticleApiKey("MyApiKey").AsQueryable();
希望我答对了你的要求。
我假设您实际上有一个 IQueryable<T>
而不是 IQueryable
。
如果您不希望您的客户端访问所有 IQueryable<T>
方法,请不要 return IQueryable<T>
。因为你希望他们只能 filter/sort/project,创建一个对象 包含一个 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>
方法的任何更改都将包含在此 class 中,而不是遍布整个地方。
在我看来,最简单的方法就是将 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
你可以看到它是惰性的,它只在调用时运行。
有没有办法冻结 IQueryable
以便在访问数据库时不会向查询添加额外的连接?例如,我可以执行 .ToList()
来冻结查询,但这会影响性能,因为我所做的任何过滤都是在中间层进行的,而我没有从数据库服务器上的预过滤中获得任何性能提升?
为清楚起见编辑:
我有一个 OData 服务,其中 returns 一个 IQueryable
客户端可以根据需要 filter/sort/project。我只是想阻止他们提取更多数据。我可以通过 ToList().AsQueryable()
来做到这一点,但这失去了 lazyLoading 的优势,并且失去了允许客户端过滤请求的全部目的。
我查看的一个选项是设置:EnableQueryAttribute.AllowedQueryOptions
以排除 Expand
,但是即使我的初始查询已展开,客户端仍然无法选择这些部分。
所以我(认为)尝试了相同的方法,我发现的唯一解决方案是在 SQL 服务器管理器中使用 TVF。我假设 EF 6.1.1 和 Web API。以下是需要采取的一些步骤:
(1)创建一个Procedure(可以加参数):
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 package. It enables Stored Procedures that are queryable. 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-过程的结果。
context.GetArticleApiKey("MyApiKey").AsQueryable();
希望我答对了你的要求。
我假设您实际上有一个 IQueryable<T>
而不是 IQueryable
。
如果您不希望您的客户端访问所有 IQueryable<T>
方法,请不要 return IQueryable<T>
。因为你希望他们只能 filter/sort/project,创建一个对象 包含一个 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>
方法的任何更改都将包含在此 class 中,而不是遍布整个地方。
在我看来,最简单的方法就是将 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
你可以看到它是惰性的,它只在调用时运行。