-->

EF 核心:

  • 如果调用 Queryable.Count 等聚合方法,将不会导致 DbContext 跟踪任何实体。
  • 另外,调用Queryable.Join方法返回的匿名类型不会被DbContext跟踪(实际上,EF Core中调用Queryable.Join方法返回的实体类型也不会被DbContext跟踪)。

不会跟踪 Queryable.Count 和 Queryable.Join 等聚合方法返回的结果。原因是这两个方法返回的结果类型没有被DbContext的OnModelCreating方法映射到实体,所以DbContext自然不会跟踪这两个方法返回的结果。

RelationalQueryableExtensions.FromSql 方法

RelationalQueryableExtensions.FromSql 方法的签名如下:

公共静态 IQueryable FromSql([NotNullAttribute] 此 IQueryable 源,[NotParameterized] RawSqlString sql,[NotNullAttribute] params object[] 参数) 其中 TEntity : class;

可以看到FromSql方法实际上是IQueryable类型的扩展方法。由于它也返回 IQueryable 类型,因此我们在使用 FromSql 方法时也可以将其与其他 Linq 方法结合起来,如下例所示,我们在 FromSql 方法之后还使用了 Linq 中的 Count 方法来进行聚合查询:

var users = dbContext.User.FromSql("从[MD].[User]中选择*").Count();

此时FromSql方法传入的SQL语句将作为子查询使用。我们通过EF Core的后台日志可以看到生成的SQL语句如下:

================================= EF Core 日志已启动 =============== ================
执行 DbCommand (58ms) [参数=[], CommandType='Text', CommandTimeout='0']
选择计数(*)
从 (
从[MD]中选择*。[用户]
) AS [u]
================================= EF Core日志完成=============== ================

另外,由于FromSql方法是IQueryable类型的扩展方法,因此我们还可以在FromSql方法之前使用其他Linq方法。例如,在下面的例子中,我们在FromSql方法之前使用Where方法来查询User表中的Username是否为null的Rows:

var users = dbContext.User.Where(e => e.Username != null).FromSql("从 [MD].[User] 选择*").Count();

我们可以通过EF Core的后台日志看到生成的SQL语句如下:

================================= EF Core 日志已启动 =============== ================
执行 DbCommand (68ms) [参数=[], CommandType='Text', CommandTimeout='0']
选择计数(*)
从 (
从[MD]中选择*。[用户]
) AS [e]
WHERE [e].[用户名]不为空
================================= EF Core日志完成=============== ================

我们可以看到EF Core生成的后台SQL语句在外层查询中添加了Where条件来过滤Username不为null的行。

FromSql方法还可以直接查询数据库中的存储过程。例如,我们现在有一个名为 SP_GetUsers 的数据库存储过程,它只有一个简单的 User 表查询,定义如下:

创建过程 [MD].[SP_GetUsers]
AS
开始
从[MD]中选择*。[用户]

GO

我们可以使用FromSql方法调用存储过程SP_GetUsers来返回User表中的三行数据,如下所示:

var users = dbContext.User.FromSql("exec [MD].[SP_GetUsers]").ToList();

可以看到FromSql方法最终成功返回了三个User实体。

当 FromSql 方法调用存储过程时,您还可以使用其他 Linq 方法。例如,下面我们在 FromSql 方法之后使用 Count 聚合查询:

var users = dbContext.User.FromSql("exec [MD].[SP_GetUsers]").Count();

================================= EF Core 日志已启动 =============== ================
执行 DbCommand (62ms) [参数=[], CommandType='Text', CommandTimeout='0']
执行 [MD].[SP_GetUsers]
================================= EF Core日志完成=============== ================

FromSql方法还支持查询时传入参数,示例如下:

var users = dbContext.User.FromSql("从 [MD].[User] 选择 *,其中 Username={0} AND DataStatus={1}", "Jim", ).ToList();

我们可以通过EF Core的后台日志看到生成的SQL语句如下:

================================= EF Core 日志已启动 =============== ================
执行 DbCommand (151ms) [参数=[@p0='?' (大小 = 4000),@p1='?' (DbType = Int32)], CommandType='文本', CommandTimeout='0']
从 [MD].[用户] 中选择 *,其中用户名=@p0 AND 数据状态=@p1
================================= EF Core日志完成=============== ================

FromSql方法返回的实体对象将被DbContext跟踪

FromSql方法返回的实体对象(对象的类型通过DbContext的OnModelCreating方法映射到实体)将被DbContext跟踪。例如,调用下面的FromSql方法中的SQL将返回三行User数据并生成三个User实体,这三个User实体存在于DbContext中的跟踪实体集合中。

var users = dbContext.User.FromSql("从 [MD].[User] 选择 u1.* 作为 u1 内部联接 [MD].[User] 作为 u1.UserCode=u2.UserCode 上的 u2").ToList ();

我们可以通过EF Core的后台日志看到生成的SQL语句如下:

================================= EF Core 日志已启动 =============== ================
执行 DbCommand (55ms) [参数=[], CommandType='Text', CommandTimeout='0']
从 [MD].[User] 选择 u1.* 作为 u1 内连接 [MD].[User] 作为 u1.UserCode=u2.UserCode
上的 u2 ================================= EF Core日志完成=============== ================

FromSql方法中SQL语句查询的列顺序可以与实体类的属性顺序不同

例如,我们现在在SQL Server数据库中有一个表Language,它有如下三列定义:

创建表 [MD].[语言](
[ID] [int] IDENTITY(1,1) NOT NULL,
[语言代码] [nvarchar](20) NULL,
[语言名称] [nvarchar](50) NULL,
约束 [PK_Language] 主键聚集
(
[ID] ASC
)WITH (PAD_INDEX = OFF、STATISTICS_NORECOMPUTE = OFF、IGNORE_DUP_KEY = OFF、ALLOW_ROW_LOCKS = ON、ALLOW_PAGE_LOCKS = ON) ON [主],
约束 [IX_Language] 唯一非聚集
(
[语言代码] ASC
)WITH (PAD_INDEX = OFF、STATISTICS_NORECOMPUTE = OFF、IGNORE_DUP_KEY = OFF、ALLOW_ROW_LOCKS = ON、ALLOW_PAGE_LOCKS = ON) ON [主]
) 在 [主要]
GO

则生成的EF Core实体类Language如下。类中三个属性的顺序与语言表中三列的顺序相同:

公共部分类语言
{
公共 int Id { 得到;放; }
公共字符串语言代码{获取;放; }
公共字符串语言名称 { get;放; }
}

然后我们使用 EF Core 的 FromSql 方法通过 SQL 语句查询 Language 表,并反转 SQL 查询中列的顺序,如下所示:

字符串sql = @"
选择
[语言名称],
[语言代码],
[ID]
来自[MD].[语言]
”; var languages = dbContext.Language.FromSql(sql).ToList();

可以看到,虽然在SQL语句中,SELECT语句之后的列顺序与实体类Language的属性顺序不一致,但这对FromSql方法没有影响。 FromSql 方法仍然正确查询两个语言表。数据,如下图:

FromSql方法中SQL语句返回的列与EF Core的实体类最匹配

FromSql 方法中 SQL 语句返回的列数默认不能小于 EF Core 实体类的属性数。

例如,我们现在数据库中有一个 User 表,有五个数据列:

创建表 [dbo].[用户](
[ID] [int] IDENTITY(1,1) NOT NULL,
[名称] [nvarchar](50) NULL,
[年龄] [int] NULL,
[性别] [int] NULL,
[电子邮件] [nvarchar](50) NULL,
约束 [PK_User] 主键聚集
(
[ID] ASC
)WITH (PAD_INDEX = OFF、STATISTICS_NORECOMPUTE = OFF、IGNORE_DUP_KEY = OFF、ALLOW_ROW_LOCKS = ON、ALLOW_PAGE_LOCKS = ON) ON [主]
) 在 [主要]

但是我们在EF Core实体类User中额外定义了一个属性DataStatus,如下所示:

公共部分类 User
{
公共 int Id { 得到;放; }
公共字符串名称{获取;放; }
公共整数?年龄{得到;放; }
公共整数?性别{得到;放; }
公共字符串电子邮件{获取;放; }
公共整数?数据状态{获取;放; }
}

那么当我们使用FromSql方法时,SQL查询中并没有查询到DataStatus列:

var users = dbContext.User.FromSql(@"SELECT
[ID]
,[姓名]
,[年龄]
,[性别]
,[邮箱]
FROM [dbo].[用户]").ToList();

FromSql方法执行时会抛出System.InvalidOperationException:

异常信息显示FromSql方法的返回结果中不存在DataStatus列。

如果EF Core实体类User中有很多属性DataStatus,其实也是可以的,但是DataStatus属性上必须标注NotMapped属性(属于System.ComponentModel.DataAnnotations.Schema命名空间),如下图:

公共部分类 User
{
公共 int Id { 得到;放; }
公共字符串名称{获取;放; }
公共整数?年龄{得到;放; }
公共整数?性别{得到;放; }
公共字符串电子邮件{获取;放; } [未映射]
公共整数?数据状态{获取;放; }
}

这样使用FromSql方法时,如果SQL查询中没有查询到DataStatus列,则不会抛出异常。但返回结果中,DataStatus属性全部为空:

另外,如果 EF Core 实体类 User 中的属性 DataStatus 为非公共(内部、受保护、私有):

公共部分类 User
{
公共 int Id { 得到;放; }
公共字符串名称{获取;放; }
公共整数?年龄{得到;放; }
公共整数?性别{得到;放; }
公共字符串电子邮件{获取;放; }
内部整数?数据状态{获取;放; }
}

那么使用FromSql方法时,如果SQL查询中没有查询到DataStatus列,则不会抛出异常。但返回结果中,DataStatus属性全部为空:

事实上,当DataStatus为非公开(内部、受保护、私有)时,即使使用FromSql方法,SQL查询中也会查询DataStatus列:

var users = dbContext.User.FromSql(@"SELECT
[ID]
,[姓名]
,[年龄]
,[性别]
,[邮箱]
,1 AS [数据状态]
FROM [dbo].[用户]").ToList();

EF Core 实体类 User 的 DataStatus 属性始终为 null:

因为FromSql方法只会将值绑定到公共EF Core实体类属性。

另外,如果EF Core实体类User,属性DataStatus只有get或set访问器:

仅获取访问器:

公共部分类 User
{
公共 int Id { 得到;放; }
公共字符串名称{获取;放; }
公共整数?年龄{得到;放; }
公共整数?性别{得到;放; }
公共字符串电子邮件{获取;放; } 受保护的整数?数据状态;
公共整数?数据状态
{
得到
{
返回数据状态;
}
}
}

仅设置访问器:

公共部分类 User
{
公共 int Id { 得到;放; }
公共字符串名称{获取;放; }
公共整数?年龄{得到;放; }
公共整数?性别{得到;放; }
公共字符串电子邮件{获取;放; } 受保护的整数?数据状态;
公共整数?数据状态
{
套装
{
数据状态 = 值;
}
}
}

那么使用FromSql方法时,如果SQL查询中没有查询到DataStatus列,则不会抛出异常:

相反,如果 FromSql 方法中 SQL 语句返回的列数大于 EF Core 实体类的属性数,则完全没问题:

例如,在我们的 EF Core 实体类 User 中,我们有以下五个属性:

公共部分类 User
{
公共 int Id { 得到;放; }
公共字符串名称{获取;放; }
公共整数?年龄{得到;放; }
公共整数?性别{得到;放; }
公共字符串电子邮件{获取;放; }
}

然后我们在使用FromSql方法的时候,在SQL查询中又查询了一列DataStatus,如下:

var users = dbContext.User.FromSql(@"SELECT
[ID]
,[姓名]
,[年龄]
,[性别]
,[邮箱]
,1 AS [数据状态]
FROM [dbo].[用户]").ToList();

这样执行绝对没有问题,FromSql方法不会抛出异常:

最后通过实验发现,目前EF Core的实体类中,并不是所有的数据类型属性都需要FromSql方法的返回结果中对应的列:

  • C#中所有SQL Server数据库基本类型:int(对应SQL Server类型int)、string(对应SQL Server类型nvarchar或varchar)、DateTime(对应SQL Server类型datetime)、byte[](对应SQL Server类型datetime) SQL Server类型 varbinary )等,要求FromSql方法的返回结果必须有对应的列
  • C#中的枚举类型要求FromSql方法的返回结果必须有对应的列
  • C#中复杂类型最典型的例子就是C#中的自定义类,它不需要FromSql方法的返回结果中对应的列

如果将来有进一步的信息,以上总结将会更新。

EF Core 3.0更新
注意,在EF Core 3.0中,FromSql方法和ExecuteSqlCommand方法已过时,请改用FromSqlRaw方法和ExecuteSqlRaw方法

-->

关闭

赞赏
返回顶部