一.查询的工作原理
Entity Framework Core 使用语言集成查询 (LINQ) 来查询数据库中的数据。 通过 LINQ 可使用 C#(或你选择的其他 .NET 语言)基于派生上下文和实体类编写强类型查询。 LINQ 查询的表示形式会传递给数据库提供程序,进而转换为特定的数据库查询语言(例如,适用于关系数据库的 SQL)。
1.1 查询的生命周期, 下面是每个查询所经历的过程概述:
(1) LINQ 查询由 E F处理,用于生成已准备好的表示形式,由数据库提供程序处理。缓存结果,以便每次执行查询时都不需要执行此处理。
(2) 结果将传递给数据库提供程序
a.数据库提供程序会识别出查询的哪些部分可以在数据库中求值。
b. 查询的这些部分会转换为特定数据库的查询语言(例如,关系数据库的 SQL)
c. 将一个或多个查询发送到数据库并返回结果集(结果是来自数据库的值,而不是实体实例)
(3) 返回结果集处理
a.如果这是跟踪查询,EF会检查数据是否代表一个实体,已存在于上下文实例的更改跟踪器中。
如果是,则会返回现有实体
如果不是,则会创建新实体、设置更改跟踪并返回该新实体
b.如果这是非跟踪查询,EF 会检查数据是否表示此查询结果集中的现有实体
如果是,则会返回现有实体
如果不是,则会创建新实体并返回该新实体
1.2 执行查询时:
调用LINQ运算符时,只会构建查询在内存中的表示形式。 只有在使用结果时,查询才会发送到数据库。触发查询发送到数据库的最常见操作如下:
(1) 在 for 循环中循环访问结果
????????var blogs = from b in BloggingContext.Blogs ????????????????????select {....} ???????????//触发数据库查询 ???????????foreach (var item in blogs) ???????????{ ???????????????int maxID = item.ID; ???????????}
(2) 使用 ToList、ToArray、Single、Count 等操作都会触发数据库查询
???????BloggingContext.Blogs.ToList(); ???????BloggingContext.Blogs.ToArray(); ???????BloggingContext.Blogs.Count(); ???????BloggingContext.Blogs.Single(); ???????BloggingContext.Blogs.First();
(3) 将查询结果数据绑定到 UI
二.LINQ 查询
Entity Framework Core 使用语言集成查询 (LINQ) 来查询数据库中的数据。 通过 LINQ 可使用 C#(或你选择的其他 .NET 语言)基于派生上下文和实体类编写强类型查询。 LINQ 查询的表示形式会传递给数据库提供程序,进而转换为特定的数据库查询语言(例如,适用于关系数据库的 SQL)。
???// (1)加载所有数据 ????var blogs = BloggingContext.Blogs.ToList();
???SELECT [b].[BlogId], [b].[Name], [b].[Title], [b].[Url] FROM [Blogs] AS [b]
???//(2)加载单个实体 ????var blog = BloggingContext.Blogs.Single(b => b.BlogId == 1);
???SELECT TOP(2) [b].[BlogId], [b].[Name], [b].[Title], [b].[Url] ???FROM [Blogs] AS [b] ???WHERE [b].[BlogId] = 1
???//(3)筛选 ???var blogs = BloggingContext.Blogs.Where(b => b.Url.Contains("dotnet")).ToList();
???SELECT [b].[BlogId], [b].[Name], [b].[Title], [b].[Url] FROM [Blogs] AS [b] WHERE CHARINDEX(N‘dotnet‘, [b].[Url]) > 0
???//(4)排序 var blogs = BloggingContext.Blogs.OrderByDescending(b => b.BlogId).Select(b=> new { b.BlogId,b.Name }).ToList();
SELECT [b].[BlogId], [b].[Name] FROM [Blogs] AS [b] ORDER BY [b].[BlogId] DESC
? //(5) group ?找出重复的url,取出最大BlogId ?? var blogs = from b in BloggingContext.Blogs ???????????????????????group b by new { b.Url} into gs ???????????????????????where gs.Count() >1 ????????????????????????select new ???????????????????????{ ???????????????????????????ID= gs.Max(b=>b.BlogId) ???????????????????????}; ??? //top 1 ? int maxID = blogs.First().ID;
SELECT TOP(1) MAX([b].[BlogId]) AS [ID] FROM [Blogs] AS [b] GROUP BY [b].[Url] HAVING COUNT(*) > 1
??? // (6)多表join查询 ???? var query = from b in context.Blogs ???????????????????????join p in context.Posts ?on ?b.BlogId equals p.BlogId ???????????????????????where b.BlogId == 1 ???????????????????????select new { b.Name,p.Title } ; ??? var bloglinq= query.ToList();
SELECT [b].[Name], [p].[Title] FROM [Blogs] AS [b] INNER JOIN [Posts] AS [p] ON [b].[BlogId] = [p].[BlogId] WHERE [b].[BlogId] = 1
有关显示 LINQ 可完成的任务的大量示例,请参阅 101 个 LINQ 示例
三. 客户端求值
EF支持部分查询在客户端上求值,而将其他部分推送到数据库执行。 由数据库提供程序确定查询的哪些部分会在数据库中求值。 下面示例中 客户端通过执行StandardizeUrl方法来返回 URL
,查询的其余部分都是在数据库中执行的。
???var blogs = context.Blogs ???.OrderByDescending(blog => blog.Rating) ???.Select(blog => new ???{ ???????Id = blog.BlogId, ???????Url = StandardizeUrl(blog.Url) ???}) ???.ToList();
???public static string StandardizeUrl(string url) ???{ ???url = url.ToLower(); ???if (!url.StartsWith("http://")) ???{ ???????url = string.Concat("http://", url); ???} ???return url; ???}
3.1 可能的性能问题
虽然客户端求值非常有用,但在某些情况下可能会导致性能不佳。 请考虑以下查询,该where中使用辅助方法。 由于无法在数据库中执行此操作,因此blog的所有数据将被拉入内存中,然后会在客户端上应用筛选器。 根据数据量以及过滤掉多少数据,可能会导致性能下降。
???var blogs = context.Blogs ???.Where(blog => StandardizeUrl(blog.Url).Contains("dotnet")) ???.ToList();
3.2 为客户端评估抛出异常
默认情况下,当执行客户端求值时,EF Core 将记录警告在日志中。可以改为引发异常或不执行任何操作。 设置如下所示
??????services.AddDbContext<BloggingContext> ???????????????(options => ????????????????options.UseSqlServer(connection) ???????????????//改为引发异常 ???????????????.ConfigureWarnings(warnings => warnings.Throw(RelationalEventId.QueryClientEvaluationWarning)) ???????????????);
四. 跟踪与非跟踪查询
跟踪行为可控制 EF是否将有关实体实例的信息保留在其更改跟踪器中。 如果已跟踪某个实体,则该实体中检测到的任何更改都会在 SaveChanges()
期间永久保存到数据库。 EF 还会修正从跟踪查询中获取的实体与先前已加载到 DbContext 实例中的实体两者之间的导航属性。
4.1 跟踪查询
默认情况下,会跟踪返回实体类型的查询。 这表示可以更改这些实体实例,然后通过 SaveChanges()
持久化这些更改。在以下示例中,将检测到对Blog评分所做的更改,并在 SaveChanges()
期间将这些更改持久化到数据库中。
???????var blog = context.Blogs.SingleOrDefault(b => b.BlogId == 1); ???????blog.Rating = 5; ???????context.SaveChanges(); ???????//显示设置与上面一样,开启了跟踪查询 ???????var blog = context.Blogs. AsTracking().SingleOrDefault(b => b.BlogId == 1);
4.2 非跟踪查询
只需要读取数据结果方案时,非跟踪查询十分有用。 可以更快速地执行非跟踪查询,因为无需设置更改跟踪信息。
???//设置当前查询为非跟踪查询 ???var blogs = context.Blogs ???????.AsNoTracking() ???????.ToList();
???//还可以在上下文实例级别, 设置默认为非跟踪查询 ???????using (var context = new BloggingContext()) ???????{ ???????????context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; ???????????var blogs = context.Blogs.ToList(); ???????}
4.3跟踪和投影
即使查询的结果类型不是实体类型,只要结果包含实体类型,则默认情况下也会跟踪这些实体类型。 在以下返回匿名类型的查询中,会跟踪结果集中 Blog
的实例。
???var blog = context.Blogs ???????.Select(b => ???????????new ???????????{ ???????????????Blog = b, ???????????????Posts = b.Posts.Count() ???????????});
如果结果集不包含任何实体类型,则不会执行跟踪。 在以下返回匿名类型(具有实体中的某些值,但没有实际实体类型的实例)的查询中,不会执行跟踪。
?????var blog = context.Blogs ???????.Select(b => ???????????new ???????????{ ???????????????Id = b.BlogId, ???????????????Url = b.Url ???????????});
参考文献
EF查询数据
asp.net core系列 32 EF查询数据 必备知识(1)
原文地址:https://www.cnblogs.com/MrHSR/p/10435921.html