首页 > 其他分享 >EF Core 数据查询

EF Core 数据查询

时间:2023-07-15 20:57:15浏览次数:30  
标签:Core EF 查询 context SQL var

查询数据

Entity Framework Core 使用语言集成查询 (LINQ) 来查询数据库中的数据。EF Core 将 LINQ 查询的表示形式传递给数据库提供程序。 反过来,数据库提供程序将其转换为数据库特定的查询语言(SQL语句)

跟踪查询与非跟踪查询

跟踪行为控制 Entity Framework Core 是否在其更改跟踪器中保留有关实体实例的信息。如果跟踪实体,在 期间 SaveChanges,实体中检测到的任何更改将保存到数据库中。 EF Core 还修复了跟踪查询结果中的实体与更改跟踪器中的实体之间的导航属性。

跟踪查询

默认情况下,跟踪返回实体类型的查询。 跟踪查询意味着对实体实例所做的任何更改都由 SaveChanges持久化。

在跟踪查询中返回结果时,EF Core 会检查实体是否已在上下文中。 如果 EF Core 找到现有实体,则返回相同的实例,这可能使用更少的内存,并且比无跟踪查询更快。 EF Core不会使用数据库值覆盖条目中实体属性的当前值和原始值。 如果在上下文中找不到实体,EF Core 会创建一个新的实体实例并将其附加到上下文。 查询结果不会包含任何已添加到上下文但尚未保存到数据库中的实体。

非跟踪查询

在只读方案中使用结果时,非跟踪查询十分有用。 通常可以更快速地执行非跟踪查询, 因为无需设置更改跟踪信息

如果不需要更新从数据库检索到的实体,则应使用无跟踪查询。 单个查询可以设置为不跟踪。

无跟踪查询还会根据数据库中的内容提供结果,而不考虑任何本地更改或添加的实体。

var blogs = context.Blogs .AsNoTracking() .ToList();

可以在上下文实例级别更改默认跟踪行为:

context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
var blogs = context.Blogs.ToList();

标识解析

跟踪查询:由于跟踪查询使用更改跟踪器,因此 EF Core 在跟踪查询中执行标识解析。 具体化实体 时,EF Core 会从更改跟踪器返回相同的实体实例(如果已被跟踪)。 如果结果多次包含同一实体,则每次出现都会返回同一实例。

非跟踪查询:不要使用更改跟踪器,也不要执行标识解析;即使同一实体多次包含在结果中,也返回实体的新实例;

AsNoTrackingWithIdentityResolution(IQueryable),就像添加 AsNoTracking 可查询运算符一样。当使用标识解析的查询配置为不进行跟踪时,生成查询结果时,后台会使用独立的 更改跟踪器,因此每个实例仅具体化一次。 此更改追踪器不同于上下文中的更改追踪 器,因此上下文不会追踪这些结果

var blogs = context.Blogs .AsNoTrackingWithIdentityResolution() .ToList();

配置默认跟踪行为

在将数据上下文注册进DI容器时,设置上下文所有查询默认不进行跟踪;但仍可添加 AsTracking 来进行特定查询跟踪

UserSqlServer(options).UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking);

跟踪和自定义投影、

即使查询的结果类型不是实体类型,默认情况下 EF Core 也会跟踪结果中包含的实体类型。

如果结果集不包含任何实体类型,则不会执行跟踪。

EF Core 不会跟踪结果中包含的无键实体实例。只要查询结果中包含无键实体类型,整个查询就会进行非跟踪。

加载相关数据

Entity Framework Core 允许你在模型中使用导航属性来加载相关实体。 有三种常见的O/RM 模式可用于加载关联数据:

预先加载表示从数据库中加载关联数据,作为初始查询的一部分。

显式加载表示稍后从数据库中显式加载关联数据。

延迟加载表示在访问导航属性时,从数据库中以透明方式加载关联数据。

预先加载

可以使用 Include 方法来指定要包含在查询结果中的关联数据。 在以下示例中,结果中返回的blogs将使用关联的posts填充其 Posts 属性。

var blogs = context.Blogs .Include(blog => blog.Posts) .ToList();

可以在单个查询中包含多个关系的关联数据。

var blogs = context.Blogs .Include(blog => blog.Posts) .Include(blog => blog.Owner) .ToList();

包含多个层级(ThenInclude):

使用 ThenInclude 方法可以依循关系包含多个层级的关联数据。 以下示例加载了所有博 客、其关联文章及每篇文章的作者。

var blogs = context.Blogs .Include(blog => blog.Posts) .ThenInclude(post => post.Author) .ToList();

可以将对来自多个级别和多个根的关联数据的所有调用合并到同一查询中。

var blogs = context.Blogs .Include(blog => blog.Posts) 
       .ThenInclude(post => post.Author)
       .ThenInclude(author => author.Photo)
       .Include(blog => blog.Owner)
       .ThenInclude(owner => owner.Photo)
       .ToList();

多个关联实体都包含进来,例如:Blog -> Posts -> Author 和 Blog ->Posts -> Tags。这并不意味着会获得冗余联接查询,在大多数情况下,EF 会在生成 SQL 时合并相应的联接查询。

经过筛选的包含

在应用包含功能来加载相关数据时,可对已包含的集合导航应用某些可枚举的操作,这样 就可对结果进行筛选和排序。

支持的操作包括:Where、OrderBy、OrderByDescending、ThenBy、ThenByDescending、 Skip 和 Take。

.Include(
 blog => blog.Posts
 .Where(post => post.BlogId == 1)
 .OrderByDescending(post => post.Title)
 .Take(5))
.ToList();

自动包含导航的模型配置

可使用 AutoInclude 方法配置每次从数据库加载实体时要包含的模型中的导航。 这与在 结果中返回实体类型的每个查询中使用导航指定 Include 具有相同的效果。

modelBuilder.Entity().Navigation(e => e.ColorScheme).AutoInclude();

显示加载

可以通过 DbContext.Entry(...) API 显式加载导航属性。

var blog = context.Blogs .Single(b => b.BlogId == 1);
context.Entry(blog) .Collection(b => b.Posts) .Load();
context.Entry(blog) .Reference(b => b.Owner) .Load();

可以通过执行返回关联实体的单独查询来显式加载导航属性。 如果已启用更改跟踪, 则在查询具体化实体时,EF Core 将自动设置新加载的实体的导航属性以引用任何已加载 的实体,并设置已加载实体的导航属性以引用新加载的实体。

还可以获得表示导航属性内容的 LINQ 查询。

var goodPosts = context.Entry(blog) .Collection(b => b.Posts) .Query() .Where(p => p.Rating > 3) .ToList();

延迟加载

使用代理的延迟加载

使用延迟加载的最简单方式是通过安装 Microsoft.EntityFrameworkCore.Proxies 包,并 通过调用 UseLazyLoadingProxies 来启用该包

.AddDbContext<BloggingContext>(
 b => b.UseLazyLoadingProxies()
         .UseSqlServer(myConnectionString));

添加后,EF Core 接着会为可重写的任何导航属性(即,必须是 virtual 且在可被继承的类上)启用延迟加载。

不使用代理的延迟加载

不使用代理进行延迟加载的工作方式是将 ILazyLoader 注入到实体中,如实体类型构造 函数中所述

public class Blog
{
 private ICollection<Post> _posts;
 public Blog()
 {
 }
private Blog(ILazyLoader lazyLoader) {   LazyLoader = lazyLoader; }
private ILazyLoader LazyLoader { get; set; } public int Id { get; set; } public string Name { get; set; } public ICollection<Post> Posts {   get => LazyLoader.Load(this, ref _posts);    set => _posts = value; } }

此方法不要求实体类型为可继承的类型,也不要求导航属性必须是虚拟的,且允许通过 new 创建的实体实例在附加到上下文后可进行延迟加载。 但它需要对 Microsoft.EntityFrameworkCore.Abstractions 包中定义的 ILazyLoader 服务的引用。

可以将 ILazyLoader.Load 方法以委托的形式注入,这样就可以完全避免依赖于实体类型 的任何 EF Core 包。

延迟加载委托的构造函数参数必须名为“lazyLoader”。

关联数据和序列化

由于 EF Core 会自动修正导航属性,因此在对象图中可能会产生循环引用。

Json.NET 在发现循环引用的情况下,会引 发以下异常。

Newtonsoft.Json.JsonSerializationException:为“MyApplication.Models.Blog”类型的 “Blog”属性检测到自引用循环。

如果找到循环,System.Text.Json 将引发类似的异常。

System.Text.Json.JsonException:检测到可能的对象周期。 这可能是由于循环或对象 深度大于允许的最大深度 32 造成的。 请考虑在 JsonSerializerOptions 上使用 ReferenceHandler.Preserve 来支持周期。

如果在 ASP.NET Core 中使用 Json.NET,则可以将 Json.NET 配置为忽略它在对象图中找 到的周期。

services.AddMvc()
 .AddJsonOptions(
 options => options.SerializerSettings.ReferenceLoopHandling =
Newtonsoft.Json.ReferenceLoopHandling.Ignore

如果使用 System.Text.Json,可以按如下所示对其进行配置。

services.AddControllers()
.AddJsonOptions(options =>
 {
   options.JsonSerializerOptions.ReferenceHandler =
  ReferenceHandler.IgnoreCycles;
 });

单查询与拆分查询

单个查询的性能问题

处理关系数据库时,EF 通过将 JOIN 引入单个查询来加载相关实体。 虽然使用 SQL 时, JOIN 非常标准,但如果使用不当,它们可能会产生严重的性能问题。

笛卡尔爆炸:

由于 和 Contributors 都是 Posts 的Blog集合导航(它们位于同一级 别),关系数据库返回一个交叉积

var blogs = ctx.Blogs .Include(b => b.Posts) .Include(b => b.Contributors) .ToList();

当两个 JONN 不在同一级别时,不会发生笛卡尔爆炸:

var blogs = ctx.Blogs .Include(b => b.Posts) .ThenInclude(p => p.Comments) .ToList();

数据重复:

var blogs = ctx.Blogs .Include(b => b.Posts) .ToList();

此查询返回的每一行都包含 Blogs 和 Posts 表中的属性;这意味着博客包 含的每篇文章的博客属性都是重复的。

该列将重复并多次发送回客户端。 这会显著增加网络流量,并会对应用程序的性能产生不利影响。

通过使用投影显式选择所需的列,可以省略大列并提高性能; 由于此将博客投影为匿名类型,因此 EF 不会跟踪该博客,并且无法像往常一样保存对博客的更改

var blogs = ctx.Blogs .Select(b => new {b.Id, b.Name, b.Posts }) .ToList();

拆分查询

若要解决单个查询性能问题,EF 允许指定应将给定的 LINQ 查询拆分为多个SQL 查询。 与 JOIN 不同,拆分查询为包含的每个集合导航生成额外的 SQL 查询:

var blogs = context.Blogs .Include(blog => blog.Posts) .AsSplitQuery() .ToList();

会生成一下SQL语句:

SELECT [b].[BlogId], [b].[OwnerId], [b].[Rating], [b].[Url]
FROM [Blogs] AS [b]
ORDER BY [b].[BlogId]
SELECT [p].[PostId], [p].[AuthorId], [p].[BlogId], [p].[Content], [p].
[Rating], [p].[Title], [b].[BlogId]
FROM [Blogs] AS [b]
INNER JOIN [Posts] AS [p] ON [b].[BlogId] = [p].[BlogId]
ORDER BY [b].[BlogId

将拆分查询与 Skip/Take 配合使用时,请特别注意使查询排序完全唯一;

全局启用拆分查询

如果没有任何配置,默认情况下,EF Core 使用单个查询模式。可以将拆分查询配置为应用程序上下文的默认查询:

optionsBuilder
 .UseSqlServer(
 @"Server=connentString,o =>o.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery));

将拆分查询配置为默认查询后,仍然可以将特定查询配置为以单个查询的形式执行

var blogs = context.Blogs .Include(blog => blog.Posts) .AsSingleQuery() .ToList();

拆分查询的缺陷

  1. 虽然大多数数据库对单个查询保证数据一致性,但对多个查询不存在这样的保证。
  2. 拆分查询,每个查询都意味着对数据库进行一次额外的网络往返。 多次网络往返会降低性能,尤其是在数据库延迟很高的情况下(例如云服务)。
  3. 虽然有些数据库(带有 MARS 的 SQL Server、Sqlite)允许同时使用多个查询的结 果,但大多数数据库在任何给定时间点只允许一个查询处于活动状态。 因此,在执 行以后的查询之前,必须先在应用程序的内存中缓冲先前查询的所有结果,这将增加内存需求。

复杂查询运算符

语言集成查询 (LINQ) 包含许多用于组合多个数据源或执行复杂处理的复杂运算符。并非 所有 LINQ 运算符都会在服务器端进行适当转换。

联接【JOIN】

LINQ Join 运算符在关系数据库中自然而然地转换为 INNER JOIN。虽然 LINQ Join 具有外部和内部键选择器,但数据库只需要一个联接条件。因此 EF Core 通过比较外部键选 择器和内部键选择器是否相等,来生成联接条件。

var query = from photo in context.Set<PersonPhoto>()
 join person in context.Set<Person>()
 on photo.PersonPhotoId equals person.PhotoId
 select new { person, photo };

JOIN 联接中ON 多个条件:

var query = from photo in context.Set<PersonPhoto>()
 join person in context.Set<Person>()
 on new { Id = (int?)photo.PersonPhotoId, photo.Caption }
 equals new { Id = person.PhotoId, Caption = "SN" }
 select new { person, photo };

GroupJoin

LINQ GroupJoin 运算符,可以采用与 Join 类似的方式连接两个数据源,但它会创建 一组内部值,用于匹配外部元素。

由于数据库(特别是关系数据库)无法表示一组客户 端对象,因此在许多情况下,GroupJoin 不会转换为服务器。

var query = from b in context.Set<Blog>()
 join p in context.Set<Post>()
 on b.BlogId equals p.BlogId into grouping
 select new { b, Posts = grouping.Where(p =>
p.Content.Contains("EF")).ToList() };

SelectMany

LINQ SelectMany 运算符,可为每个外部元素枚举集合选择器,并从每个数据源生成值的元组。在某种程度上,它是没有任何条件的联接,因此每个外部元素都与来自集 合源的元素连接。SelectMany 可在服务器端转换 为各种不同的查询。

集合选择器不引用外部:

如果集合选择器不引用外部源中的任何内容,则结果是这两个数据源的笛卡尔乘积。在关系数据库中转换为 CROSS JOIN

var query = from b in context.Set<Blog>()
 from p in context.Set<Post>()
 select new { b, p };

转换SQL:

SELECT [b].[BlogId], [b].[OwnerId], [b].[Rating], [b].[Url], [p].[PostId],
[p].[AuthorId], [p].[BlogId], [p].[Content], [p].[Rating], [p].[Title]
FROM [Blogs] AS [b]
CROSS JOIN [Posts] AS [p]

集合选择器引用 where 子句中的外部:

如果集合选择器具有引用外部元素的 where 子句,则 EF Core 会将其转换为数据库联接,并将谓词用作联接条件

如果在集合选择器上应用 DefaultIfEmpty,则外部元素将与内部元素的默认值连接。

由于这种区别,应用DefaultIfEmpty时,如果缺少DefaultIfEmpty 和 LEFT JOIN,此类查询会转换为 INNER JOIN。

var query = from b in context.Set<Blog>()
 from p in context.Set<Post>().Where(p => b.BlogId == p.BlogId)
 select new { b, p };

var query2 = from b in context.Set<Blog>()
 from p in context.Set<Post>().Where(p => b.BlogId ==p.BlogId).DefaultIfEmpty()
 select new { b, p };

分别转换SQL:

SELECT [b].[BlogId], [b].[OwnerId], [b].[Rating], [b].[Url], [p].[PostId],[p].[AuthorId], [p].[BlogId], [p].[Content], [p].[Rating], [p].[Title]
FROM [Blogs] AS [b]
INNER JOIN [Posts] AS [p] ON [b].[BlogId] = [p].[BlogId]

SELECT [b].[BlogId], [b].[OwnerId], [b].[Rating], [b].[Url], [p].[PostId],
[p].[AuthorId], [p].[BlogId], [p].[Content], [p].[Rating], [p].[Title]
FROM [Blogs] AS [b]
LEFT JOIN [Posts] AS [p] ON [b].[BlogId] = [p].[BlogId]

集合选择器引用非 where 情况下的外部:

如果集合选择器引用的外部元素不在 where 子句中(如上所述),则它不会转换为数据库联接。

在许多关系数据库 中转换为 APPLY 操作。 如果外部元素的集合为空,则不会为该外部元素生成任何结果。 但如果在集合选择器上应用 DefaultIfEmpty,则外部元素将与内部元素的默认值连接。 由于这种区别,应用 DefaultIfEmpty 时,如果缺少 DefaultIfEmpty 和 OUTER APPLY,此类查询会转换为 CROSS APPLY。

var query = from b in context.Set<Blog>()
 from p in context.Set<Post>().Select(p => b.Url + "=>" +
p.Title)
 select new { b, p };

var query2 = from b in context.Set<Blog>()
 from p in context.Set<Post>().Select(p => b.Url + "=>" +
p.Title).DefaultIfEmpty()
 select new { b, p };

转换成SQL:

SELECT [b].[BlogId], [b].[OwnerId], [b].[Rating], [b].[Url], ([b].[Url] +N'=>') + [p].[Title] AS [p]
FROM [Blogs] AS [b]
CROSS APPLY [Posts] AS [p]

SELECT [b].[BlogId], [b].[OwnerId], [b].[Rating], [b].[Url], ([b].[Url] +N'=>') + [p].[Title] AS [p]
FROM [Blogs] AS [b]
OUTER APPLY [Posts] AS [p]

GroupBy

GroupBy 运算符创建 IGrouping<tkey, telement=""> 类型的结果,其中 TKey 和 TElement 可以是任意类型。,该运算符可在关系数据库中转换为 SQL GROUP BY。

SQL GROUP BY 也会受到限制。 它要求只按标量值进行分组。 投影只能包含分组键列或对列应用的任何聚合。

var query = from p in context.Set<Post>()
 group p by p.AuthorId
 into g
 select new { g.Key, Count = g.Count() };

转换SQL:

SELECT [p].[AuthorId] AS [Key], COUNT(*) AS [Count]
FROM [Posts] AS [p]
GROUP BY [p].[AuthorId]

分组的聚合运算符出现在 Where 或 OrderBy(或 其他排序方式)LINQ 运算符中。 它在SQL中将HAVING子句用于where 子句。

var query = from p in context.Set<Post>()
 group p by p.AuthorId
 into g
 where g.Count() > 0
 orderby g.Key
 select new { g.Key, Count = g.Count() };

转换SQL:

SELECT [p].[AuthorId] AS [Key], COUNT(*) AS [Count]
FROM [Posts] AS [p]
GROUP BY [p].[AuthorId]
HAVING COUNT(*) > 0
ORDER BY [p].[AuthorId

EF Core 支持的聚合函数:

Average(x => x.Property)           AVG(Property)

Count()                                      COUNT(*)

LongCount()                              COUNT(*)

Max(x => x.Property)                 MAX(Property)

Min(x => x.Property)                 MIN(Property)

Sum(x => x.Property)                SUM(Property)

Left Join

虽然 Left Join 不是 LINQ 运算符,但关系数据库具有常用于查询的 Left Join 的概念。LINQ 查询中的特定模式提供与服务器上的 LEFT JOIN 相同的结果。 EF Core 标识此类模 式,并在服务器端生成等效的 LEFT JOIN。

注意:可通过GroupJoin 、SelectMany 与DefaultIfEnpty 来实现Left Join

var query = from b in context.Set<Blog>()
 join p in context.Set<Post>()
 on b.BlogId equals p.BlogId into grouping
 from p in grouping.DefaultIfEmpty()
 select new { b, p }

转换SQL:

SELECT [b].[BlogId], [b].[OwnerId], [b].[Rating], [b].[Url], [p].[PostId],[p].[AuthorId], [p].[BlogId], [p].[Content], [p].[Rating], [p].[Title]
FROM [Blogs] AS [b]
LEFT JOIN [Posts] AS [p] ON [b].[BlogId] = [p].[BlogId]

EF Core 要求在紧随运算符的步骤中将 GroupJoin 运算符的分组结果平展。即使使用了 GroupJoin-DefaultIfEmpty-SelectMany,但采用其他的模式,也不能将其标识为 Left Join。

分页查询

无论使用哪种分页方法,始终确保排序是完全唯一的。

偏移分页

使用数据库实现分页的一种常见方法是在 SQL) 中使用 Skip 和 Take ( OFFSET LIMIT 。

var position = 20;
var nextPage = context.Posts
 .OrderBy(b => b.PostId)
 .Skip(position)
 .Take(10)
 .ToList();

偏移分页缺点

1. 数据库仍必须处理前 20 个条目,即使它们未返回到应用程序:这会创建可能显著计算负载,该负载会随着跳过的行数而增加。

2. 如果同时发生任何更新,则分页最终可能会跳过某些条目或显示两次。

Ketset分页

建议使用基于偏移的分页的替代方法是使用 WHERE 子句跳过行,而不是偏移量。

var lastId = 55;
var nextPage = context.Posts
 .OrderBy(b => b.PostId)
 .Where(b => b.PostId > lastId)
 .Take(10)
 .ToList();

假设定义了索引 PostId,则此查询非常高效,并且对 ID 值较低时发生的任何并发更改也 不敏感。

Keyset 分页适用于用户向前和向后导航但不支持随机访问的分页接口,用户可以跳转到任何特定页面。随机访问分页需要使用偏移量分页

多个分页键:

var lastDate = new DateTime(2020, 1, 1);
var lastId = 55;
var nextPage = context.Posts
 .OrderBy(b => b.Date)
 .ThenBy(b => b.PostId)
 .Where(b => b.Date > lastDate || (b.Date == lastDate && b.PostId > lastId))
 .Take(10)
 .ToList();

注意:适当的索引对于良好的性能至关重要:请确保具有与分页排序对应的索引。 如果按多个列排序,则可以定义多个列的索引;这称为复合索引。

SQL查询

通过 Entity Framework Core 可以在使用关系数据库时下降到 SQL 查询。如果所需查询 无法使用 LINQ 表示,或者 LINQ 查询导致 EF 生成效率低下的 SQL,则可使用 SQL 查 询。

基本SQL查询

可使用 FromSql 基于 SQL 查询开始 LINQ 查询:

var blogs = context.Blogs .FromSql($"SELECT * FROM dbo.Blogs") .ToList();

SQL 查询可用于执行返回实体数据的存储过程:

var blogs = context.Blogs .FromSql($"EXECUTE dbo.GetMostPopularBlogs") .ToList();

注意:FromSql 只能直接在 DbSet 上使用。 不能在任意 LINQ 查询的基础上组合使用它

传递参数:

FromSql 和 FromSqlInterpolated 方法可以防止 SQL 注入,始终将参数数据作为单独的 SQL 参数进行集成。

将单个参数 传递到存储过程:

var user = "johndoe";
var blogs = context.Blogs
 .FromSql($"EXECUTE dbo.GetMostPopularBlogsForUser {user}")
 .ToList();

虽然此语法可能看上去像常规 C# 字符串内插,但提供的值包装在 DbParameter 中,且生 成的参数名称插入到指定了 {0} 占位符的位置。 这使得 FromSql 可以防范 SQL 注入攻 击,可以将值高效且正确地发送到数据库。

执行存储过程时,在 SQL 查询字符串中使用命名参数很有用,尤其是在存储过程具有可 选参数的情况下:

var user = new SqlParameter("user", "johndoe");
var blogs = context.Blogs
 .FromSql($"EXECUTE dbo.GetMostPopularBlogsForUser @filterByUser={user}")
 .ToList();

注意:传递的参数必须与存储过程定义完全匹配。

动态 SQL 和参数:

应尽可能使用 FromSql 及其参数化。 但在某些情况下,需要将 SQL 动态拼凑在一起,并且无法使用数据库参数。

var blogs = context.Blogs
 .FromSql($"SELECT * FROM [Blogs] WHERE {propertyName} =
{propertyValue}")
 .ToList();

此代码无效,因为数据库不允许将列名(或架构的任何其他部分)参数化。

如果决定要动态构造 SQL,则必须使用 FromSqlRaw,这样就可以直接将变量数据内插到 SQL 字符串中,而不使用数据库参数:

var columnName = "Url";
var columnValue = new SqlParameter("columnValue", "http://SomeURL");
var blogs = context.Blogs .FromSqlRaw($"SELECT * FROM [Blogs] WHERE {columnName} = @columnValue", columnValue) .ToList();

使用 LINQ 编写:

可以使用 LINQ 运算符在初始 SQL 查询的基础上进行组合;EF Core 会将 SQL 视为子查询,在数据库中以它为基础进行组合。

包含关联数据:

Include 运算符可用于加载相关数据,就像对其他 LINQ 查询那样。

SQL Server 不允许对存储过程调用进行组合,因此任何尝试向此类调用应用其他查询运 算符的操作都将导致无效的 SQL。 请在 FromSql 或 FromSqlRaw 之后立即使用 AsEnumerable 或 AsAsyncEnumerable,确保 EF Core 不会尝试对存储过程进行组合。

更改跟踪:

使用 FromSql 或 FromSqlRaw 的查询遵循与 EF Core 中所有其他 LINQ 查询完全相同的更 改跟踪规则。

查询标量(非实体)类型:

虽然可以使用 FromSql 来查询模型中定义的实体,但如果使用 SqlQuery,你就可以通过 SQL 轻松查询非实体标量类型,无需下降到较低级别的数据访问 API。

FromSql 可与数据库提供程序支持的任何标量类型配合使用。

SqlQueryRaw 允许动态构造 SQL 查询,就像 FromSqlRaw 对实体类型所做的那样。

执行非查询SQL

在某些情况下,可能需要执行不返回任何数据的 SQL,通常用于修改数据库中的数据或 调用不返回任何结果集的存储过程。 可以通过 ExecuteSql 完成此操作.

它会执行提供的 SQL 并返回修改的行的数目。 ExecuteSql 使用安全的参数化来防止 SQL 注入,就像 FromSql 一样,而 ExecuteSqlRaw 允许动态构造 SQL 查询,就像 FromSqlRaw 对查询所做的那样。

注意:

在 EF Core 7.0 之前,有时必须使用 ExecuteSql API 对数据库执行“批量更新”,如上 所示;这比在查询所有匹配行后使用 SaveChanges 来修改它们要高效得多。 EF Core 7.0 引入了 ExecuteUpdate 和 ExecuteDelete,因此可以通过 LINQ 表达高效的批量 更新操作。 建议尽可能使用这些 API 而非 ExecuteSql

编写高性能查询

  1. 比较不可为 null 的列比比较可为 null 的列更简单且更快。 如果可能,请考虑将列标 记为不可为 null。
  2. 检查相等性 ( == ) 比检查不相等 ( != ) 更简单且更快,因为查询无需区分 null 和 false 结果。 尽可能使用相等性比较。 不过,只是否定 == 比较这一点实际上与 != 等效,因此不会提高性能。

查询的工作原理

Entity Framework Core 使用语言集成查询 (LINQ) 来查询数据库中的数据。 通过 LINQ 可使用 C#(或你选择的 .NET 语言)基于派生上下文和实体类编写强类型查询。

查询的寿命

下面的描述是每个查询所经历的过程的综合概述。

1. LINQ 查询由 Entity Framework Core 处理,用于生成已准备好由数据库提供程序处理的表示形式

a. 结果会被缓存,以便每次执行查询时无需进行此处理

2. 结果会传递到数据库提供程序

a. 数据库提供程序确定可以在数据库中评估查询的哪些部分

b. 查询的这些部分会转换为特定数据库的查询语言(例如关系数据库的 SQL)

c. 查询会发送到数据库并返回结果集(返回的是数据库中的值,而不是实体实例中的)

3. 对于结果集中的每一项

a. 如果该查询是跟踪查询,EF 会检查数据是否表示上下文实例内更改跟踪器中的现有实体,如果是,则会返回现有实体;如果不是,则会创建新实体、设置更改跟踪并返回该新实体

b. 如果该查询是非跟踪查询,将始终创建并返回新实体

执行查询时

调用 LINQ 运算符时,只会构建查询在内存中的表示形式。 使用结果时,查询才会发送到数据库。

导致查询发送到数据库的最常见操作如下:

  1. 在 for 循环中循环访问结果
  2. 使用 ToList、ToArray、Single、Count 等操作或等效的异步重载

备注

  1. LINQ 的JOIN 、GroupJOin 、SelectMany 都可以实现INNER JOIN
  2. LINQ 的GroupJoin、SelectMany 与DefaultIfEmpty 结合使用可实现LEFT JOIN
  3. 预先加载 Incluede ,ThenInclude 转换SQL 是LEFT JOIN 联表查询

标签:Core,EF,查询,context,SQL,var
From: https://www.cnblogs.com/xjxue/p/17555148.html

相关文章

  • jquery怎么实现点查询时页面淡化并转圈提示正在加载
    jQuery实现点查询时页面淡化并转圈提示正在加载在现代的网页应用中,用户体验是至关重要的一部分。当用户进行查询操作时,如果页面没有及时给出反馈,用户可能会感到焦虑和不耐烦。因此,在进行查询时,我们可以使用jQuery来实现页面的淡化效果,并显示一个加载提示,以提升用户体验。实际问题......
  • Codeforces Round #881 (Div. 3) A-F
    比赛链接A代码#include<bits/stdc++.h>usingnamespacestd;usingll=longlong;inta[57];boolsolve(){intn;cin>>n;for(inti=1;i<=n;i++)cin>>a[i];sort(a+1,a+n+1);intsum=0;for(inti......
  • 【EF Core】主从实体关系与常见实体关系的区别
    上次老周扯了有关主、从实体的话题,本篇咱们再挖一下,主、从实体之间建立的关系,跟咱们常用的一对一、一对多这些关系之间有什么不同。先看看咱们从学习数据库开始就特熟悉的常用关系——多对多、一对一、一对多说起。数据实体之间会建立什么样的关系,并不是规则性的,而是要看数据的功......
  • SpringBoot中整合Sharding Sphere实现数据加解密/数据脱敏/数据库密文,查询明文
    场景为防止数据泄露,需要在插入等操作时将某表的字段在数据库中加密存储,在需要查询使用时明文显示。ShardingSphereShardingSphere是一套开源的分布式数据库中间件解决方案组成的生态圈,它由Sharding-JDBC、Sharding-Proxy和Sharding-Sidecar(计划中)这3款相互独立的产品组成。......
  • C#查询本机所在网段的所有IP以及计算目标IP对应同局域网的本机IP
    1借助System.Net.NetworkInformation.NetworkInterface作为切入点获取所有的网卡接口2通过NetworkInterface.OperationalStatus状态判断是否可以传送包3 通过NetworkInterface.NetworkInterfaceType判断网卡类型4通过UnicastIPAddressInformation.Address和 UnicastIP......
  • mysql 预约时间 开始时间 结束时间 查询
    MySQL预约时间查询在许多应用程序中,我们需要管理预约时间。比如,医院预约系统、会议室预约系统等。MySQL是一个流行的关系型数据库管理系统,我们可以使用它来存储和查询预约时间数据。本文将介绍如何使用MySQL进行预约时间的查询操作,并提供相应的代码示例。数据库设计首先,我们需......
  • mysql三表连接查询sql语句
    实现MySQL三表连接查询SQL语句的步骤在MySQL中,我们可以通过使用JOIN关键字来实现多个表的连接查询。具体而言,三表连接查询是指同时连接三张表,根据表之间的关联关系进行数据的查询。下面是实现MySQL三表连接查询SQL语句的步骤:步骤一:建立三张表在进行三表连接查询之前,首先需要建立......
  • SQLServer 查询语句指定排序规则(查询时区分大小写)
    SQLServer查询语句指定排序规则(查询时区分大小写)介绍可以使用COLLATE子句将字符表达式应用于某个排序规则。为字符文本和变量分配当前数据库的默认排序规则。为列引用分配列的定义排序规则。COLLATE定义数据库或表列的排序规则,或应用于字符串表达式时的排序规则强制转换......
  • ES 实战复杂sql查询、修改字段类型
    转载请注明出处:1.查询索引得mapping与setting get直接查询索引名称时,会返回该索引得mapping和settings得配置,上述返回得结构如下:{"terra-syslog_2023-07-12":{"aliases":{},"mappings":{"properties":{"@ti......
  • Codeforces Round 881 (Div. 3) D - Apple Tree(dfs)
    https://codeforces.com/contest/1843/problem/D题目大意:一颗树中,每次给定两个结点,每个结点都可以移动到孩子结点,最后可以到达叶子结点,问我们这两个结点最终移到叶子结点有多少种组合?(其实就是让求以这两个节点为根的子树的叶子结点个数的乘积)input2512345332......