冻结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之后
你可以看到它是懒惰的,它只在调用时运行。
- 是什么导致Azure事件中心ReceiverDisconnectedException / LeaseLostException?
- C# – 如何validation日期时间(制作“20120713”或“120713”至“13.07.2012”)
- PresentationFramework.dll中出现未处理的“System.Windows.Markup.XamlParseException”类型exception
- 如何向ThenInclude添加where子句
- 如何使用Json.Net将JSON数组反序列化为对象?
- Monocross和Monotouch(xamarin)之间的区别?
- 在MVC4中访问原始http请求
- c#根据下载请求动态重命名文件
- 仅使用Bouncy Castle阅读PEM RSA公钥