首页 / 缓存 / c# – 实体框架查询缓存
c# – 实体框架查询缓存
内容导读
互联网集市收集整理的这篇技术教程文章主要介绍了c# – 实体框架查询缓存,小编现在分享给大家,供广大互联网技能从业者学习和参考。文章包含8509字,纯文字阅读大概需要13分钟。
内容图文
这篇MSDN文章列出了一大堆提高Entity Framework性能的方法:
https://msdn.microsoft.com/en-us/data/hh949853.aspx
其中一个建议(4.3)是将非映射对象的属性转换为局部变量,以便EF可以缓存其内部查询计划.
这主意听起来很不错.因此,我使用一个简单的查询进行测试,该查询将查询中间接属性引用的10,000次迭代的性能与局部变量进行比较.像这样:
[Fact]
public void TestQueryCaching()
{
const int iterations = 1000;
var quote = new Quote();
using (var ctx = new CoreContext())
{
quote.QuoteId = ctx.Quotes.First().Id;
}
double indirect = 0;
double direct = 0;
10.Times(it =>
{
indirect += PerformCoreDbTest(iterations, "IndirectValue", (ctx, i) =>
{
var dbQuote = ctx.Quotes.First(x => x.Id == quote.QuoteId);
}).TotalSeconds;
direct += PerformCoreDbTest(iterations, "DirectValue", (ctx, i) =>
{
var quoteId = quote.QuoteId;
var dbQuote = ctx.Quotes.First(x => x.Id == quoteId);
}).TotalSeconds;
});
_logger.Debug($"Indirect seconds: {indirect:0.00}, direct seconds:{direct:0.00}");
}
protected TimeSpan PerformCoreDbTest(int iterations, string descriptor, Action<ICoreContext, int> testAction)
{
var sw = new Stopwatch();
sw.Start();
for (var i = 0; i < iterations; i++)
{
using (var ctx = new CoreContext())
{
testAction(ctx, i);
}
}
sw.Stop();
_logger.DebugFormat("{0}: Took {1} milliseconds for {2} iterations",
descriptor, sw.Elapsed.TotalMilliseconds, iterations);
return sw.Elapsed;
}
但我没有看到任何真正的性能优势.在两台不同的机器上,这些是5次迭代的结果:
Machine1 - Indirect seconds: 9.06, direct seconds:9.36
Machine1 - Indirect seconds: 9.98, direct seconds:9.84
Machine2 - Indirect seconds: 22.41, direct seconds:20.38
Machine2 - Indirect seconds: 17.27, direct seconds:16.93
Machine2 - Indirect seconds: 16.35, direct seconds:16.32
使用本地变量 – MSDN文章推荐的“直接”方法 – 可能是最快的(4/5次),但不是一致的,而且实际上并不是很多.
我在测试中做错了吗?或者效果真的很轻微,它没有太大的区别?或者MSDN文章基本上是错误的,这种引用对象的方式对查询缓存没有任何影响?
**编辑10/9/16 **
我将查询修改为(a)使其更复杂,并且(b)每次传入不同的quoteId.我怀疑后者很重要,否则查询确实会被缓存 – 因为没有任何参数.请参阅下面@raderick的答案.
这是更复杂的测试:
[Fact]
public void TestQueryCaching()
{
const int iterations = 1000;
List<EFQuote> quotes;
using (var ctx = new CoreContext())
{
quotes = ctx.Quotes.Take(iterations).ToList();
}
double indirect = 0;
double direct = 0;
double iqueryable = 0;
10.Times(it =>
{
indirect += PerformCoreDbTest(iterations, "IndirectValue", (ctx, i) =>
{
var quote = quotes[i];
var dbQuote = ctx.Quotes
.Include(x => x.QuoteGroup.QuoteGroupElements.Select(e => e.DefaultElement.DefaultChoices))
.Include(x => x.QuoteElements.Select(e => e.DefaultElement.DefaultChoices))
.Include(x => x.QuotePackage)
.Include(x => x.QuoteDefinition)
.Include(x => x.QuoteLines)
.First(x => x.Id == quote.Id);
}).TotalSeconds;
direct += PerformCoreDbTest(iterations, "DirectValue", (ctx, i) =>
{
var quoteId = quotes[i].Id;
var dbQuote = ctx.Quotes
.Include(x => x.QuoteGroup.QuoteGroupElements.Select(e => e.DefaultElement.DefaultChoices))
.Include(x => x.QuoteElements.Select(e => e.DefaultElement.DefaultChoices))
.Include(x => x.QuotePackage)
.Include(x => x.QuoteDefinition)
.Include(x => x.QuoteLines)
.First(x => x.Id == quoteId);
}).TotalSeconds;
iqueryable += PerformCoreDbTest(iterations, "IQueryable", (ctx, i) =>
{
var quoteId = quotes[i].Id;
var dbQuote = ctx.Quotes
.Include(x => x.QuoteGroup.QuoteGroupElements.Select(e => e.DefaultElement.DefaultChoices))
.Include(x => x.QuoteElements.Select(e => e.DefaultElement.DefaultChoices))
.Include(x => x.QuotePackage)
.Include(x => x.QuoteDefinition)
.Include(x => x.QuoteLines)
.Where(x => x.Id == quoteId).First();
}).TotalSeconds;
});
_logger.Debug($"Indirect seconds: {indirect:0.00}, direct seconds:{direct:0.00}, iqueryable seconds:{iqueryable:0.00}");
}
结果(超过10,000次迭代)更像上面的MSDN文章描述的内容:
间接秒:141.32,直接秒:91.95,可查询秒:93.96
解决方法:
我不是100%确定本文可以描述Entity Framework版本6的当前行为,但是这个东西应该与Entity Framework中的查询编译相关联到存储过程.
当您第一次使用Entity Framework调用某个查询时,必须将它编译为一个SQL语句 – 一个纯SELECT查询,或一个使用exec和参数的过程,例如:
exec sp_executesql N'SELECT
[Extent1].[Id] AS [Id],
[Extent1].[Name] AS [Name],
[Extent1].[IssuedAt] AS [IssuedAt],
[Extent1].[Status] AS [Status],
[Extent1].[Foo_Id] AS [Foo_Id]
FROM [dbo].[Activities] AS [Extent1]
WHERE (N''Some Name'' = [Extent1].[Name]) AND ([Extent1].[Id] = @p__linq__0)',N'@p__linq__0 int',@p__linq__0=0
@ p__linq__0是查询中的一个参数,因此每次在查询代码中更改Id时,实体框架都会从查询缓存中选择这个完全相同的语句并调用它而不再尝试为其编译SQL.另一方面N”Some Name”= [Extent1].[Name] part等于代码x.Name ==“Some Name”,我在这里使用了一个常量,所以它不是转换为查询参数,而是查询语句的简单部分.
每次尝试进行查询时,Entity Framework都会检查包含已编译SQL语句的缓存,以查看是否存在可以与参数一起重用的已编译语句.如果找不到该语句,Entity Framework必须再次将C#查询编译为Sql.因此,如果您的查询很小并且编译速度很快,您将不会注意到任何内容,但如果您有大量包含,条件,转换和内置函数使用的“难以编译”查询,那么您可以点击当您的查询没有点击Entity Framework编译的查询缓存时会受到重罚.
你可以看到当前的分页工作有一些相似之处,而不使用Skip和Take的重载,而不是在更改页面时遇到编译的查询缓存:Force Entity Framework to use SQL parameterization for better SQL proc cache reuse
在代码中使用常量时可以面对这种效果,其效果非常明显.让我们比较这些代码片段和EntityFramework生成的SQL(为了简洁我省略了类定义,应该非常明显):
查询1
示例代码:
var result = context.Activities
.Where(x => x.IssuedAt >= DateTime.UtcNow && x.Id == iteration)
.ToList();
制作的Sql:
exec sp_executesql N'SELECT
[Extent1].[Id] AS [Id],
[Extent1].[Name] AS [Name],
[Extent1].[IssuedAt] AS [IssuedAt],
[Extent1].[Status] AS [Status],
[Extent1].[Foo_Id] AS [Foo_Id]
FROM [dbo].[Activities] AS [Extent1]
WHERE ([Extent1].[IssuedAt] >= (SysUtcDateTime())) AND ([Extent1].[Id] = @p__linq__0)',N'@p__linq__0 int',@p__linq__0=0
您可以看到,在这种情况下条件x.IssuedAt> = DateTime.UtcNow转换为语句[Extent1].[IssuedAt]> =(SysUtcDateTime()).
查询2
示例代码:
var now = DateTime.UtcNow;
var result = context.Activities
.Where(x => x.IssuedAt >= now && x.Id == iteration)
.ToList();
制作的Sql:
exec sp_executesql N'SELECT
[Extent1].[Id] AS [Id],
[Extent1].[Name] AS [Name],
[Extent1].[IssuedAt] AS [IssuedAt],
[Extent1].[Status] AS [Status],
[Extent1].[Foo_Id] AS [Foo_Id]
FROM [dbo].[Activities] AS [Extent1]
WHERE ([Extent1].[IssuedAt] >= @p__linq__0) AND ([Extent1].[Id] = @p__linq__1)',N'@p__linq__0 datetime2(7),@p__linq__1 int',@p__linq__0='2016-10-09 15:27:37.3798971',@p__linq__1=0
在这种情况下,您可以看到条件x.IssuedAt> = now已转换为[Extent1].[IssuedAt]> = @ p__linq__0 – 参数化语句,并且DateTime值作为过程参数传递.
你可以清楚地看到这里与查询1的区别 – 条件是没有参数的查询代码的一部分,它使用内置函数来获取日期时间.
这两个查询可能会给你一个提示,实体框架中常量的使用会产生不同的查询,只使用字段,属性,参数等.这是一个合成的例子,让我们检查更接近真实查询的东西.
查询3
在这里,我使用enum ActivityStatus并想要查询具有特定Id的Activity,并且我希望能够仅获取状态为“Active”的活动(无论这意味着什么).
示例代码:
var result = context.Activities
.Where(x => x.Status == ActivityStatus.Active
&& x.Id == id)
.ToList();
制作的Sql:
exec sp_executesql N'SELECT
[Extent1].[Id] AS [Id],
[Extent1].[Name] AS [Name],
[Extent1].[IssuedAt] AS [IssuedAt],
[Extent1].[Status] AS [Status],
[Extent1].[Foo_Id] AS [Foo_Id]
FROM [dbo].[Activities] AS [Extent1]
WHERE (0 = [Extent1].[Status]) AND ([Extent1].[Id] = @p__linq__0)',N'@p__linq__0 int',@p__linq__0=0
你可以看到,在条件x.Status == ActivityStatus.Active中使用常量会产生SQL 0 = [Extent1].[Status],这是正确的.这里的状态不是参数化的,所以如果你使用条件x.Status = ActivityStatus.Pending在其他地方调用相同的查询,那将产生另一个查询,所以第一次调用它将导致实体框架查询编译.您可以使用查询4来避免它.
查询4
示例代码:
var status = ActivityStatus.Active;
var result = context.Activities
.Where(x => x.Status == status
&& x.Id == iteration)
.ToList();
制作的Sql:
exec sp_executesql N'SELECT
[Extent1].[Id] AS [Id],
[Extent1].[Name] AS [Name],
[Extent1].[IssuedAt] AS [IssuedAt],
[Extent1].[Status] AS [Status],
[Extent1].[Foo_Id] AS [Foo_Id]
FROM [dbo].[Activities] AS [Extent1]
WHERE ([Extent1].[Status] = @p__linq__0) AND ([Extent1].[Id] = @p__linq__1)',N'@p__linq__0 int,@p__linq__1 int',@p__linq__0=0,@p__linq__1=0
如您所见,此查询语句是完全参数化的,因此将状态更改为Pending,Active,Inactive等仍将使用编译查询缓存中的相同查询.
根据您的编码风格,您可能会不时遇到此问题,当只有2个具有不同常量值的查询时,每个查询都会编译一个查询.我可以为你提供尝试使用布尔值作为常量的相同查询,它应该产生相同的结果 – 条件未参数化.
内容总结
以上是互联网集市为您收集整理的c# – 实体框架查询缓存全部内容,希望文章能够帮你解决c# – 实体框架查询缓存所遇到的程序开发问题。 如果觉得互联网集市技术教程内容还不错,欢迎将互联网集市网站推荐给程序员好友。
内容备注
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 gblab@vip.qq.com 举报,一经查实,本站将立刻删除。
内容手机端
扫描二维码推送至手机访问。