EF Core 中DbContext不会跟踪聚合方法和Join方法返回的结果,及FromSql方法使用讲解
EF Core中:
- 如果调用Queryable.Count等聚合方法,不会导致DbContext跟踪(track)任何实体。
- 此外调用Queryable.Join方法返回的匿名类型也不会被DbContext所跟踪(实测调用Queryable.Join方法返回EF Core中的实体类型也不会被DbContext所跟踪)。
Queryable.Count等聚合方法和Queryable.Join方法返回的结果不会被跟踪,原因是因为这两种方法返回的结果类型并没有被DbContext的OnModelCreating方法映射为实体,所以DbContext自然就不会去跟踪这两种方法返回的结果。
RelationalQueryableExtensions.FromSql方法
RelationalQueryableExtensions.FromSql方法的签名如下:
public static IQueryable<TEntity> FromSql<TEntity>([NotNullAttribute] this IQueryable<TEntity> source, [NotParameterized] RawSqlString sql, [NotNullAttribute] params object[] parameters) where TEntity : class;
可以看到FromSql方法其实是IQueryable<TEntity>类型的扩展方法,由于其返回的也是IQueryable<TEntity>类型,所以我们在使用FromSql方法时还可以结合其它Linq方法,例如下面的示例中,我们在FromSql方法后还使用了Linq中的Count方法来做聚合查询:
var users = dbContext.User.FromSql<User>("select * from [MD].[User]").Count();
这时FromSql方法传入的SQL语句会作为子查询,我们可以通过EF Core的后台日志看到生成的SQL语句如下:
=============================== EF Core log started ===============================
Executed DbCommand (58ms) [Parameters=[], CommandType='Text', CommandTimeout='0']
SELECT COUNT(*)
FROM (
select * from [MD].[User]
) AS [u]
=============================== EF Core log finished ===============================
此外因为FromSql方法是IQueryable<TEntity>类型的扩展方法,所以我们也可以在FromSql方法前使用其它Linq方法,例如下面的例子中我们在FromSql方法前使用Where方法来查询User表中Username不为null的行:
var users = dbContext.User.Where(e => e.Username != null).FromSql<User>("select * from [MD].[User]").Count();
我们可以通过EF Core的后台日志看到生成的SQL语句如下:
=============================== EF Core log started ===============================
Executed DbCommand (68ms) [Parameters=[], CommandType='Text', CommandTimeout='0']
SELECT COUNT(*)
FROM (
select * from [MD].[User]
) AS [e]
WHERE [e].[Username] IS NOT NULL
=============================== EF Core log finished ===============================
我们可以看到,EF Core生成的后台SQL语句在外层查询上加了一个Where条件来过滤Username不为null的行。
FromSql方法还可以直接查询数据库中的存储过程,例如我们现在有一个数据库存储过程叫SP_GetUsers,其中只有一个简单的User表查询,定义如下:
CREATE PROCEDURE [MD].[SP_GetUsers]
AS
BEGIN
select * from [MD].[User]
END
GO
我们可以使用FromSql方法调用存储过程SP_GetUsers来返回User表的三行数据,如下所示:
var users = dbContext.User.FromSql<User>("exec [MD].[SP_GetUsers]").ToList();
可以看到FromSql方法最后成功返回了三个User实体。
也可以在FromSql方法调用存储过程时使用其它Linq方法,例如下面我们在FromSql方法后使用了Count聚合查询:
var users = dbContext.User.FromSql<User>("exec [MD].[SP_GetUsers]").Count();
不过这个时候,我们可以通过下面EF Core的后台日志看到,其做SQL查询的时候只是调用了存储过程,并没有做Count聚合查询,说明聚合查询Count是EF Core将数据库的数据加载到内存中的实体对象后再做的,没有在数据库层面做Count聚合查询。
=============================== EF Core log started ===============================
Executed DbCommand (62ms) [Parameters=[], CommandType='Text', CommandTimeout='0']
exec [MD].[SP_GetUsers]
=============================== EF Core log finished ===============================
FromSql方法也支持在查询时传入参数,示例如下:
var users = dbContext.User.FromSql<User>("select * from [MD].[User] Where Username={0} AND DataStatus={1}", "Jim", ).ToList();
我们可以通过EF Core的后台日志看到生成的SQL语句如下:
=============================== EF Core log started ===============================
Executed DbCommand (151ms) [Parameters=[@p0='?' (Size = 4000), @p1='?' (DbType = Int32)], CommandType='Text', CommandTimeout='0']
select * from [MD].[User] Where Username=@p0 AND DataStatus=@p1
=============================== EF Core log finished ===============================
FromSql方法返回的实体对象会被DbContext所跟踪
FromSql方法返回的实体对象(该对象的类型有被DbContext的OnModelCreating方法映射为实体),是会被DbContext所跟踪的,比如调用下面FromSql方法中的SQL会返回三行User数据生成三个User实体,这三个User实体是存在于DbContext中被跟踪的实体集合中的。
var users = dbContext.User.FromSql<User>("select u1.* from [MD].[User] as u1 inner join [MD].[User] as u2 on u1.UserCode=u2.UserCode").ToList();
我们可以通过EF Core的后台日志看到生成的SQL语句如下:
=============================== EF Core log started ===============================
Executed DbCommand (55ms) [Parameters=[], CommandType='Text', CommandTimeout='0']
select u1.* from [MD].[User] as u1 inner join [MD].[User] as u2 on u1.UserCode=u2.UserCode
=============================== EF Core log finished ===============================
FromSql方法中SQL语句查询的列顺序可以和实体类的属性顺序不一样
例如我们现在在SQL Server数据库中有一个表Language,其有三个列定义如下:
CREATE TABLE [MD].[Language](
[ID] [int] IDENTITY(1,1) NOT NULL,
[LanguageCode] [nvarchar](20) NULL,
[LanguageName] [nvarchar](50) NULL,
CONSTRAINT [PK_Language] PRIMARY KEY CLUSTERED
(
[ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY],
CONSTRAINT [IX_Language] UNIQUE NONCLUSTERED
(
[LanguageCode] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
然后其生成的EF Core实体类Language如下,类中三个属性的顺序和Language表中三个列的顺序相同:
public partial class Language
{
public int Id { get; set; }
public string LanguageCode { get; set; }
public string LanguageName { get; set; }
}
然后我们使用EF Core的FromSql方法来用SQL语句查询Language表时,在SQL查询中将列的顺序反过来写,如下所示:
string sql = @"
SELECT
[LanguageName],
[LanguageCode],
[ID]
FROM [MD].[Language]
"; var languages = dbContext.Language.FromSql(sql).ToList();
可以看到虽然SQL语句中,SELECT语句后面的列顺序和实体类Language的属性顺序不一致,但是这对于FromSql方法来说并没有影响,FromSql方法还是正确地查询出了两条Language表的数据,如下图所示:
FromSql方法中SQL语句返回的列最好和EF Core的实体类相匹配
FromSql方法中SQL语句返回的列数,默认情况下不能小于EF Core实体类的属性数。
例如我们现在数据库中有一个User表,有五个数据列:
CREATE TABLE [dbo].[User](
[ID] [int] IDENTITY(1,1) NOT NULL,
[Name] [nvarchar](50) NULL,
[Age] [int] NULL,
[Sex] [int] NULL,
[Email] [nvarchar](50) NULL,
CONSTRAINT [PK_User] PRIMARY KEY CLUSTERED
(
[ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
但是我们在EF Core的实体类User中,多定义了一个属性DataStatus,如下所示:
public partial class User
{
public int Id { get; set; }
public string Name { get; set; }
public int? Age { get; set; }
public int? Sex { get; set; }
public string Email { get; set; }
public int? DataStatus { get; set; }
}
然后我们使用FromSql方法时,在SQL查询中不查询列DataStatus:
var users = dbContext.User.FromSql(@"SELECT
[ID]
,[Name]
,[Age]
,[Sex]
,[Email]
FROM [dbo].[User]").ToList();
执行时FromSql方法会抛出System.InvalidOperationException异常:
异常信息显示,列DataStatus在FromSql方法的返回结果中不存在。
如果在EF Core实体类User中,实在有多的属性DataStatus,其实也是可以的,但是要在DataStatus属性上标记NotMapped特性(所属System.ComponentModel.DataAnnotations.Schema命名空间),如下所示:
public partial class User
{
public int Id { get; set; }
public string Name { get; set; }
public int? Age { get; set; }
public int? Sex { get; set; }
public string Email { get; set; } [NotMapped]
public int? DataStatus { get; set; }
}
这样使用FromSql方法时,在SQL查询中不查询列DataStatus,就不会抛出异常了,只不过返回的结果中,DataStatus属性全为null而已:
另外如果EF Core实体类User中,属性DataStatus是非public(internal、protected、private)的:
public partial class User
{
public int Id { get; set; }
public string Name { get; set; }
public int? Age { get; set; }
public int? Sex { get; set; }
public string Email { get; set; }
internal int? DataStatus { get; set; }
}
那么在使用FromSql方法时,在SQL查询中不查询列DataStatus,也是不会抛出异常的,只不过返回的结果中,DataStatus属性全为null而已:
其实DataStatus是非public(internal、protected、private)的时候,就算使用FromSql方法时,在SQL查询中查询了列DataStatus:
var users = dbContext.User.FromSql(@"SELECT
[ID]
,[Name]
,[Age]
,[Sex]
,[Email]
,1 AS [DataStatus]
FROM [dbo].[User]").ToList();
EF Core实体类User的DataStatus属性也始终为null:
因为FromSql方法只会为public的EF Core实体类属性绑定值。
此外如果EF Core实体类User中,属性DataStatus只有get或set访问器:
只有get访问器:
public partial class User
{
public int Id { get; set; }
public string Name { get; set; }
public int? Age { get; set; }
public int? Sex { get; set; }
public string Email { get; set; } protected int? dataStatus;
public int? DataStatus
{
get
{
return dataStatus;
}
}
}
只有set访问器:
public partial class User
{
public int Id { get; set; }
public string Name { get; set; }
public int? Age { get; set; }
public int? Sex { get; set; }
public string Email { get; set; } protected int? dataStatus;
public int? DataStatus
{
set
{
dataStatus = value;
}
}
}
那么在使用FromSql方法时,在SQL查询中不查询列DataStatus,也是不会抛出异常的:
相反如果FromSql方法中SQL语句返回的列数,大于EF Core实体类的属性数,这是完全没问题的:
例如我们在EF Core的实体类User中,有五个属性,如下所示:
public partial class User
{
public int Id { get; set; }
public string Name { get; set; }
public int? Age { get; set; }
public int? Sex { get; set; }
public string Email { get; set; }
}
然后我们使用FromSql方法时,在SQL查询中多查询一个列DataStatus,如下:
var users = dbContext.User.FromSql(@"SELECT
[ID]
,[Name]
,[Age]
,[Sex]
,[Email]
,1 AS [DataStatus]
FROM [dbo].[User]").ToList();
这样执行是完全没有问题的,FromSql方法不会抛出异常:
最后,通过实验发现,目前在EF Core的实体类中,也不是所有数据类型的属性都要求FromSql方法的返回结果中要有列相对应:
- C#中所有SQL Server数据库基础类型:int(对应SQL Server类型int)、string(对应SQL Server类型nvarchar或varchar)、DateTime(对应SQL Server类型datetime),byte[](对应SQL Server类型varbinary)等,要求FromSql方法的返回结果中要有列相对应
- C#中枚举(enum)类型,要求FromSql方法的返回结果中要有列相对应
- C#中的复杂类型,最典型的例子就是C#中自定义的类,不要求FromSql方法的返回结果中要有列相对应
以上总结如果以后发现进一步信息,会再做更新。
EF Core 3.0更新
注意在EF Core 3.0中,FromSql方法和ExecuteSqlCommand方法都已经过时,请使用FromSqlRaw方法和ExecuteSqlRaw方法进行替代
EF Core 中DbContext不会跟踪聚合方法和Join方法返回的结果,及FromSql方法使用讲解的更多相关文章
- EF Core中DbContext可以被Dispose多次
我们知道,在EF Core中DbContext用完后要记得调用Dispose方法释放资源.但是其实DbContext可以多次调用Dispose方法,虽然只有第一次Dispose会起作用,但是DbCon ...
- EF Core 中多次从数据库查询实体数据,DbContext跟踪实体的情况
使用EF Core时,如果多次从数据库中查询一个表的同一行数据,DbContext中跟踪(track)的实体到底有几个呢?我们下面就分情况讨论下. 数据库 首先我们的数据库中有一个Person表,其建 ...
- EF Core中Key属性相同的实体只能被跟踪(track)一次
在EF Core的DbContext中,我们可以通过DbContext或DbSet的Attach方法,来让DbContext上下文来跟踪(track)一个实体对象,假设现在我们有User实体对象,其U ...
- EF Core中如何正确地设置两张表之间的关联关系
数据库 假设现在我们在SQL Server数据库中有下面两张表: Person表,代表的是一个人: CREATE TABLE [dbo].[Person]( ,) NOT NULL, ) NULL, ...
- [小技巧]EF Core中如何获取上下文中操作过的实体
原文地址:https://www.cnblogs.com/lwqlun/p/10576443.html 作者:Lamond Lu 源代码:https://github.com/lamondlu/EFC ...
- EF Core中避免贫血模型的三种行之有效的方法(翻译)
Paul Hiles: 3 ways to avoid an anemic domain model in EF Core 1.引言 在使用ORM中(比如Entity Framework)贫血领域模型 ...
- EF Core中的多对多映射如何实现?
EF 6.X中的多对多映射是直接使用HasMany-HasMany来做的.但是到了EF Core中,不再直接支持这种方式了,可以是可以使用,但是不推荐,具体使用可以参考<你必须掌握的Entity ...
- 9.4 翻译系列:EF 6以及 EF Core中的NotMapped特性(EF 6 Code-First系列)
原文链接:http://www.entityframeworktutorial.net/code-first/notmapped-dataannotations-attribute-in-code-f ...
- EF Core中Join可以进行子查询
我们来看看下面的代码,这个代码是一个INNER JOIN的EF Core查询,其中用SubCategory表INNER JOIN了SubCategoryLanguage表,但是我们需要在SubCate ...
随机推荐
- For循环中由于ajax异步导致的问题解决(增加alert数据正常,去掉alert之后数据错误)
由于ajax异步请求的机制,for循环运行不会等内部ajax请求结束,而直接循环到最后.解决方法:将for循环里面的请求单独封装一个方法. 个人遇到的问题具体如下 下面这段代码,如果第5行studat ...
- SPOJ:NSUBSTR - Substrings
题面 字符串$ S \(最多包含\) 25 \(万个小写拉丁字母.我们将\) F(x) \(定义为长度为\) x \(的某些字符串出现在\) s \(中的最大次数.例如,对于字符串\) "a ...
- 洛谷P4165 [SCOI2007]组队(排序 堆)
题意 题目链接 Sol 跟我一起大喊:n方过百万,暴力踩标算! 一个很显然的思路是枚举\(H, S\)的最小值算,复杂度\(O(n^3)\) 我们可以把式子整理一下,变成 \[A H_i + B S_ ...
- Codeforces Round #417 B. Sagheer, the Hausmeister
B. Sagheer, the Hausmeister time limit per test 1 second memory limit per test 256 megabytes Som ...
- Windows 10 Framework 3.5 _x64 离线安装包 最新安装版
原文:http://www.jb51.net/softs/325481.html Windows 10 Framework 3.5 离线安装包,适用于 Win10 和 Server 2016 离线安装 ...
- MUI框架-12-使用原生底部选项卡(凸出图标案例)
MUI框架-12-使用原生底部选项卡(凸出图标案例) 今天,用 mui 做 app 时,遇到了可能各位都遇到过的头疼问题:底部中间图标凸起,如下图: 最后有源代码 [提示]:有人问我在 HBuilde ...
- JS的排序算法
排序是最基本的算法(本文排序为升序Ascending),常见的有以下几种: 1.冒泡排序 Bubble Sort 2.选择排序 Selection Sort 3.插入排序 Insertion Sort ...
- 四、angularjs 如何在页面没有登录的情况下阻止用户通过更改url进入页面--$stateChangeStart
有时候用户没有登录或者在某些情况下你是不希望用户进入页面,但是angular的路由机制可以让用户直接通过更改Url进入页面,如何处理这一问题呢? ——监控路由转换机制 $stateChangeStar ...
- 中间件(WAS、WMQ)运维 9个常见难点解析
本文由社区中间件达人wangxuefeng266.ayy216226分享整理,包括WAS.WMQ在安装.巡检.监控.优化过程中的常见难点. 安装 1.was 负载均衡的机制的粘连性,was负载均衡异常 ...
- 如何对iPhone进行屏幕录像
如何对iPhone进行屏幕录像 录制时候的效果: 1. 打开QuickTime Player 2. 在文件中新建影片录制 3. 然后酱紫录制